how to reduce a map to get a pair or entry - java

I have a map ,the Integer stands for the frequency and the Set stands for a Set of words ,Now I want to reduce this map to get the frequency of the set with the most words ,and the return the frequency and the number of words in that Set with a pair
public static Map<Integer, Set<String>> getCounts(In in)
My instinct tells me this is ok, this is what I think :
initial
Compare each word set separately,once the sum of words in a Set greater than the previous recorded, Update the pair
get the pair
but I get stuck as soon as I start ....
var temp = new Pair<Integer,Integer>(0, 0);
Stream.of(wordCounts)
.reduce()

Note that you might want to think about what you want to output when the map is empty.
You can use the convenient max method on streams
var pair = wordCounts.entrySet().stream()
.map(x -> new Pair<>(x.getKey(), x.getValue().size()))
.max(Comparator.comparing(Pair::getSecond));
This gives you an optional pair. The optional will be empty when the map is empty.
If you really want to use reduce, you can. Just change the max call to:
.reduce((a, b) -> a.getSecond() >= b.getSecond() ? a : b);

I hope I understood this correctly:
map.entrySet().stream().max(Comparator.comparing(x -> x.getValue().size()))
.map(x -> new Map.entry(x.getKey(), x.getValue().size()))
.orElse(new Map.entry(0, 0));

Related

Effective way. of comparing list elements in Java

Is there any **effective way **of comparing elements in Java and print out the position of the element which occurs once.
For example: if I have a list: ["Hi", "Hi", "No"], I want to print out 2 because "No" is in position 2. I have solved this using the following algorithm and it works, BUT the problem is that if I have a large list it takes too much time to compare the entire list to print out the first position of the unique word.
ArrayList<String> strings = new ArrayList<>();
for (int i = 0; i < strings.size(); i++) {
int oc = Collections.frequency(strings, strings.get(i));
if (oc == 1)
System.out.print(i);
break;
}
I can think of counting each element's occurrence no and filter out the first element though not sure how large your list is.
Using Stream:
List<String> list = Arrays.asList("Hi", "Hi", "No");
//iterating thorugh the list and storing each element and their no of occurance in Map
Map<String, Long> counts = list.stream().collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting()));
String value = counts.entrySet().stream()
.filter(e -> e.getValue() == 1) //filtering out all the elements which have more than 1 occurance
.map(Map.Entry::getKey) // creating a stream of element from map as all of these have only single occurance
.findFirst() //finding the first element from the element stream
.get();
System.out.println(list.indexOf(value));
EDIT:
A simplified version can be
Map<String, Long> counts2 = new LinkedHashMap<String, Long>();
for(String val : list){
long count = counts2.getOrDefault(val, 0L);
counts2.put(val, ++count);
}
for(String key: counts2.keySet()){
if(counts2.get(key)==1){
System.out.println(list.indexOf(key));
break;
}
}
The basic idea is to count each element's occurrence and store them in a Map.Once you have count of all elements occurrences. then you can simply check for the first element which one has 1 as count.
You can use HashMap.For example you can put word as key and index as value.Once you find the same word you can delete the key and last the map contain the result.
If there's only one word that's present only once, you can probably use a HashMap or HashSet + Deque (set for values, Deque for indices) to do this in linear time. A sort can give you the same in n log(n), so slower than linear but a lot faster than your solution. By sorting, it's easy to find in linear time (after the sort) which element is present only once because all duplicates will be next to each other in the array.
For example for a linear solution in pseudo-code (pseudo-Kotlin!):
counters = HashMap()
for (i, word in words.withIndex()) {
counters.merge(word, Counter(i, 1), (oldVal, newVal) -> Counter(oldVald.firstIndex, oldVald.count + newVal.count));
}
for (counter in counters.entrySet()) {
if (counter.count == 1) return counter.firstIndex;
}
class Counter(firstIndex, count)
Map<String,Boolean> + loops
Instead of using Map<String,Integer> as suggested in other answers.
You can maintain a HashMap (if you need to maintain the order, use LinkedHashMap instead) of type Map<String,Boolean> where a value would denote whether an element is unique or not.
The simplest way to generate the map is method put() in conjunction with containsKey() check.
But there are also more concise options like replace() + putIfAbsent(). putIfAbsent() would create a new entry only if key is not present in the map, therefore we can associate such string with a value of true (considered to be unique). On the other hand replace() would update only existing entry (otherwise map would not be effected), and if entry exist, the key is proved to be a duplicate, and it has to be associated with a value of false (non-unique).
And since Java 8 we also have method merge(), which expects tree arguments: a key, a value, and a function which is used when the given key already exists to resolve the old value and the new one.
The last step is to generate list of unique strings by iterating over the entry set of the newly created map. We need every key having a value of true (is unique) associated with it.
List<String> strings = // initializing the list
Map<String, Boolean> isUnique = new HashMap<>(); // or LinkedHashMap if you need preserve initial order of strings
for (String next: strings) {
isUnique.replace(next, false);
isUnique.putIfAbsent(next, true);
// isUnique.merge(next, true, (oldV, newV) -> false); // does the same as the commented out lines above
}
List<String> unique = new ArrayList<>();
for (Map.Entry<String, Boolean> entry: isUnique.entrySet()) {
if (entry.getValue()) unique.add(entry.getKey());
}
Stream-based solution
With streams, it can be done using collector toMap(). The overall logic remains the same.
List<String> unique = strings.stream()
.collect(Collectors.toMap( // creating intermediate map Map<String, Boolean>
Function.identity(), // key
key -> true, // value
(oldV, newV) -> false, // resolving duplicates
LinkedHashMap::new // Map implementation, if order is not important - discard this argument
))
.entrySet().stream()
.filter(Map.Entry::getValue)
.map(Map.Entry::getKey)
.toList(); // for Java 16+ or collect(Collectors.toList()) for earlier versions

