Swapping key from a Map<Key, List<Values>> - java

I'm searching a solution for this problem(it is for an exam):
I have a Map < String, SortedSet < String > > operators populated by a function
public void addOperator(String operatorName, String... destinationNames) throws ProposalException {
if(operators.containsKey((operatorName))){
throw new ProposalException("Operator " + operatorName + "already into system!");
}
else{
SortedSet<String> destinationstemp=new TreeSet<>();
for(String s: destinationNames){
if(s!=null){
destinationstemp.add(s);
}
}
operators.put(operatorName, destinationstemp);
}
Now, i want to create a new Map < String, SortedSet < String > > destinations that has as key the destinationName and as values the operatorNames related.
How can i make this out?
P.S: this one up there is the usage of the methods and the not-in-code part is the output wanted. Sorry for the bad formattation of the code. ph is the instance of the façade pattern class
public SortedSet<String> getDestOperators(String destinationName) {...}//method that returns the **destinations** values related to destinationName}
ph.addOperator("op3","london","rome");
ph.addOperator("op2","london","berlin");
ph.addOperator("op5","berlin","rome","madrid");
ph.addOperator("op1","london","madrid","berlin");
ph.addOperator("op10","rome");
ph.addOperator("op4","madrid","berlin");
System.out.println(ph.getDestOperators("madrid"));
Output: [op1, op4, op5]

you need to go through each entry in your map and check if inner set contains the value you are checking against,
public SortedSet<String> getDestOperators(String destinationName) {
Set<String> result = new HashSet<String>();
for(Map.Entry<String,Set<String>> entry : operators.getValues()){
if(entry.getValue().contains(destinationName)){
results.add(entry.getKey());
}
}
return result;
}

To get your example output a simple one-liner with streams:
List<String> result = operators.entrySet().stream().filter(entry -> entry.getValue().contains(destinationName)).map(Entry::getKey).sorted().collect(Collectors.toList());
or here for better readability spread over multiple lines:
List<String> result = operators
.entrySet()
.stream()
.filter(entry -> entry.getValue().contains(destinationName))
.map(Entry::getKey)
.sorted()
.collect(Collectors.toList());
A more complex one-liner if you want to "reverse" the mapping as described in your text:
Map<String, List<String>> result = operators.entrySet().stream().flatMap(entry -> entry.getValue().stream().collect(Collectors.toMap(Function.identity(), o -> Arrays.asList(entry.getKey()))).entrySet().stream()).collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> Stream.of(a, b).flatMap(List::stream).sorted().collect(Collectors.toList())));
or here for better readability spread over multiple lines:
Map<String, List<String>> result2 = operators
.entrySet()
.stream()
.flatMap(entry -> entry
.getValue()
.stream()
.collect(Collectors.toMap(Function.identity(),
o -> Arrays.asList(entry.getKey())))
.entrySet()
.stream())
.collect(Collectors.toMap(Entry::getKey,
Entry::getValue,
(a, b) -> Stream.of(a, b)
.flatMap(List::stream)
.sorted()
.collect(Collectors.toList())));

