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));
Related
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'm checking to see if a key in my HashMap exists, if it does, I also want to check to see if any other keys have a value with the same name as that of the original key I checked for or not.
For example I have this.
System.out.println("What course do you want to search?");
String searchcourse = input.nextLine();
boolean coursefound = false;
if(hashmap.containsKey(searchcourse) == true){
coursefound = true;
}
This checks to see if the key exists in my hashmap, but now I need to check every single key's values for a specific value, in this case the string searchcourse.
Usually I would use a basic for loop to iterate through something like this, but it doesn't work with HashMaps. My values are also stored in a String ArrayList, if that helps.
You will want to look at each entry in the HashMap. This loop should check the contents of the ArrayList for your searchcourse and print out the key that contained the value.
for (Map.Entry<String,ArrayList> entries : hashmap.entrySet()) {
if (entries.getValue().contains(searchcourse)) {
System.out.println(entries.getKey() + " contains " + searchcourse);
}
}
Here are the relevant javadocs:
Map.Entry
HashMap entrySet method
ArrayList contains method
You can have a bi-directional map. E.g. you can have a Map<Value, Set<Key>> or MultiMap for the values to keys or you can use a bi-directional map which is planned to be added to Guava.
As I understand your question, the values in your Map are List<String>. That is, your Map is declares as Map<String, List<String>>. If so:
for (List<String> listOfStrings : myMap.values()) [
if (listOfStrings .contains(searchcourse) {
// do something
}
}
If the values are just Strings, i.e. the Map is a Map<String, String>, then #Matt has the simple answer.
I currently have a map which stores the following information:
Map<String,String> animals= new HashMap<String,String>();
animals.put("cat","50");
animals.put("bat","38");
animals.put("dog","19");
animals.put("cat","31");
animals.put("cat","34");
animals.put("bat","1");
animals.put("dog","34");
animals.put("cat","55");
I want to create a new map with total for unique items in the above map. So in the above sample, count for cat would be 170, count for bat would be 39 and so on.
I have tried using Set to find unique animal entries in the map, however, I am unable to get the total count for each unique entry
First, don't use String for arithmetic, use int or double (or BigInteger/BigDecimal, but that's probably overkill here). I'd suggest making your map a Map<String, Integer>.
Second, Map.put() will overwrite the previous value if the given key is already present in the map, so as #Guy points out your map actually only contains {cat:55, dog:34, bat:1}. You need to get the previous value somehow in order to preserve it.
The classic way (pre-Java-8) is like so:
public static void putOrUpdate(Map<String, Integer> map, String key, int value) {
Integer previous = map.get(key);
if (previous != null) {
map.put(key, previous + value);
} else {
map.put(key, value);
}
}
Java 8 adds a number of useful methods to Map to make this pattern easier, like Map.merge() which does the put-or-update for you:
map.merge(key, value, (p, v) -> p + v);
You may also find that a multiset is a better data structure to use as it handles incrementing/decrementing for you; Guava provides a nice implementation.
As Guy said. Now you have one bat, one dog and one cat. Another 'put's will override your past values. For definition. Map stores key-value pairs where each key in map is unique. If you have to do it by map you can sum it just in time. For example, if you want to add another value for cat and you want to update it you can do it in this way:
animals.put("cat", animals.get("cat") + yourNewValue);
Your value for cat will be updated. This is for example where our numbers are float/int/long, not string as you have. If you have to do it by strings you can use in this case:
animals.put("cat", Integer.toString(Integer.parseInt(animals.get("cat")) + yourNewValue));
However, it's ugly. I'd recommend create
Map<String, Integer> animals = new HashMap<String, Integer>();
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<>.
I have the following list:
List<ArrayList> list;
list.get(i) contains the ArrayList object with the following values {p_name=set1, number=777002}.
I have to create a
Map<key,value>
where the key contains the p_name, and values are the numbers.
How to do it easily and fast as there can be hundreds of entries in the initial list and each number can be present in multiple p_name entries.
Update: Here is my current solution
List<Row> list; //here is my data
Map<String,String> map = new TreeMap<String,String>();
for (Row l : list) {
if (l.hasValues()) {
Map<String, String> values = l.getResult(); // internal method of Row interface that returns a map
String key = values.get( "number");
map.put(key, values.get( "p_name" ));
}
}
The method works, but maybe it could be done better?
PS : There is an obvious error in my design. I wonder if you find it :)
Sine the key can have more then one values, what you are looking for is a MultiMap. Multimap
Or a simple map in the form
Map<Key,ArrayList<Values>>
There is no "fast" way here to me. You still need to iterate through all the elements and check all the values.
And actually hundreds to Java is not much at all