I check my case I have a listing where a process after the data is loaded with this information:
-> ExpAdminBean [codTipoExpediente=1, desTipoExpediente=Exp Coactivo, siNumSecuencia=2, nroDocumento=null]
-> ExpAdminBean [codTipoExpediente=19, desTipoExpediente=R Sancion, siNumSecuencia=0, nroDocumento=218-056-02742669]
-> ExpAdminBean [codTipoExpediente=2, desTipoExpediente=Rec, siNumSecuencia=0, nroDocumento=220-041-01169690]
-> ExpAdminBean [codTipoExpediente=19, desTipoExpediente=R Sancion, siNumSecuencia=0, nroDocumento=218-056-03048986]
-> ExpAdminBean [codTipoExpediente=2, desTipoExpediente=Rec, siNumSecuencia=0, nroDocumento=220-041-01169690]
-> ExpAdminBean [codTipoExpediente=23, desTipoExpediente=CIR, siNumSecuencia=0, nroDocumento=218-174-00146216]
-> ExpAdminBean [codTipoExpediente=2, desTipoExpediente=Rec, siNumSecuencia=0, nroDocumento=220-041-01169690]
What I have to do to eliminate duplicate records but the nroDocumento attribute, the attribute nroDocumento not be repeated, if any should be removed repeated, I wanted to use a Hash Set, but also is present as null, which complicates little.
My code Java:
Map<String, Set<ExpAdminBean>> map = new ConcurrentHashMap<String, Set<ExpAdminBean>>();
Set<ExpAdminBean> setExpAdmin = null;
for(ExpAdminBean exp : listaExpAdminAux) {
setExpAdmin = new HashSet<ExpAdminBean>();
map.put(exp.getNroDocumento(), setExpAdmin);
}
List<String> listExp = new ArrayList<String>();
for(String keySist : map.keySet()){
listExp.add(keySist);
}
A collection without duplicates (how to determine duplicates being up to you) is a Set.
A collection that maintain an arbitrary order is a List (in ArrayList the default order is the order of insertion). Note that list MAINTAIN an order ... but does not necessarily sort its elements.
Anyway if you want to keep the insertion order AND keep out duplicates you can use a LinkedHashSet<>.
In your case you need to reimplement the equals(~) and hashCode() functions of your ExpAdminBean class if possible. I suggest you implement it so that equals returns true when nroDocumento is equal and return the hashCode of nroDocumento dirrectly in the new hashCode implementation.
Otherwize if you cannot do that I suggest you use a map:
Map<String, ExpAdminBean> map = ~~~~~~ ;
for(ExpAdminBean exp : listaExpAdminAux) {
if(!map.containsKey(exp.getNroDocumento())
map.put(exp.getNroDocumento(), exp);
}
//if you need to put the values in a list you can do ...
ArrayList<ExpAdminBean> theList = new ArrayList<>(map.size());
theList.addAll(map.values());
Like I said before if you want to keep the "insertion order" of your first list you should use a LinkedHashMap<> or a LinkedHashSet<>.
Related
I'm new to java stream API.
I have 2 lists, and if both their internal object ID matches wants to put some attributes to MAP.
Below is the implementation.
List<LookupMstEntity> examTypeDetails; //This list contains values init.
List<MarksMstEntity> marksDetailList; //This list contains values init.
//FYI above entities have lombok setter, getter, equals & hashcode.
Map<Long, Integer> marksDetailMap = new HashMap<>();
//need below implementation to changed using java 8.
for (LookupMstEntity examType : examTypeDetails) {
for (MarksMstEntity marks : marksDetailList) {
if (examType.getLookupId() == marks.getExamTypeId())
marksDetailMap.put(examType.getLookupId(), marks.getMarks());
}
}
Creating a set of lookupIds Set<Long> ids helps you to throw away duplicate values and to get rid of unnecessary checks.
Then you can filter marksDetailList accordingly with examTypeId values:
filter(m -> ids.contains(m.getExamTypeId()))
HashSet contains() method has constant time complexity O(1).
Try this:
Set<Long> ids = examTypeDetails.stream().map(LookupMstEntity::getLookupId)
.collect(Collectors.toCollection(HashSet::new));
Map<Long, Integer> marksDetailMap = marksDetailList.stream().filter(m -> ids.contains(m.getExamTypeId()))
.collect(Collectors.toMap(MarksMstEntity::getExamTypeId, MarksMstEntity::getMarks));
As long as you are looking for these with equal ID, it doesn't matter which ID you use then. I suggest you to start streaming the marksDetailList first since you need its getMarks(). The filtering method searches if there is a match in IDs. If so, collect the required key-values to the map.
Map<Long, Integer> marksDetailMap = marksDetailList.stream() // List<MarksMstEntity>
.filter(mark -> examTypeDetails.stream() // filtered those where ...
.map(LookupMstEntity::getLookupId) // ... the lookupId
.anyMatch(id -> id == mark.getExamTypeId())) // ... is present in the list
.collect(Collectors.toMap( // collected to Map ...
MarksMstEntity::getExamTypeId, // ... with ID as a key
MarksMstEntity::getMarks)); // ... and marks as a value
The .map(..).anyMatch(..) can be shrink into one:
.anyMatch(exam -> exam.getLookupId() == mark.getExamTypeId())
As stated in the comments, I'd rather go for the for-each iteration as you have already used for sake of brevity.
An observation:
First, your resultant map indicates that there can only be one match for ID types (otherwise you would have duplicate keys and the value would need to be a List or some other way of merging duplicate keys, not an Integer. So when you find the first one and insert it in the map, break out of the inner loop.
for (LookupMstEntity examType : examTypeDetails) {
for (MarksMstEntity marks : marksDetailList) {
if (examType.getLookupId() == marks.getExamTypeId()) {
marksDetailMap.put(examType.getLookupId(),
marks.getMarks());
// no need to keep on searching for this ID
break;
}
}
}
Also if your two classes were related by a parent class or a shared interface that had access to to the id, and the two classes were considered equal based on that id, then you could do something similar to this.
for (LookupMstEntity examType : examTypeDetails) {
int index = marksDetailList.indexOf(examType);
if (index > 0) {
marksDetailMap.put(examType.getLookupId(),
marksDetaiList.get(index).getMarks());
}
}
Of course the burden of locating the index is still there but it is now under the hood and you are relieved of that responsibility.
You can do it with O(N) time complexity using HashMap, first convert two lists into Map<Integer, LookupMstEntity> and Map<Integer, MarksMstEntity> with id as key
Map<Integer, LookupMstEntity> examTypes = examTypeDetails.stream()
.collect(Collectors.toMap(LookupMstEntity::getLookupId,
Function.identity()) //make sure you don't have any duplicate LookupMstEntity objects with same id
Map<Integer, MarksMstEntity> marks = marksDetailList.stream()
.collect(Collectors.toMap(MarksMstEntity::getExamTypeId,
Function.identity()) // make sure there are no duplicates
And then stream the examTypes map and then collect into map if MarksMstEntity exists with same id in marks map
Map<Integer, Integer> result = examTypes.entrySet()
.stream()
.map(entry->new AbstractMap.SimpleEntry<Integer, MarksMstEntity>(entry.getKey(), marks.get(entry.getKey())))
.filter(entry->entry.getValue()!=null)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
I'm not able to convert below snippet in Java 8 stream format.
List<String> titles = Arrays.asList("First Name", "Last Name");
for (FirstClass first : firstClassList) {
for (SecondClass second : first.getSecondClassList()) {
for (ThirdClass third : second.getThirdClassList()) {
if(!titles.contains(third.getField())) {
second.getThirdClassList().remove(third);
}
}
}
}
I'm comparing third level nested list object against the input list of fields. If fields are not matching then I'm removing them from original list.
How can I achieve this using Java 8 syntax.
Edit: I want List of FirstClass to be returned.
I don't think streams win you anything in this case. All you do is iterate over the nested lists and either the enhanced for loop or forEach is more straightforward.
The improvements can come from using removeIf to modify the list and, possibly, from moving the rejection logic out of the loop:
Predicate<ThirdClass> reject = third -> !titles.contains(third.getField());
firstClassList.forEeach(first ->
first.getSecondClassList().forEach(second ->
second.getThirdClassList().removeIf(reject)
)
);
Get a stream of SecondClass objects by flattening the firstClassList and for each SecondClass get the filtered list of ThirdClass objects and set it back in the SecondClass
List<String> titles = Arrays.asList("First Name", "Last Name");
firstClassList
.stream()
.flatMap(firstClass -> firstClass.getSecondClassList().stream())
.forEach(secondClass -> {
List<ThirdClass> filteredThirdClasses = secondClass.getThirdClassList()
.stream()
.filter(thirdClass -> titles.contains(thirdClass.getField()))
.collect(toList());
secondClass.setThirdClassList(filteredThirdClasses);
}
);
First you can use Stream.map() and Stream.flatMap() to get a Stream containing a List of ThirdClass. To remove the items matching the condition you could use Collection.removeIf(), which removes all items from a collection matching the given condition:
firstClassList.stream() // Stream<FirstClass>
.map(FirstClass::getSecondClassList) // Stream<List<SecondClass>>
.flatMap(Collection::stream) // Stream<SecondClass>
.map(SecondClass::getThirdClassList) // Stream<List<ThirdClass>>
.forEach(thirdList -> thirdList.removeIf(third -> !titles.contains(third.getField())));
This modifies the original List, just like you did in your example. You then can use firstClassList as result for further processing.
Beside that I would recommend using a Set<String> instead of a List<String> for your titles, because it has a time complexity of O(1) instead of O(n):
Set<String> titles = new HashSet<>(Arrays.asList("First Name", "Last Name"));
Assuming the three levels in your data-structure are collections then a solution could be:
flatten the structure to a stream of the leaf level
filter by the required titles
final List<ThirdClassList> results = firstClassList
.stream()
.flatMap(FirstClassList::getSecondClassList)
.flatMap(FirstClassList::getThirdClassList)
.filter(third -> !titles.contains(third))
.collect(toList());
This will give you the leaf level objects to be removed, though this is only half the solution of course, you still want to remove them.
If you are the author of the list classes then perhaps you could have a reference from each third level to its 'parent' second level so removing is then a relatively simple second step:
results.forEach(third -> third.parent().remove(this));
where third.parent() returns the second level object.
I just started messing around with Java streams and I wrote something like this:
List<Device> devicesToDelete = new ArrayList<>();
List<Device> oldDeviceList = getCurrentDevices();
for (Device deviceFromOldList : oldDeviceList)
{
// part to simplify
boolean deviceNotExistOnDeleteList =
devicesToDelete.stream().noneMatch(nd -> nd.id == deviceFromOldList.id);
if (deviceNotExistOnDeleteList) {
devicesToDelete.add(deviceFromOldList);
}
// part to simplify end
}
Can it be simplified even more?
I'm not using Set because my Device class .equals() implementation compares all fields in that class. And here I need to compare only id field.
Just use a Map
Map<Object, Device> devicesToDelete = new HashMap<>();
List<Device> oldDeviceList = getCurrentDevices();
for(Device deviceFromOldList: oldDeviceList) {
devicesToDelete.putIfAbsent(deviceFromOldList.id, deviceFromOldList);
}
// in case you need a Collection:
Collection<Device> allDevicesToDelete = devicesToDelete.values();
putIfAbsent will only store the mapping if the key is not already present. This will get you the performance of hashing while only considering the ID.
You may change the type argument Object in Map<Object,Device> to whatever type your ID has, though it doesn’t matter for the operation, if all you need at the end, is the Collection<Device>.
You can use a Stream, e.g.
Map<Object, Device> devicesToDelete = getCurrentDevices().stream()
.collect(Collectors.toMap(
deviceFromOldList -> deviceFromOldList.id, Function.identity(), (a,b) -> a));
though, it’s debatable whether this is a necessary change. The loop is not bad.
I have a list of 5-digit combinations (possibly with repetitions: doubles, triples, etc). I need to count how often every combination appears in that list. Actually, a combination is a unique BitSet with respective bits set (if combination contains digit 5 then 5th bit is set, etc.)
Given list
12345
34578
12345
98710
12345
I shall get
12345 -> 3
34578 -> 1
98710 -> 1
Is there anything ready to solve this task? Like I add 12345 string to this data structure three times and then I query it for 12345 (respective Bitset object) and it returns 3 as number of occurrencies. I thought of Apache Commons Frequency class, but it does not help.
If you are looking for a ready-to-use data structure which stores elements with counts, then Guava's Multiset does exactly that.
If you just need to convert a list to a map of counts, please read on.
You can convert a list to a map of counts in a single statement using Java 8 Streams API:
final var list = List.of("12345", "34578", "12345", "98710", "12345");
final var counts = list.stream()
.collect(Collectors.toMap(
Function.identity(), // Map keys are list elements
value -> 1, // Map values are counts, a single item counts "1"
(count1, count2) -> count1 + count2 // On duplicate keys, counts are added
));
Under the hood, this solution uses a hash map (elements to counts) as a data structure.
You may also use the groupingBy collector, as Peter Lawrey kindly suggested:
final var list = List.of("12345", "34578", "12345", "98710", "12345");
final var counts = list.stream()
.collect(Collectors.groupingBy(
Function.identity(), // Group the elements by equality relation
Collectors.counting() // Map values are counts of elements in the equality groups
));
Sometimes (while learning) it's beneficial to implement everything "by hand" to understand the algorithms. So here a version without Java 8 goodies like streams, collectors and new map methods like Map.compute():
final List<Stream> list = List.of("12345", "34578", "12345", "98710", "12345"); // Use ArrayList if you're below Java 9
final Map<String, Integer> counts = new HashMap<>();
for (final String item : list) {
// Note: I'm deliberately NOT using Map.compute() here
// to demonstrate how to do everything "manually"
Integer count = counts.get(item);
if (count == null) {
count = 0;
}
counts.put(item, count + 1);
}
Assuming your list are strings (if not you might need a "comparator"). Loop over the entire list adding the elements to a HashMap and a counter of themselves; but right before doing that, check if the element in question exists, updating the counter accordingly.
Eventually, Java streams can help too.
You can use the plain Collections.frequency method that does exactly that.
import java.util.List;
import static java.util.Collections.frequency;
List list = List.of("12345", "34578", "12345", "98710", "12345");
System.out.println( frequency(list, "12345") );
I have a HashSet of Strings in the format: something_something_name="value"
Set<String> name= new HashSet<String>();
Farther down in my code I want to check if a String "name" is included in the HashSet. In this little example, if I'm checking to see if "name" is a substring of any of the values in the HashSet, I'd like it to return true.
I know that .contains() won't work since that works using .equals(). Any suggestions on the best way to handle this would be great.
With your existing data structure, the only way is to iterate over all entries checking each one in turn.
If that's not good enough, you'll need a different data structure.
You can build a map (name -> strings) as follows:
Map<String, List<String>> name_2_keys = new HashMap<>();
for (String name : names) {
String[] parts = key.split("_");
List<String> keys = name_2_keys.get(parts[2]);
if (keys == null) {
keys = new ArrayList<>();
}
keys.add(name);
name_2_keys.put(parts[2], keys);
}
Then retrieve all the strings containing the name name:
List<String> keys = name_2_keys.get(name)
You can keep another map where name is the key and something_something_name is the value.
Thus, you would be able to move from name -> something_something_name -> value. If you want a single interface, you can write a wrapper class around these two maps, exposing the functionality you want.
I posted a MapFilter class here a while ago.
You could use it like:
MapFilter<String> something = new MapFilter<String>(yourMap, "something_");
MapFilter<String> something_something = new MapFilter<String>(something, "something_");
You will need to make your container into a Map first.
This would only be worthwhile doing if you look for the substrings many times.