What you need to do, is loop over each operator, and then loop over all entries in the list, if value from the list is not yet present in your output map, you add it, else you modify its colection of operators.
Here is some code for you:
origin.forEach((key, list) -> {list.forEach(city -> {
if(result.containsKey(city))
result.get(city).add(key);
else{
SortedSet<String> set = new TreeSet<>();
set.add(key);
result.put(city, set);
});
});

Related

Java count word frequency using stream

Hey I need to count frequency of words and return a string listing them. I have to omit words that have less than 4 characters and words that have count of less than 10. I have to order them from highest to lowest count as well as alphabetically if count is same.
Here's the code.
import java.util.*;
import java.util.stream.*;
public class Words {
public String countWords(List<String> lines) {
String text = lines.toString();
String[] words = text.split("(?U)\\W+");
Map<String, Long> freq = Arrays.stream(words).sorted()
.collect(Collectors.groupingBy(String::toLowerCase,
Collectors.counting()));
LinkedHashMap<String, Long> freqSorted = freq.entrySet().stream()
.filter(x -> x.getKey().length() > 3)
.filter(y -> y.getValue() > 9)
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue, (oldValue, newValue) -> oldValue,
LinkedHashMap::new));
return freqSorted.keySet().stream()
.map(key -> key + " - " + freqSorted.get(key))
.collect(Collectors.joining("\n", "", ""));
}
}
I can't change the argument of this method. I have trouble sorting it alphabetically after sorting it by value. Tried using thenCompare but couldn't make it work. Aside from that I'd appreciate any feedback on how to reduce number of lines so I don't have to stream 3 times.
Another aproach to do it in one go without intermediate collecting into maps is to wrap your grouping collector in collectingAndThen, where you can format your final result :
public String countWords(List<String> lines) {
String text = lines.toString();
String[] words = text.split("(?U)\\W+");
return Arrays.stream(words)
.filter(s -> s.length() > 3)
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(String::toLowerCase, Collectors.counting()),
map -> map.entrySet()
.stream()
.filter(e -> e.getValue() > 9)
.sorted(Map.Entry.<String, Long>comparingByValue().reversed()
.thenComparing(Map.Entry.comparingByKey()))
.map(e -> String.format("%s - %d", e.getKey(), e.getValue()))
.collect(Collectors.joining(System.lineSeparator()))));
}
Here is one approach. I am using your frequency count map as the source.
first define a comparator.
then sort putting the existing map into sorted order
toMap takes a key, value, merge function, and final map of LinkedhashMap to preserve the order.
Comparator<Entry<String, Long>> comp =
Entry.comparingByValue(Comparator.reverseOrder());
comp = comp.thenComparing(Entry.comparingByKey());
Map<String, Long> freqSorted = freq.entrySet().stream()
.filter(x -> x.getKey().length() > 3
&& x.getValue() > 9)
.sorted(comp)
.collect(Collectors.toMap(Entry::getKey,
Entry::getValue, (a, b) -> a,
LinkedHashMap::new));
Notes:
To verify that the sorting is proper you can comment out the filter and use fewer words.
you do not need to sort your initial stream of words when preparing the frequency count as they will be sorted in the final map.
the merge function is syntactically required but not used since there are no duplicates.
I chose not to use TreeMap as once the stream is sorted, there is no need to sort again.
The problem should be your LinkedHasMap because it only keeps insertion order and therefore can't be sorted. You can try using TreeMap since it can be sorted and keeps the order.
And I think you shouldn't focus about getting as less lines as possible instead try to get it as readable as possible for the future. So I think what you have there is fine because you split the streams in logical parts; Counting, Sorting and joining!
To swap to TreeMap just change the variable and collector type
Would look like this:
import java.util.*;
import java.util.stream.*;
public class Words {
public String countWords(List<String> lines) {
String text = lines.toString();
String[] words = text.split("(?U)\\W+");
Map<String, Long> freq = Arrays.stream(words).sorted()
.collect(Collectors.groupingBy(String::toLowerCase,
Collectors.counting()));
TreeMap<String, Long> freqSorted = freq.entrySet().stream()
.filter(x -> x.getKey().length() > 3)
.filter(y -> y.getValue() > 9)
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue, (oldValue, newValue) -> oldValue,
TreeMap::new));
return freqSorted.keySet().stream()
.map(key -> key + " - " + freqSorted.get(key))
.collect(Collectors.joining("\n", "", ""));
}
}

Method to calculate the most frequent last name from list of given users with Java Stream API

