Optimize Nested-if using any alternative DataStructure in Java - java

How to optimize the nested-if block to have a quick comparison. Below is my code where it compares two different java objects. I have a member variable which has the pattern too which lies in one of the if block.
listOfFilters is a subset of Map<String, List<Filter>>. Below method is invoked with the below signature. This list can be as many as 400~1000.
checkRequest(incomingRequest,map.get(incomingRequest.getFiltersForThis()))
Problem -
public boolean checkRequest(Request incomingRequest, List<Filter> listOfFilters){
for(Filter filter : listOfFilters){
if(incomingRequest.getName() == filter.getName()){
if(incomingRequest.getOrigen() == filter.getOrigen()){
.....
.....
.....
filterMatched = true;
}
}
}
}
}
}
I need to compare the incoming request as above with each Filter available in the system. O(n) is the complexity.
Is there any way I can use the data structure to reduce the complexity from O(n) to O(log n).
Performance hits when the number of filters configured is more in the system.
I cannot use hashcode() or equals() because the incomingRequest should still succeed if the corresponding filter field is not available for it. It means the incomingRequest should match all the filter values but, in case if it doesn't have related filter field, it should just pass.
public boolean checkMatchOrigen(){
return (filter.getOrigen() == null || filter.getOrigen().isEmpty()) ||
(incomingRequest.getOrigen() != null &&
incomingRequest.getOrigen().trim().equals(filter.getOrigen()));
}

You could create a structure like a decision tree or a database index. There is the rather complicated task.
For example, you have four filters:
Name is n1, origin is o1;
Name is n1, origin is o2;
Name is n2, origin is o1;
Name is n2, origin is o5;
One of possible decision trees is:
or-->nameIs(n1)->and->or-->originIs(o1)
| |->originIs(o2)
|
|->nameIs(n2)->and->or-->originIs(o1)
|->originIs(o5)
The idea is to check 'n1' only once for both filters included it and so on. Usually, the stronges filters have to be checked first. Again, it's difficult to predict, which filter will reject more requests.
For example, i've build the tree from your data structure:
public class DemoApplication {
// Group filter list by names, except nulls
public static Map<String, List<Filter>> mapNameToFilter(List<Filter> filters) {
return filters
.stream()
.filter(filter -> filter.getName() != null)
.collect(groupingBy(Filter::getName));
}
// Create predicate to check name and all chunked origins for all entries
public static Predicate<Request> createPredicateByNameAndOrigin(Map<String, List<Filter>> nameToFilterMap) {
return nameToFilterMap
.keySet()
.stream()
.map(name -> {
final Predicate<Request> filterByName = request -> name.equals(request.getName());
final Map<String, List<Filter>> originToFilterMap = mapOriginToFilter(nameToFilterMap.get(name));
return filterByName.and(createPredicateByOrigin(originToFilterMap));
})
.reduce(Predicate::or)
.orElse(filter -> true);
}
// Group filter list by origins, except nulls
public static Map<String, List<Filter>> mapOriginToFilter(List<Filter> filters) {
return filters
.stream()
.filter(filter -> filter.getOrigin() != null)
.collect(groupingBy(Filter::getOrigin));
}
// Create predicate to check origin for all entries
public static Predicate<Request> createPredicateByOrigin(Map<String, List<Filter>> originToFilterMap) {
return originToFilterMap
.keySet()
.stream()
.map(origin -> {
final Predicate<Request> filterByOrigin = request -> origin.equals(request.getOrigin());
return filterByOrigin; // Or go deeper to create more complex predicate
})
.reduce(Predicate::or)
.orElse(filter -> true);
}
public static void main(String[] args) {
List<Filter> list = new ArrayList<>();
list.add(new Filter("n1", "o1"));
list.add(new Filter("n1", "o2"));
list.add(new Filter("n2", "o1"));
list.add(new Filter("n2", "o5"));
list.add(new Filter(null, "o10"));
list.add(new Filter(null, "o20"));
Predicate<Request> p = createPredicateByNameAndOrigin(mapNameToFilter(list));
System.out.println(p.test(new RequestImpl("n1", "2")));
System.out.println(p.test(new RequestImpl("n1", "1")));
System.out.println(p.test(new RequestImpl("n2", "1")));
System.out.println(p.test(new RequestImpl("n10", "3")));
}
}
I've used JDK Predicates which can be presented as a tree with operations as nodes. There is no correct processing with null values in this realization, but it can be easy added.
Note, that my tree is static and need to be rebuilded after each change of the filter list. And it's not balanced. So it's not a solution, just an example.
If you need only filter by equality critera, you could create map for each field. Again, the same grouping idea when checking. In this case, you can dynamically rebuild searching maps:
public class DemoApplication {
public static List<Filter> filters = new ArrayList<>();
public static Map<String, Set<Filter>> nameToFiltersMap = new HashMap<>();
public static Map<String, Set<Filter>> originToFiltersMap = new HashMap<>();
public static void addFilter(Filter filter) {
filters.add(filter);
// Rebuild name index
Set<Filter> nameFilters = nameToFiltersMap.getOrDefault(filter.getName(), new HashSet<>());
nameFilters.add(filter);
nameToFiltersMap.put(filter.getName(), nameFilters);
// Rebuild origin index
Set<Filter> originFilters = originToFiltersMap.getOrDefault(filter.getOrigin(), new HashSet<>());
originFilters.add(filter);
originToFiltersMap.put(filter.getOrigin(), originFilters);
}
public static boolean test(Request request) {
// Get all filters matched by name
Set<Filter> nameFilters = nameToFiltersMap.get(request.getName());
if (nameFilters != null) {
// Get all filters matched by origin
Set<Filter> originFilters = originToFiltersMap.get(request.getOrigin());
for (Filter nameFilter: nameFilters) {
if (originFilters != null && originFilters.contains(nameFilter)) {
return true; //filter matches
}
}
}
return false;
}
public static void main(String[] args){
addFilter(new Filter("n1", "o1"));
addFilter(new Filter("n1", "o2"));
addFilter(new Filter("n2", "o1"));
addFilter(new Filter("n2", "o5"));
addFilter(new Filter(null, "o7"));
addFilter(new Filter(null, "o8"));
System.out.println(test(new RequestImpl(null, "o7")));
System.out.println(test(new RequestImpl(null, "o9")));
System.out.println(test(new RequestImpl("n1", "o1")));
System.out.println(test(new RequestImpl("n1", "o3")));
System.out.println(test(new RequestImpl("n2", "o5")));
System.out.println(test(new RequestImpl("n3", "o3")));
}
}
Also, you can create a custom tree data structure with dynamic rebuilding and rebalancing. But may be better to use database or searching engine?