Elements in second list matched with elements in first list

I have 2 lists as the following
List<String> firstList = Arrays.asList("E","B","A","C");
List<String> secondList = Arrays.asList("Alex","Bob","Chris","Antony","Ram","Shyam");
I want the output in the form of a map having values in the second list mapped to elements in the first list based on first character.
For example I want the output as
Map<String,List<String>> outputMap;
and it has the following content
key -> B, value -> a list having single element Bob
key -> A, value -> a list having elements Alex and Antony
key -> C, value -> a list having single element Chris
I did something like this
firstList.stream()
.map(first->
secondList.stream().filter(second-> second.startsWith(first))
.collect(Collectors.toList())
);
and can see the elements of the second list group by first character. However I am unsure as to how to store the same in a map .
Please note that my question is more from the perspective of using the streaming API to get the job done.
I'm pretty sure that instead of nesting streaming of both lists you should just group the second one by first letter and filter values by testing whether the first letter is in the first list
final Map<String, List<String>> result = secondList.stream()
.filter(s -> firstList.contains(s.substring(0, 1)))
.collect(Collectors.groupingBy(s -> s.substring(0, 1)));
You can also extract s.substring(0, 1) to some
String firstLetter(String string)
method to make code a little bit more readable
Just move that filter to a toMap() collector:
Map<String, List<String>> grouped = firstList.stream()
.collect(Collectors.toMap(first -> first, first -> secondList.stream()
.filter(second -> second.startsWith(first))
.collect(Collectors.toList())));
If you want only keys that have matching names, you can
.filter(first -> secondList.stream().anyMatch(second -> second.startsWith(first)))

Split string into parts in java to put it in a map

I have been trying to get all the values from a string and put them in a map in the following manner:
So I have a string which is like this:
String cookies = "i=lol;haha=noice;df3=ddtb;"
So far I have been trying this out:
final Map<String, String> map = new HashMap<>();
map.put(cookies.split(";")[0].split("=")[0], cookies.split(";")[0].split("=")[1]);
But this way I can only put one value in and it is quite long and ugly. Is there any was to due this with regex or a loop?
You could use a loop to iterate over the key value pairs and put them into the map:
String[] cookieArr = cookies.split(";");
for(String cookieString : cookieArr){
String[] pair = cookieString.split("=");
if(pair.length < 2){
continue;
}
map.put(pair[0], pair[1]);
}
The if is only there to prevent ArrayIndexOutOfBounds expcetions if cookie string is malformed
an alternativ would be using a stream:
Arrays.stream(cookies.split(";")).forEach(cookieStr -> map.put(cookieStr.split("=")[0], cookieStr.split("=")[1]));
As mentioned by #WJS in the comment, you could use map.putIfAbsent(key, vlaue) instead of map.put(key, value) to prevent overriding of values. But in case of cookies it could be a desired behavior to overwrite the old value with the new.
You could do it like this. It presumes your format is consistent.
first splits each k/v pair on ";"
the splits on "=" into key and value.
and adds to map.
if duplicate keys show up, the first one encountered takes precedence (if you want the latest value for a duplicate key then use (a, b)-> b as the merge lambda.)
String cookies = "i=lol;haha=noice;df3=ddtb";
Map<String, String> map = Arrays.stream(cookies.split(";"))
.map(str -> str.split("=")).collect(Collectors
.toMap(a -> a[0], a->a[1], (a, b) -> a));
map.entrySet().forEach(System.out::println);
Prints
df3=ddtb
haha=noice
i=lol