Function should return optional of most frequent last name (if it encountered at least two times) or optional empty if number of last names is the same or list of users is empty
This is what i came up with, but it doesnt return Optional.empty
#Override
public Optional<String> getMostFrequentLastName(final List<User> users) {
return users.stream()
.map(User::getLastName)
.distinct()
.collect
(Collectors.groupingBy(
Function.identity(),
Collectors.summingInt(w -> 1)
))
.entrySet()
.stream()
.filter(stringIntegerEntry -> stringIntegerEntry.getValue() >= 2)
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.map(Map.Entry::getKey)
.findFirst();
}
This is my test class
public static void main(String[] args) {
Optional<String> optionalS = Stream.of(new User("name1"),
new User("name1"), new User("name2"), new User("name2"))
.map(User::getLastName)
.collect
(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
))
.entrySet()
.stream()
.filter(stringIntegerEntry -> stringIntegerEntry.getValue() >= 2)
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.map(Map.Entry::getKey)
.findFirst();
System.out.println(optionalS.toString());
}
Here is the awnser
Optional[name2]
But should be
Optional[empty]
You may use
Optional<String> optionalS =
Stream.of(new User("name1"), new User("name1"), new User("name2"), new User("name2"))
.collect(Collectors.groupingBy(User::getLastName, Collectors.counting()))
.entrySet()
.stream()
.filter(entry -> entry.getValue() >= 2)
.reduce((e1, e2) -> e1.getValue() < e2.getValue()? e2:
e1.getValue() > e2.getValue()? e1:
new AbstractMap.SimpleImmutableEntry<>(null, e1.getValue()))
.map(Map.Entry::getKey);
System.out.println(optionalS.toString());
Getting the maximum value is a form of Reduction. Since you want to get an empty optional in case of a tie, the simplest solution is to write the reduction function explicitly, use the Map.Entry with the bigger value if there is one, otherwise construct a new Map.Entry with a null key.
The result of the reduction is already an Optional, which will be empty if there were no elements (with a count >=2). So the last map step is applied on an Optional. If already empty, the map function won’t be evaluated and the resulting Optional stays empty. If the optional is not empty, but Map.Entry::getKey evaluates to null, the resulting optional will be empty.
It seems to me that if you have the same number of maximum of some different lastNames you want to return an Optional::empty, as such:
Map<String, Long> map =
Stream.of(new User("name1"),
new User("name1"),
new User("name2"),
new User("name2"))
.collect(Collectors.groupingBy(User::getLastName, Collectors.counting()));
map.entrySet()
.stream()
.max(Entry.comparingByValue())
.flatMap(en -> {
boolean b = map.entrySet()
.stream()
.filter(x -> !x.getKey().equals(en.getKey()))
.mapToLong(Entry::getValue)
.noneMatch(x -> x == en.getValue());
return b ? Optional.of(en.getKey()) : Optional.empty();
})
.ifPresent(System.out::println);
}
Here my monster for you:
Optional<String> optionalS = Stream.of(
new User("name1"),
new User("name1"),
new User("name2"),
new User("name2"))
.map(User::getLastName)
.collect(
Collectors.groupingBy(
Function.identity(),
Collectors.counting()
))
.entrySet()
.stream()
.filter(stringIntegerEntry -> stringIntegerEntry.getValue() >= 2)
.collect(
Collectors.groupingBy(
Map.Entry::getValue,
Collectors.toList()
))
.entrySet()
.stream()
.sorted(Comparator.comparing(
Map.Entry::getKey,
Comparator.reverseOrder()))
.map(Map.Entry::getValue)
.findFirst()
.filter(x -> x.size() == 1)
.map(x -> x.get(0).getKey());
System.out.println(optionalS);
As far as I undestand your solution in stream you code creates
Map<String(lastname),Integer(number of occurence)>
and then filter that map where number of occurence >=2 and in your test case you have map with entries:
<"name1",2>
<"name2",2>
So ordering by value will still return two values.
You should try create
Map<Integer,List<String>>
which will store number of occurence -> names, then filter map keys, sort them descending and (in map value) you will get most frequently lastname (or lastnames if there were more than once in input).
//edit
Below short snippet with my solution:
Map<Integer, List<String>> map = new HashMap<>();
map.put(2,Arrays.asList("name1","name2"));
Optional<String> optionalS = map
.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey(Comparator.reverseOrder()))
.findFirst() //get max{map's keys}
.filter(x->x.getValue().size() == 1) //get lastname that occured only once
.map(x->x.getValue().get(0)); //get that lastname (above filter check that list has only one element) or Optional.empty if stream didn't find any
System.out.println(optionalS.toString());
I skipped the part of creating map.
P.S. You can replace HashMap with TreeMap with custom comparator to avoid sorting in stream.

java: how to extract values from a Map<String,Collection<Map<String,String>>>