First, you should not use Object as the type of the request. At least for this question, use an interface having the appropriate methods, so that your code has a chance to compile.
interface Request { ... }
Then, if you have really many filters, you can group these filters by name.
Map<String, List<Filter>> filtersByName = ...;
After that, your filtering code becomes:
String reqName = blankToNull(request.getName());
if (reqName != null) {
List<Filter> nameFilters = filtersByName.get(reqName);
if (anyFilterMatches(nameFilters, request)) {
return Decision.REJECT;
}
}
If any of these filters rejects the request, you're done. Otherwise proceed with the next field.
This pattern will be more efficient if the names of the filters differ a lot.

Related

Extract a list containing duplicates from a list and also get the non-duplicate list java 8 stream

I am reading data from an excel file using apache poi and transforming it into a list of object. But now I want to extract any duplicates based on certain rules into another list of that object and also get the non-duplicate list.
Condition to check for a duplicate
name
email
phone number
gst number
Any of these properties can result in a duplicate. which mean or not an and
Party Class
public class Party {
private String name;
private Long number;
private String email;
private String address;
private BigDecimal openingBalance;
private LocalDateTime openingDate;
private String gstNumber;
// Getter Setter Skipped
}
Let's say this is the list returned by the logic to excel data so far
var firstParty = new Party();
firstParty.setName("Valid Party");
firstParty.setAddress("Valid");
firstParty.setEmail("Valid");
firstParty.setGstNumber("Valid");
firstParty.setNumber(1234567890L);
firstParty.setOpeningBalance(BigDecimal.ZERO);
firstParty.setOpeningDate(DateUtil.getDDMMDateFromString("01/01/2020"));
var secondParty = new Party();
secondParty.setName("Valid Party");
secondParty.setAddress("Valid Address");
secondParty.setEmail("Valid Email");
secondParty.setGstNumber("Valid GST");
secondParty.setNumber(7593612247L);
secondParty.setOpeningBalance(BigDecimal.ZERO);
secondParty.setOpeningDate(DateUtil.getDDMMDateFromString("01/01/2020"));
var thirdParty = new Party();
thirdParty.setName("Valid Party 1");
thirdParty.setAddress("address");
thirdParty.setEmail("email");
thirdParty.setGstNumber("gst");
thirdParty.setNumber(7593612888L);
thirdParty.setOpeningBalance(BigDecimal.ZERO);
secondParty.setOpeningDate(DateUtil.getDDMMDateFromString("01/01/2020"));
var validParties = List.of(firstParty, secondParty, thirdParty);
What I have attempted so far :-
var partyNameOccurrenceMap = validParties.parallelStream()
.map(Party::getName)
.collect(Collectors.groupingBy(Function.identity(), HashMap::new, Collectors.counting()));
var partyNameOccurrenceMapCopy = SerializationUtils.clone(partyNameOccurrenceMap);
var duplicateParties = validParties.stream()
.filter(party-> {
var occurrence = partyNameOccurrenceMap.get(party.getName());
if (occurrence > 1) {
partyNameOccurrenceMap.put(party.getName(), occurrence - 1);
return true;
}
return false;
})
.toList();
var nonDuplicateParties = validParties.stream()
.filter(party -> {
var occurrence = partyNameOccurrenceMapCopy.get(party.getName());
if (occurrence > 1) {
partyNameOccurrenceMapCopy.put(party.getName(), occurrence - 1);
return false;
}
return true;
})
.toList();
The above code only checks for party name but we also need to check for email, phone number and gst number.
The code written above works just fine but the readability, conciseness and the performance might be an issue as the data set is large enough like 10k rows in excel file
Never ignore Equals/hashCode contract
name, email, number, gstNumber
Any of these properties can result in a duplicate, which mean or
Your definition of a duplicate implies that any of these properties should match, whilst others might not.
It means that it's impossible to provide an implementation equals/hashCode that would match the given definition and doesn't violate the hashCode contract.
If two objects are equal according to the equals method, then calling the hashCode method on each of the two objects must produce the same integer result.
I.e. if you implement equals in such a way they any (not all) of these properties: name, email, number, gstNumber could match, and that would enough to consider the two objects equal, then there's no way to implement hashCode correctly.
And as the consequence of this, you can't use the object with a broken equals/hashCode implementation in with a hash-based Collection because equal objects might end up the in the different bucket (since they can produce different hashes). I.e. HashMap would not be able to recognize the duplicated keys, hence groupingBy with groupingBy() with Function.identity() as a classifier function would not work properly.
Therefore, to address this problem, you need to implement equals() based on all 4 properties: name, email, number, gstNumber (i.e. all these values have to be equal), and similarly all these values must contribute to hash-code.
How to determine Duplicates
There's no easy way to determine duplicates by multiple criteria. The solution you've provided is not viable, since we can't rely on the equals/hashCode.
The only way is to generate a HashMap separately for each end every attribute (i.e. in this case we need 4 maps). But can we alternate this, avoiding repeating the same steps for each map and hard coding the logic?
Yes, we can.
We can create a custom generic accumulation type (it would be suitable for any class - no hard-coded logic) that would encapsulate all the logic of determining duplicates and maintain an arbitrary number of maps under the hood. After consuming all the elements from the given collection, this custom object would be aware of all the duplicates in it.
That's how it can be implemented.
A custom accumulation type that would be used as container of a custom Collector. Its constructor expects varargs of functions, each function correspond to the property that should be taken into account while checking whether an object is a duplicate.
public static class DuplicateChecker<T> implements Consumer<T> {
private List<DuplicateHandler<T>> handles;
private Set<T> duplicates;
#SafeVarargs
public DuplicateChecker(Function<T, ?>... keyExtractors) {
this.handles = Arrays.stream(keyExtractors)
.map(DuplicateHandler::new)
.toList();
}
#Override
public void accept(T t) {
handles.forEach(h -> h.accept(t));
}
public DuplicateChecker<T> merge(DuplicateChecker<T> other) {
for (DuplicateHandler<T> handler: handles) {
other.handles.forEach(handler::merge);
}
return this;
}
public DuplicateChecker<T> finish() {
duplicates = handles.stream()
.flatMap(handler -> handler.getDuplicates().stream())
.flatMap(Set::stream)
.collect(Collectors.toSet());
return this;
}
public boolean isDuplicate(T t) {
return duplicates.contains(t);
}
}
A helper class representing a single createrion (like name, email, etc.) which encapsulates a HashMap. keyExtractor is used to obtain a key from an object of type T.
public static class DuplicateHandler<T> implements Consumer<T> {
private Map<Object, Set<T>> itemByKey = new HashMap<>();
private Function<T, ?> keyExtractor;
public DuplicateHandler(Function<T, ?> keyExtractor) {
this.keyExtractor = keyExtractor;
}
#Override
public void accept(T t) {
itemByKey.computeIfAbsent(keyExtractor.apply(t), k -> new HashSet<>()).add(t);
}
public void merge(DuplicateHandler<T> other) {
other.itemByKey.forEach((k, v) ->
itemByKey.merge(k,v,(oldV, newV) -> { oldV.addAll(newV); return oldV; }));
}
public Collection<Set<T>> getDuplicates() {
Collection<Set<T>> duplicates = itemByKey.values();
duplicates.removeIf(set -> set.size() == 1); // the object is proved to be unique by this particular property
return duplicates;
}
}
And that is the method, responsible for generating the map of duplicates, that would be used from the clean code. The given collection would be partitioned into two parts: one mapped to the key true - duplicates, another mapped to the key false - unique objects.
public static <T> Map<Boolean, List<T>> getPartitionByProperties(Collection<T> parties,
Function<T, ?>... keyExtractors) {
DuplicateChecker<T> duplicateChecker = parties.stream()
.collect(Collector.of(
() -> new DuplicateChecker<>(keyExtractors),
DuplicateChecker::accept,
DuplicateChecker::merge,
DuplicateChecker::finish
));
return parties.stream()
.collect(Collectors.partitioningBy(duplicateChecker::isDuplicate));
}
And that how you can apply it for your particular case.
main()
public static void main(String[] args) {
List<Party> parties = // initializing the list of parties
Map<Boolean, List<Party>> isDuplicate = partitionByProperties(parties,
Party::getName, Party::getNumber,
Party::getEmail, Party::getGstNumber);
}
I would use create a map for each property where
key is the property we want to check duplicate
value is a Set containing all the index of element in the list with same key.
Then we can
filter values in the map with more that 1 index (i.e. duplicate indexes).
union all the duplicate index
determine if the element is duplicate/unique by using the duplicate index.
The time complexity is roughly O(n).
public class UniquePerEachProperty {
private static void separate(List<Party> partyList) {
Map<String, Set<Integer>> nameToIndexesMap = new HashMap<>();
Map<String, Set<Integer>> emailToIndexesMap = new HashMap<>();
Map<Long, Set<Integer>> numberToIndexesMap = new HashMap<>();
Map<String, Set<Integer>> gstNumberToIndexesMap = new HashMap<>();
for (int i = 0; i < partyList.size(); i++) {
Party party = partyList.get(i);
nameToIndexesMap.putIfAbsent(party.getName(), new HashSet<>());
nameToIndexesMap.get(party.getName()).add(i);
emailToIndexesMap.putIfAbsent(party.getEmail(), new HashSet<>());
emailToIndexesMap.get(party.getEmail()).add(i);
numberToIndexesMap.putIfAbsent(party.getNumber(), new HashSet<>());
numberToIndexesMap.get(party.getNumber()).add(i);
gstNumberToIndexesMap.putIfAbsent(party.getGstNumber(), new HashSet<>());
gstNumberToIndexesMap.get(party.getGstNumber()).add(i);
}
Set<Integer> duplicatedIndexes = Stream.of(
nameToIndexesMap.values(),
emailToIndexesMap.values(),
numberToIndexesMap.values(),
gstNumberToIndexesMap.values()
).flatMap(Collection::stream).filter(indexes -> indexes.size() > 1)
.flatMap(Set::stream).collect(Collectors.toSet());
List<Party> duplicatedList = new ArrayList<>();
List<Party> uniqueList = new ArrayList<>();
for (int i = 0; i < partyList.size(); i++) {
Party party = partyList.get(i);
if (duplicatedIndexes.contains(i)) {
duplicatedList.add(party);
} else {
uniqueList.add(party);
}
}
System.out.println("duplicated:" + duplicatedList);
System.out.println("unique:" + uniqueList);
}
public static void main(String[] args) {
separate(List.of(
// name duplicate
new Party("name1", 1L, "email1", "gstNumber1"),
new Party("name1", 2L, "email2", "gstNumber2"),
// number duplicate
new Party("name3", 3L, "email3", "gstNumber3"),
new Party("name4", 3L, "email4", "gstNumber4"),
// email duplicate
new Party("name5", 5L, "email5", "gstNumber5"),
new Party("name6", 6L, "email5", "gstNumber6"),
// gstNumber duplicate
new Party("name7", 7L, "email7", "gstNumber7"),
new Party("name8", 8L, "email8", "gstNumber7"),
// unique
new Party("name9", 9L, "email9", "gstNumber9")
));
}
}
Assume Party has below constructor and toString()(for testing)
public class Party {
public Party(String name, Long number, String email, String gstNumber) {
this.name = name;
this.number = number;
this.email = email;
this.address = "";
this.openingBalance = BigDecimal.ZERO;
this.openingDate = LocalDateTime.MIN;
this.gstNumber = gstNumber;
}
#Override
public String toString() {
return "Party{" +
"name='" + name + '\'' +
", number=" + number +
", email='" + email + '\'' +
", gstNumber='" + gstNumber + '\'' +
'}';
}
...
}