Get all the values of single key from the list map java8

I have the list as follows:
List<Map<String,Object>> mapList=new ArrayList<>();
Map<String,Object> mapObject=new HashMap<String,Object>();
mapObject.put("No",1);
mapObject.put("Name","test");
mapList.add(mapObject);
Map<String,Object> mapObject1=new HashMap<String,Object>();
mapObject1.put("No",2);
mapObject1.put("Name","test");
mapList.add(mapObject1);
and so on...
Now I want to get all the values of the key "No" as a string seperated by comma as follows:
String noList="1,2,3"
Can anyone please suggest me what may best way to do it. I know we can do it by looping but instead of looping is any other ways to do it.
Explanations inline!
mapList.stream() // stream over the list
.map(m -> m.get("No")) // try to get the key "No"
.filter(Objects::nonNull) // filter any null values in case it wasn't present
.map(Object::toString) // call toString for each object
.collect(Collectors.joining(",")); // join the values
Simply map the list:
String list = mapList.stream()
.filter(x -> x.containsKey("No")) // get only the maps that has the key
.map(x -> x.get("No").toString()) // every map will be transformed like this
.collect(Collectors.joining(",")); // joins all the elements with ","
System.out.println(list);
The use of HashMap<String, Object> suggests that it might be better to create a new class for this data. Have you considered this possibility before?
You can loop like this:
List<String> noList = new ArrayList<>(mapList.size());
for (Map<String,Object> m : mapList) {
Optional.ofNullable(m.get("No")) // get value mapped to "No" or empty Optional
.map(Object::toString)
.ifPresent(noList::add); // if not empty, add to list
}
System.out.println(String.join(",", noList));
or internally (the officially preferred version IIRC):
List<String> noList = new ArrayList<>(mapList.size());
mapList.forEach(m ->
Optional.ofNullable(m.get("No")).map(Object::toString).ifPresent(noList::add));
System.out.println(String.join(",", noList));
Now that I think of it, it's shorter than the Stream version.
Answered a pretty similar question 30 minutes ago.
You are using repeated keys. This makes it look like you don't need maps, but a class with the attributes "No", "Name", etc. If you've this class you can just iterate your instances on the list and concatenating to a String.
If no matter what you want to have your maps, simply get the values of the "No" key, but note that this is a wrong practise and you should be probably using a class instead of maps:
String res = "";
for(int i = 0; i < mapList.size(); i++) {
Map<String,Object> map = mapList.get(i);
res.concat(map.get("No"));
if(i != mapList.size() - 1)
res.concat(",");
}
PS: If you are going with the bad solution practise, use the stream alternatives in the other answers if your knowledge of stream is enough to understand them.

Extract any value from hashmap (one for each key)

I have a large map with different keys and several values (DepthFeed) associated to each. I would like to get any value (DepthFeed) from that to be able to extract the name of the instrument one for each key.
I have this map
private static Map<Integer, List<DepthFeed>> mapDepthFeed = new HashMap<>();
From that I would like to do something like, however not returning the keyset integer. Instead I want a List<DepthFeed> back (containing one row for each key)
List<DepthFeed> d = mapPriceFeed.values().stream().distinct().collect(Collectors.toList());
Use
List<DepthFeed> result = mapDepthFeed.values().stream()
.filter(list -> !list.isEmpty())
.map(list -> list.get(0))
.collect(Collectors.toList());
This way you will get the first element from each non-empty list stored in map values.

Categories