I inherited some code and there is a map which is defined as Map<String, Collection<Map<String, String>>>
Here is a sample set of values stored in this map:
{
A1: [
{Item Number: "1234",Tax Code: "1"},
{Item Number: "2345",Tax Code: "2"},
{Item Number: "1234",Tax Code: "1"}
],
B2: [
{Store Number: "111",Status: "2"},
{Store Number: "222",Status: "3"}
]
}
How can I get, say, A1.Item Number as a list of strings? I was planning to covert this to json and use json path. But since the map itself is available to me, is there an easier way?
I am using java 8.
Thanks!
You can achieve the required result like so:
String itemNumber = "1234";
List<String> result =
myMap.values()
.stream()
.flatMap(Collection::stream)
.map(Map::entrySet)
.flatMap(Collection::stream)
.filter(e -> e.getKey().equals(itemNumber))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
This is most likely to be used numerous times, in which case it would be wise to encapsulate the logic into a method as such:
static List<String> getListOfValuesByItemNumber(
Map<String, Collection<Map<String, String>>> map,
String itemNumber){
return map.values()
.stream()
.flatMap(Collection::stream)
.map(Map::entrySet)
.flatMap(Collection::stream)
.filter(e -> e.getKey().equals(itemNumber))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}
Then simply pass a reference to the map and the item number as the second parameter and retrieve back a list of values where the map entry key is equal to the item number.
Further, a non-stream approach would be:
static List<String> getListOfValuesByItemNumber(
Map<String, Collection<Map<String, String>>> map,
String itemNumber){
List<String> accumulator = new ArrayList<>();
map.values()
.forEach(collection ->
collection.forEach(m -> m.forEach((k, v) -> {
if(k.equals(itemNumber))
accumulator.add(v);
})));
return accumulator;
}
If I understand you correctly, this is what you want:
public static List<String> extractFieldValues(
Map<String, Collection<Map<String, String>>> horribleMess,
String fieldName) {
return horribleMess.values()
.stream()
.flatMap(Collection::stream)
.map(map -> map.get(fieldName))
.collect(Collectors.toList());
}
But as indicated in a comment above, a Map/Collection/Map nesting is a maintenance nightmare you should get rid of through refactoring.
Solution is,
List<String> ItemNumberLists = ( map.values() ).stream()
.flatMap( Collection::stream )
.map( s -> s.get("a") )
.collect( Collectors.toList() );
If you still doubt ask in a comment.

List of Strings to hashmap using Streams

I have a "," separated String array like this
a b c d,
f b h j,
l p o i,
I would like this to be converted to a Hashmap like
HashMap<String, List<String>> such that second element in list (delimited by space becomes key and the 3rd element becomes value)
So,
This should become
b -> c,h
p -> o
I want to use Streams API and I think this is the way to go:
List<String> entries = new ArrayList<>();
HashMap<String, List<String>> map = new HashMap<>();
HashMap<String, List<String>> newMap = entries.stream()
.collect(line -> {
if (map.contains(line.split(" ")[1])) {
// Get existing list and add the element
map.get(line.split(" ")[1].add(line.split(" ")[1]));
} else {
// Create a new list and add
List<String> values = new ArrayList<>();
values.add(line.split(" ")[1]);
map.put(line.split(" ")[0], values);
}
});
Is there any better way? How exactly should I return Hashmap from collect function?
You can use the Collectors.groupingBy as shown below to group the inputs (follow the inline comments):
String[] inputs = {"a b c d,", "f b h j,", "l p o i,"};
Map<String, List<String>> results =
Arrays.stream(inputs).map(s -> s.split(" ")).//splt with space
collect(Collectors.groupingBy(arr -> arr[1], // Make second element as the key
Collectors.mapping(arr -> arr[2], // Make third element as the value
Collectors.toList())));//collect the values to List
System.out.println(results);
Output:
{p=[o], b=[c, h]}
I suggest you read the API here to understand how Collectors.groupingBy along with Collectors.mappingworks.
You can achieve the task at hand using a groupingBy collector along with Collectors.mapping as a downstream collector.
Map<String, List<String>> collect =
myList.stream()
.map(s -> s.split(" "))
.collect(Collectors.groupingBy(a -> a[1],
Collectors.mapping(a -> a[2], Collectors.toList())));
output:
{p=[o], b=[c, h]}
if you want to maintain insertion order then you can specify a LinkedHashMap like this:
Map<String, List<String>> collect =
myList.stream()
.map(s -> s.split(" "))
.collect(Collectors.groupingBy(s -> s[1],
LinkedHashMap::new,
Collectors.mapping(s -> s[2], Collectors.toList())));
output:
{b=[c, h], p=[o]}
If you want HashMap , not just any Map
HashMap<String, List<String>> output =myList.stream().map(s -> s.split(" "))
.collect(Collectors.groupingBy((s) -> s[1],
HashMap::new,
Collectors.mapping(
(s) -> s[2],
Collectors.toList())));

Hashmap with Streams in Java 8 Streams to collect value of Map