If else with java 8 Lambda

I want to replace conventional if else with lambda. Consider following highlighted code, is there some simple way to have this represented with Lambda ?
public class IfElseLambda {
public static void main(String[] args) {
String value = null;
DataObj data = new DataObj();
List<DataObj> dataObjs = data.getDataObjs();
***if (dataObjs != null) {
value = dataObjs.stream().map(dataObject -> getValue(dataObject)).filter(Objects::nonNull).findFirst().orElse(null);
} else {
value = getValue(data);
}***
}
public static String getValue(DataObj dataObj) {
return "Get value from dataObj";
}
}
class DataObj {
List<DataObj> dataObjs;
public List<DataObj> getDataObjs() {
return dataObjs;
}
public void setDataObjs(List<DataObj> dataObjs) {
this.dataObjs = dataObjs;
}
}
One thing you can do is to change the null list to something which results in the same output:
List<DataObj> dataObjs = Optional.ofNullable(data.getDataObjs()).orElse(Collections.singletonList(data));
dataObjs will now be a list with a single element in the case that data.getDataObjs() is null.
Now you don't need the if/else:
value = dataObjs.stream().map(dataObject -> getValue(dataObject)).filter(Objects::nonNull).findFirst().orElse(null);
I your aim is to isolate the logic of your if-else, and potentially allowing it to be replaced, maybe you could do the following :
Your lambda take as input your data list, and gives you back a String value. Therefore, you can use a java.util.Function interface, like this:
Function<List<DataObj>, String> extractor = dataList
-> dataList == null? Stream.of(DEFAULT_DATA_OBJ) : dataList.stream()
.map(dataObject -> getValue(dataObject))
.filter(Objects::nonNull)
.findFirst()
.orElse(null)
Note, you still have a ternary operator (Do not see how you could do without it, because if your list can be null, you cannot even use Stream.concat to protect from empty-list). However, with that construct, the logic of your ternary operator is replaceable if you make the extractor function replaceable in your code.
Exemple:
public static void main(String... args) {
final List<DataObj> dataList = ...;
final DataObj defaultValue = ...;
Function<List<DataObj>, String> extractor = dataList
-> dataList == null? Stream.of(defaultValue) : dataList.stream()
.map(dataObject -> getValue(dataObject))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
doStuff(dataList, extractor);
// Now, if you want to change your extraction logic, do
doStuff(dataList, whatever -> "Return a constant title");
}
public static void doStuff(final List<DataObj> dataList, final Function<List<DataObj, String> titleExtractor) {
// Do stuff here
}

Is there a set-of-sets where sets can be addressed by any of their members?

I'm trying to find a data-structure in Java (or Groovy) that where something like this works:
MemberAdressableSetsSet mass = new MemberAdressableSetsSet();
mass.addSet(["a","b"]);
mass.addSet(["c","d","e"]);
mass.get("d").add("f");
String output = Arrays.toString(mass.get("e").toArray());
System.out.println(output); // [ "c", "d", "e", "f" ] (ordering irrelevant)
Does anything like that exist? And if not, is there a way to implement something like this with normal Java code that doesn't give the CPU or the memory nightmares for weeks?
Edit: more rigorously
MemberAdressableSetsSet mass = new MemberAdressableSetsSet();
Set<String> s1 = new HashSet<String>();
s1.add("a");
Set<String> s2 = new HashSet<String>();
s2.add("c");s2.add("d");s2.add("e");
mass.addSet(s1);
mass.addSet(s2);
Set<String> s3 = new HashSet<String>();
s3.add("a");s3.add("z");
mass.addSet(s3);
/* s3 contains "a", which is already in a subset of mass, so:
* Either
* - does nothing and returns false or throws Exception
* - deletes "a" from its previous subset before adding s3
* => possibly returns the old subset
* => deletes the old subset if that leaves it empty
* => maybe requires an optional parameter to be set
* - removes "a" from the new subset before adding it
* => possibly returns the new subset that was actually added
* => does not add the new subset if purging it of overlap leaves it empty
* => maybe requires an optional parameter to be set
* - merges all sets that would end up overlapping
* - adds it with no overlap checks, but get("a") returns an array of all sets containing it
*/
mass.get("d").add("f");
String output = Arrays.toString(mass.get("e").toArray());
System.out.println(output); // [ "c", "d", "e", "f" ] (ordering irrelevant)
mass.get("d") would return the Set<T> in mass that contains "d". Analogous to how get() works in, say, HashMap:
HashMap<String,LinkedList<Integer>> map = new HashMap<>();
LinkedList<Integer> list = new LinkedList<>();
list.add(9);
map.put("d",list);
map.get("d").add(4);
map.get("d"); // returns a LinkedList with contents [9,4]
The best I could come up with so far looks like this:
import java.util.HashMap;
import java.util.Set;
public class MemberAdressableSetsSet {
private int next_id = 1;
private HashMap<Object,Integer> members = new HashMap();
private HashMap<Integer,Set> sets = new HashMap();
public boolean addSet(Set s) {
if (s.size()==0) return false;
for (Object member : s) {
if (members.get(member)!=null) return false;
}
sets.put(next_id,s);
for (Object member : s) {
members.put(member,next_id);
}
next_id++;
return true;
}
public boolean deleteSet(Object member) {
Integer id = members.get(member);
if (id==null) return false;
Set set = sets.get(id);
for (Object m : set) {
members.remove(m);
}
sets.remove(id);
return true;
}
public boolean addToSet(Object member, Object addition) {
Integer id = members.get(member);
if (id==null) throw new IndexOutOfBoundsException();
if (members.get(addition)!=null) return false;
sets.get(id).add(addition);
members.put(addition,id);
return true;
}
public boolean removeFromSet(Object member) {
Integer id = members.get(member);
if (id==null) return false;
Set s = sets.get(id);
if (s.size()==1) sets.remove(id);
else s.remove(member);
members.remove(member);
return true;
}
public Set getSetClone(Object member) {
Integer id = members.get(member);
if (id==null) throw new IndexOutOfBoundsException();
Set copy = new java.util.HashSet(sets.get(id));
return copy;
}
}
Which has some drawbacks:
Sets are not directly accessible, which makes all Set methods and properties not exposed by explicitly defined translation methods inaccessible, unless the clones are an acceptable option
Type information is lost.
Say a Set<Date> is added.
It would not complain about trying to add, for example, a File object to that set.
At least the lost type information for the Sets doesn't extend to their members: the Set.contains() still works exactly as expected, despite both sides having been typecast to Object before being compared by contains(). So a set containing (Object)3 won't return true when asked whether it contains (Object)3L and vice versa, for example.
A set containing (Object)(new java.util.Date(10L)) will return true when asked whether it contains (Object)(new java.sql.Date(10L)) (and the other way round), but that's true even without the (Object) in front, so I guess that's "works as intended" ¯\_(ツ)_/¯
How often do you need to access by one element? Might be worth using a map and storing the same Set reference under multiple keys.
I would prevent external mutation to the map and sub sets, and provide helper method to do all of the updates:
public class MemberAdressableSets<T> {
Map<T, Set<T>> data = new HashMap<>();
public void addSet(Set<T> dataSet) {
if (dataSet.stream().anyMatch(data::containsKey)) {
throw Exception("Key already in member addressable data");
}
Set<T> protectedSet = new HashSet<>(dataSet);
dataSet.forEach(d -> data.put(d, protectedSet));
}
public void updateSet(T key, T... newData) {
Set<T> dataSet = data.get(key);
Arrays.stream(newData).forEach(dataSet::add);
Arrays.stream(newData).forEach(d -> data.put(d, dataSet));
}
public Set<T> get(T key) {
return Collections.unmodifiableSet(data.get(key));
}
}
Alternatively you could update the addSet and updateSet to create new Set instances if the key doesn't exist and make updateSet never throw. You'll also need to extend this class to handle the cases of merging sets. i.e. handle the use-case:
mass.addSet(["a","b"]);
mass.addSet(["a","c"]);
This solution allows for things like mass.get("d").add("f"); to affect the subset stored in mass, but with major drawbacks.
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
public class MemberAdressableSetsSetDirect {
private LinkedHashSet<Set> sets;
public void addSet(Set newSet) {
sets.add(newSet);
}
public Set removeSet(Object member) {
Iterator<Set> it = sets.iterator();
while (it.hasNext()) {
Set s = it.next();
if (s.contains(member)) {
it.remove();
return s;
}
}
return null;
}
public int removeSets(Object member) {
int removed = 0;
Iterator<Set> it = sets.iterator();
while (it.hasNext()) {
Set s = it.next();
if (s.contains(member)) {
it.remove();
removed++;
}
}
return removed;
}
public void deleteEmptySets() {
sets.removeIf(Set::isEmpty);
}
public Set get(Object member) {
for (Set s : sets) {
if (s.contains(member)) return s;
}
return null;
}
public Set[] getAll(Object member) {
LinkedHashSet<Set> results = new LinkedHashSet<>();
for (Set s : sets) {
if (s.contains(member)) results.add(s);
}
return (Set[]) results.toArray();
}
}
There's no built-in protection against overlap and thus we have unreliable access, as well as introducing the possibility of countless empty sets that need to be periodically purged with a manual call to deleteEmptySets(), as this solution can't detect if a subset was modified by direct access.
MemberAdressableSetsSetDirect massd = new MemberAdressableSetsSetDirect();
Set s1 = new HashSet();Set s2 = new HashSet();Set s3 = new HashSet();
s1.add("a");s1.add("b");
s2.add("c");s2.add("d");
s3.add("e");
massd.addSet(s1);massd.addSet(s2);
massd.get("c").add("a");
// massd.get("a") will now either return the Set ["a","b"] or the Set ["a","c","d"]
// (could be that my usage of a LinkedHashSet as the basis of massd
// at least makes it consistently return the set added first)
massd.get("e").remove("e");
// the third set is now empty, can't be accessed anymore,
// and massd has no clue about that until it's told to look for empty sets
massd.get("c").remove("d");
massd.get("c").remove("c");
// if LinkedHashSet makes this solution act as I suspected above,
// this makes the third subset inaccessible except via massd.getAll("a")[1]
Additionaly, this solution also can't preserve type information.
This will not even give warnings:
MemberAdressableSetsSetDirect massd = new MemberAdressableSetsSetDirect();
Set<Long> s = new HashSet<Long>();
s.add(3L);
massd.addSet(s);
massd.get(3L).add("someString");
// massd.get(3L) will now return a Set with contents [3L, "someString"]