Let consider a hashmap
Map<Integer, List> id1 = new HashMap<Integer,List>();
I inserted some values into both hashmap.
For Example,
List<String> list1 = new ArrayList<String>();
list1.add("r1");
list1.add("r4");
List<String> list2 = new ArrayList<String>();
list2.add("r2");
list2.add("r5");
List<String> list3 = new ArrayList<String>();
list3.add("r3");
list3.add("r6");
id1.put(1,list1);
id1.put(2,list2);
id1.put(3,list3);
id1.put(10,list2);
id1.put(15,list3);
Q1) Now I want to apply a filter condition on the key in hashmap and retrieve the corresponding value(List).
Eg: Here My query is key=1, and output should be 'list1'
I wrote
id1.entrySet().stream().filter( e -> e.getKey() == 1);
But I don't know how to retrieve as a list as output of this stream operation.
Q2) Again I want to apply a filter condition on the key in hashmap and retrieve the corresponding list of lists.
Eg: Here My query is key=1%(i.e key can be 1,10,15), and output should be 'list1','list2','list3'(list of lists).
If you are sure you are going to get at most a single element that passed the filter (which is guaranteed by your filter), you can use findFirst :
Optional<List> o = id1.entrySet()
.stream()
.filter( e -> e.getKey() == 1)
.map(Map.Entry::getValue)
.findFirst();
In the general case, if the filter may match multiple Lists, you can collect them to a List of Lists :
List<List> list = id1.entrySet()
.stream()
.filter(.. some predicate...)
.map(Map.Entry::getValue)
.collect(Collectors.toList());
What you need to do is create a Stream out of the Map's .entrySet():
// Map<K, V> --> Set<Map.Entry<K, V>> --> Stream<Map.Entry<K, V>>
map.entrySet().stream()
From the on, you can .filter() over these entries. For instance:
// Stream<Map.Entry<K, V>> --> Stream<Map.Entry<K, V>>
.filter(entry -> entry.getKey() == 1)
And to obtain the values from it you .map():
// Stream<Map.Entry<K, V>> --> Stream<V>
.map(Map.Entry::getValue)
Finally, you need to collect into a List:
// Stream<V> --> List<V>
.collect(Collectors.toList())
If you have only one entry, use this instead (NOTE: this code assumes that there is a value; otherwise, use .orElse(); see the javadoc of Optional for more details):
// Stream<V> --> Optional<V> --> V
.findFirst().get()
For your Q2, there are already answers to your question. For your Q1, and more generally when you know that the key's filtering should give a unique value, there's no need to use Streams at all.
Just use get or getOrDefault, i.e:
List<String> list1 = id1.getOrDefault(1, Collections.emptyList());
You can also do it like this
public Map<Boolean, List<Student>> getpartitionMap(List<Student> studentsList) {
List<Predicate<Student>> allPredicates = getAllPredicates();
Predicate<Student> compositePredicate = allPredicates.stream()
.reduce(w -> true, Predicate::and)
Map<Boolean, List<Student>> studentsMap= studentsList
.stream()
.collect(Collectors.partitioningBy(compositePredicate));
return studentsMap;
}
public List<Student> getValidStudentsList(Map<Boolean, List<Student>> studentsMap) throws Exception {
List<Student> validStudentsList = studentsMap.entrySet()
.stream()
.filter(p -> p.getKey() == Boolean.TRUE)
.flatMap(p -> p.getValue().stream())
.collect(Collectors.toList());
return validStudentsList;
}
public List<Student> getInValidStudentsList(Map<Boolean, List<Student>> studentsMap) throws Exception {
List<Student> invalidStudentsList =
partionedByPredicate.entrySet()
.stream()
.filter(p -> p.getKey() == Boolean.FALSE)
.flatMap(p -> p.getValue().stream())
.collect(Collectors.toList());
return invalidStudentsList;
}
With flatMap you will get just List<Student> instead of List<List<Student>>.
Thanks
Using keySet-
id1.keySet().stream()
.filter(x -> x == 1)
.map(x -> id1.get(x))
.collect(Collectors.toList())
why all that !!!
List list1 = id1.get(1);
HashMap<Integer, String> hashmap = new HashMap<>();
hashmap.put(1, "a");
hashmap.put(2, "b");
List<String> collect = hashmap.keySet().stream()
.map(k -> "key=" + k + " value=" + hashmap.get(k))
.collect(Collectors.toList());
System.out.println(collect);
Maybe the sample is oversimplified, but you don't need the Java stream API here. Just use the Map directly.
List<String> list1 = id1.get(1); // this will return the list from your map

Categories

Resources