Java - Filter a list based on multiple values

I am trying to filter out a list based on values. I have two List. One is a list of names which i want to remove i.e present in animalList. And another is the main primary list AnimalPrimaryDataPojoFilterList from where i have to remove the object which matches the names from animalList. Now i do have the solution but i think it takes lot of time. Below is the code. I am using Java 8. Can it be optimised?
if(animalList!=null && animalList.size()>0)
{
for(AnimalFilterPojo dtoObject:animalList)
{
if(!dtoObject.getApproved())
{
for(AnimalPrimaryDataPojo mainDtoObject: AnimalPrimaryDataPojoFilterList)
{
if(mainDtoObject.getAnimalName().equalsIgnoreCase(dtoObject.getValue()))
{
AnimalPrimaryDataPojoFilterList.remove(mainDtoObject);
}
}
}
}
Use removeAll() method.
AnimalPrimaryDataPojoFilterList.removeAll(animalList);
It will remove the objects of animalList from AnimalPrimaryDataPojoFilterList
N.B: You need to implement hashCode() and equals() method in AnimalFilterPojo
You can use Java 8 streams to filter the list. In below example Parent is the object which has abc property of type String. We are filtering List<Parent> objs using List<String> names
public class ListFilterDemo {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
List<Parent> objs = new ArrayList<>();
List<Parent> filtersObjs = objs.parallelStream().filter((obj) -> names.contains(obj.getAbc())).collect(Collectors.toList());
}
}
class Parent {
private String abc;
public Parent(String abc) {
this.abc = abc;
}
public String getAbc() {
return this.abc;
}
}
You can try this:
if(animalList!=null && animalList.size()>0)
animalList.removeIf(animal ->
AnimalPrimaryDataPojoFilterList.stream()
.filter(filter -> !filter.getApproved())
.map(AnimalFilter::getValue)
.collect(Collectors.toList()).contains(animal.getAnimalName()));
to explain the code: here we use removeIf() on the List to remove the objects using a Predicate that is a lambda that receives the animal and filters the list by removing the elements by name where name is taken from a list generated as a selection of the AnimalPrimaryDataPojoFilterList of the elments that have the approved flag (the second filter), extracting the value (using the map) and constructing a list out of it using a Collector.
The portion:
AnimalPrimaryDataPojoFilterList.stream()
.filter(filter -> !filter.getApproved())
.map(AnimalFilter::getValue)
.collect(Collectors.toList())
generates the list to be used as a filter
animalList.removeIf(animal ->
<generated list>.contains(animal.getAnimalName()));
uses list generated in place to apply the filter.
Beware that this of course modifies the list you have
Besides, you should not start a variable with a capital letter like you did for AnimalPrimaryDataPojoFilterList.
you can use removeIf then use AnimalPrimaryDataPojoFilterList as the source in which case you'll need to invert your logic within the if block i.e:
if(animalList != null && animalList.size() > 0){
AnimalPrimaryDataPojoFilterList.removeIf(x ->
animalList.parallelStream()
.filter(e -> !e.getApproved() && x.getAnimalName().equalsIgnoreCase(e.getValue())).findAny().orElse(null) != null);
}

Lambdas, multiple forEach with casting

Need some help thinking in lambdas from my fellow StackOverflow luminaries.
Standard case of picking through a list of a list of a list to collect some children deep in a graph. What awesome ways could Lambdas help with this boilerplate?
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<ContextInfo>();
final StandardServer server = getServer();
for (final Service service : server.findServices()) {
if (service.getContainer() instanceof Engine) {
final Engine engine = (Engine) service.getContainer();
for (final Container possibleHost : engine.findChildren()) {
if (possibleHost instanceof Host) {
final Host host = (Host) possibleHost;
for (final Container possibleContext : host.findChildren()) {
if (possibleContext instanceof Context) {
final Context context = (Context) possibleContext;
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
list.add(info);
}
}
}
}
}
}
return list;
}
Note the list itself is going to the client as JSON, so don't focus on what is returned. Must be a few neat ways I can cut down the loops.
Interested to see what my fellow experts create. Multiple approaches encouraged.
EDIT
The findServices and the two findChildren methods return arrays
EDIT - BONUS CHALLENGE
The "not important part" did turn out to be important. I actually need to copy a value only available in the host instance. This seems to ruin all the beautiful examples. How would one carry state forward?
final ContextInfo info = new ContextInfo(context.getPath());
info.setHostname(host.getName()); // The Bonus Challenge
It's fairly deeply nested but it doesn't seem exceptionally difficult.
The first observation is that if a for-loop translates into a stream, nested for-loops can be "flattened" into a single stream using flatMap. This operation takes a single element and returns an arbitrary number elements in a stream. I looked up and found that StandardServer.findServices() returns an array of Service so we turn this into a stream using Arrays.stream(). (I make similar assumptions for Engine.findChildren() and Host.findChildren().
Next, the logic within each loop does an instanceof check and a cast. This can be modeled using streams as a filter operation to do the instanceof followed by a map operation that simply casts and returns the same reference. This is actually a no-op but it lets the static typing system convert a Stream<Container> to a Stream<Host> for example.
Applying these transformations to the nested loops, we get the following:
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<ContextInfo>();
final StandardServer server = getServer();
Arrays.stream(server.findServices())
.filter(service -> service.getContainer() instanceof Engine)
.map(service -> (Engine)service.getContainer())
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(possibleHost -> possibleHost instanceof Host)
.map(possibleHost -> (Host)possibleHost)
.flatMap(host -> Arrays.stream(host.findChildren()))
.filter(possibleContext -> possibleContext instanceof Context)
.map(possibleContext -> (Context)possibleContext)
.forEach(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
list.add(info);
});
return list;
}
But wait, there's more.
The final forEach operation is a slightly more complicated map operation that converts a Context into a ContextInfo. Furthermore, these are just collected into a List so we can use collectors to do this instead of creating and empty list up front and then populating it. Applying these refactorings results in the following:
public List<ContextInfo> list() {
final StandardServer server = getServer();
return Arrays.stream(server.findServices())
.filter(service -> service.getContainer() instanceof Engine)
.map(service -> (Engine)service.getContainer())
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(possibleHost -> possibleHost instanceof Host)
.map(possibleHost -> (Host)possibleHost)
.flatMap(host -> Arrays.stream(host.findChildren()))
.filter(possibleContext -> possibleContext instanceof Context)
.map(possibleContext -> (Context)possibleContext)
.map(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
})
.collect(Collectors.toList());
}
I usually try to avoid multi-line lambdas (such as in the final map operation) so I'd refactor it into a little helper method that takes a Context and returns a ContextInfo. This doesn't shorten the code at all, but I think it does make it clearer.
UPDATE
But wait, there's still more.
Let's extract the call to service.getContainer() into its own pipeline element:
return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.filter(container -> container instanceof Engine)
.map(container -> (Engine)container)
.flatMap(engine -> Arrays.stream(engine.findChildren()))
// ...
This exposes the repetition of filtering on instanceof followed by a mapping with a cast. This is done three times in total. It seems likely that other code is going to need to do similar things, so it would be nice to extract this bit of logic into a helper method. The problem is that filter can change the number of elements in the stream (dropping ones that don't match) but it can't change their types. And map can change the types of elements, but it can't change their number. Can something change both the number and types? Yes, it's our old friend flatMap again! So our helper method needs to take an element and return a stream of elements of a different type. That return stream will contain a single casted element (if it matches) or it will be empty (if it doesn't match). The helper function would look like this:
<T,U> Stream<U> toType(T t, Class<U> clazz) {
if (clazz.isInstance(t)) {
return Stream.of(clazz.cast(t));
} else {
return Stream.empty();
}
}
(This is loosely based on C#'s OfType construct mentioned in some of the comments.)
While we're at it, let's extract a method to create a ContextInfo:
ContextInfo makeContextInfo(Context context) {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
}
After these extractions, the pipeline looks like this:
return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.flatMap(container -> toType(container, Engine.class))
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.flatMap(possibleHost -> toType(possibleHost, Host.class))
.flatMap(host -> Arrays.stream(host.findChildren()))
.flatMap(possibleContext -> toType(possibleContext, Context.class))
.map(this::makeContextInfo)
.collect(Collectors.toList());
Nicer, I think, and we've removed the dreaded multi-line statement lambda.
UPDATE: BONUS CHALLENGE
Once again, flatMap is your friend. Take the tail of the stream and migrate it into the last flatMap before the tail. That way the host variable is still in scope, and you can pass it to a makeContextInfo helper method that's been modified to take host as well.
return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.flatMap(container -> toType(container, Engine.class))
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.flatMap(possibleHost -> toType(possibleHost, Host.class))
.flatMap(host -> Arrays.stream(host.findChildren())
.flatMap(possibleContext -> toType(possibleContext, Context.class))
.map(ctx -> makeContextInfo(ctx, host)))
.collect(Collectors.toList());
This would be my version of your code using JDK 8 streams, method references, and lambda expressions:
server.findServices()
.stream()
.map(Service::getContainer)
.filter(Engine.class::isInstance)
.map(Engine.class::cast)
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(Host.class::isInstance)
.map(Host.class::cast)
.flatMap(host -> Arrays.stream(host.findChildren()))
.filter(Context.class::isInstance)
.map(Context.class::cast)
.map(context -> {
ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
})
.collect(Collectors.toList());
In this approach, I replace your if-statements for filter predicates. Take into account that an instanceof check can be replaced with a Predicate<T>
Predicate<Object> isEngine = someObject -> someObject instanceof Engine;
which can also be expressed as
Predicate<Object> isEngine = Engine.class::isInstance
Similarly, your casts can be replaced by Function<T,R>.
Function<Object,Engine> castToEngine = someObject -> (Engine) someObject;
Which is pretty much the same as
Function<Object,Engine> castToEngine = Engine.class::cast;
And adding items manually to a list in the for loop can be replaced with a collector. In production code, the lambda that transforms a Context into a ContextInfo can (and should) be extracted into a separate method, and used as a method reference.
Solution to bonus challenge
Inspired by #EdwinDalorzo answer.
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<>();
final StandardServer server = getServer();
return server.findServices()
.stream()
.map(Service::getContainer)
.filter(Engine.class::isInstance)
.map(Engine.class::cast)
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(Host.class::isInstance)
.map(Host.class::cast)
.flatMap(host -> mapContainers(
Arrays.stream(host.findChildren()), host.getName())
)
.collect(Collectors.toList());
}
private static Stream<ContextInfo> mapContainers(Stream<Container> containers,
String hostname) {
return containers
.filter(Context.class::isInstance)
.map(Context.class::cast)
.map(context -> {
ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
info.setHostname(hostname); // The Bonus Challenge
return info;
});
}
First attempt beyond ugly. It will be years before I find this readable. Has to be a better way.
Note the findChildren methods return arrays which of course work with for (N n: array) syntax, but not with the new Iterable.forEach method. Had to wrap them with Arrays.asList
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<ContextInfo>();
final StandardServer server = getServer();
asList(server.findServices()).forEach(service -> {
if (!(service.getContainer() instanceof Engine)) return;
final Engine engine = (Engine) service.getContainer();
instanceOf(Host.class, asList(engine.findChildren())).forEach(host -> {
instanceOf(Context.class, asList(host.findChildren())).forEach(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
list.add(info);
});
});
});
return list;
}
The utility methods
public static <T> Iterable<T> instanceOf(final Class<T> type, final Collection collection) {
final Iterator iterator = collection.iterator();
return () -> new SlambdaIterator<>(() -> {
while (iterator.hasNext()) {
final Object object = iterator.next();
if (object != null && type.isAssignableFrom(object.getClass())) {
return (T) object;
}
}
throw new NoSuchElementException();
});
}
And finally a Lambda-powerable implementation of Iterable
public static class SlambdaIterator<T> implements Iterator<T> {
// Ya put your Lambdas in there
public static interface Advancer<T> {
T advance() throws NoSuchElementException;
}
private final Advancer<T> advancer;
private T next;
protected SlambdaIterator(final Advancer<T> advancer) {
this.advancer = advancer;
}
#Override
public boolean hasNext() {
if (next != null) return true;
try {
next = advancer.advance();
return next != null;
} catch (final NoSuchElementException e) {
return false;
}
}
#Override
public T next() {
if (!hasNext()) throw new NoSuchElementException();
final T v = next;
next = null;
return v;
}
#Override
public void remove() {
throw new UnsupportedOperationException();
}
}
Lots of plumbing and no doubt 5x the byte code. Must be a better way.

Categories

Resources