I'm storing information of the lastByFirst variable.
{Peter=[Leigh], George=[Barron, Trickett,Evans],
Paul-Courtenay=[Hyu], Ryan=[Smith], Toby=[Geller, Williams],
Simon=[Bush, Milosic, Quarterman,Brown]}
How can I print the first 3 which appeared the most and also the number of appereance.
I would like to list those which 3 value appeared the most. In the lastByFirst contains something like that. I would like to print in this way:
Simon: 4
George: 3
Toby:2
Map<String, List<String>> lastByFirst = PeopleProcessor.lastnamesByFirstname(PeopleSetup.people);
My attempt was something like that:
var store = lastByFirst.entrySet()
.stream()
.collect( Collectors.groupingBy(Person::getLastName,
Collectors.counting())
.toString();
store should be equal with
Simon: 4
George: 3
Toby:2
Here's one that first converts the map of lists to a map of the sizes of the list, and then picks the top three such sizes:
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Demo {
public static void main(String[] args) {
Map<String, List<String>> lastByFirst =
Map.of("Peter", List.of("Leigh"), "George", List.of("Barron", "Trickett", "Evans"),
"Paul-Courtenay", List.of("Hyu"), "Ryan", List.of("Smith"),
"Toby", List.of("Geller", "Williams"), "Simon", List.of("Bush", "Milosic", "Quaterman", "Brown"));
List<Map.Entry<String, Integer>> topThree =
lastByFirst.entrySet().stream()
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, e -> e.getValue().size()))
.entrySet()
.stream()
.sorted(Comparator.<Map.Entry<String, Integer>, Integer>comparing(Map.Entry::getValue).reversed())
.limit(3)
.collect(Collectors.toList());
System.out.println(topThree);
}
}
You can:
sort in descending mode by size
select the first three elements
reduce to one string
//1
List<Map.Entry<String, List<String>>> entryList = lastByFirst.entrySet()
.stream()
.sorted((e2, e1) -> Integer.compare(e1.getValue().size(), e2.getValue().size()))
.toList();
//2
String result = IntStream.range(0, 3)
.mapToObj(entryList::get)
.map(e -> String.format("%s: %d\n", e.getKey(), e.getValue().size()))
.collect(Collectors.joining()); //3
If you already have a map of people grouped by first name, you can address the problem of finding the 3 most frequent first names in a linear time O(n). Which is faster than sorting the whole data set.
If instead of picking 3 most frequent first names it would be generalized to m most frequent, then the time complexity would be O(n + m * log m) (which for small values of m would be close to linear time).
To implement it using streams, we can utilize a custom comparator, which can be created using the static method Collector.of().
As a mutable container of the collector, we can use a TreeMap sorted in the natural order, where the key would represent the of people having the same first name and the value would be a first name itself.
In order to retain only m most frequent names we need to track the size of the TreeMap and when it gets exceeded we have to remove the first entry (i.e. an entry having the lowest key).
public static <K, V> Collector<Map.Entry<K, List<V>>, ?, NavigableMap<Integer, K>>
getEntryCollector(int size) {
return Collector.of(
TreeMap::new,
(NavigableMap<Integer, K> map, Map.Entry<K, List<V>> entry) -> {
if (map.size() < size || map.firstKey() < entry.getValue().size()) { // the container hasn't reached the maximum size of the frequency of the offered name is higher than the lowest existing frequency
map.put(entry.getValue().size(), entry.getKey());
}
if (map.size() > size) map.remove(map.firstKey()); // the size of the container has been exceeded
},
(NavigableMap<Integer, K> left, NavigableMap<Integer, K> right) -> { // merging the two containers with partial results obtained during the parallel execution
left.putAll(right);
while (left.size() > size) left.remove(left.firstKey());
return left;
}
);
}
main()
public static void main(String args[]) {
Map<String, List<String>> lastByFirst =
Map.of("Peter", List.of("Leigh"), "George", List.of("Barron", "Trickett", "Evans"),
"Paul-Courtenay", List.of("Hyu"), "Ryan", List.of("Smith"), "Toby", List.of("Geller", "Williams"),
"Simon", List.of("Bush", "Milosic", "Quarterman", "Brown"));
NavigableMap<Integer, String> nameByFrequency =
lastByFirst.entrySet().stream()
.collect(getEntryCollector(3));
nameByFrequency.entrySet().stream() // printing the result, sorting in reversed order applied only for demo purposes
.sorted(Map.Entry.comparingByKey(Comparator.<Integer>naturalOrder().reversed()))
.forEach(entry -> System.out.println(entry.getValue() + ": " + entry.getKey()));
}
Output:
Simon: 4
George: 3
Toby: 2
A link Online Demo
Here is another solution by StreamEx:
EntryStream.of(lastByFirst)
.mapValues(List::size) //
.reverseSorted(Comparators.comparingByValue())
.limit(3)
.toList()
.forEach(System.out::println);
Related
So basically i have a Map which contains lists. for example
Map<String, List<String>> data = new HashMap<>();
data.put("car", Arrays.asList("toyota", "bmw", "honda"));
data.put("fruit", Arrays.asList("apple","banana"));
data.put("computer", Arrays.asList("acer","asus","ibm"));
List<String> multipleVersions = new ArrayList<>();
I would like a exeption thrown is lets say there are more than 2 elements in any given list so something like this
if(data.values().size() >= 3){
System.out.println("multipbe versions exist" );
}
my question is how would i stream and filter out these elements greater than x and add it to the print statement?
i tried creating a new list and adding in these values
if (data.values().size() > 2){
multipleVersions.add((data.values().stream().filter(val -> val.size() > 2)).toString());
}
but when i go to print it out
multipleVersions.stream().forEach(System.out::println);
i get java.util.stream.ReferencePipeline$2#1b28cdfa
I know i need to read up on streams a bit more but can someone explain to me how to do this correctly and elegantly ?
public class Main {
public static void main(String[] args) {
Map<String, List<String>> data = new HashMap<>();
data.put("car", Arrays.asList("toyota", "bmw", "honda"));
data.put("fruit", Arrays.asList("apple","banana"));
data.put("computer", Arrays.asList("acer","asus","ibm"));
List<String> multipleVersions = new ArrayList<>();
if (data.values().size() > 2){
multipleVersions.add((data.values().stream().filter(val -> val.size() > 2)).toString());
}
if(data.values().size() >= 3){
System.out.println("Over limit in the following " + );
System.out.println(multipleVersions.stream().toList())
}
}
expected to get Over limit in the following : Car , Computer
i can print of the lists like so
System.out.println(data.values().stream().filter(val-> val.size() > 2).collect(Collectors.toSet()));
but still need to get the keys
I would like a exeption thrown is lets say there are more than 2 elements in any given list
You can iterate over the Values of the Map and pick the largest size using mapToInt().max().
public static <K, V> int getMaxSize(Map<K, List<V>> map) {
return map.values().stream() // Stream<List<V>>
.mapToInt(List::size) // IntStream
.max() // OptionalInt
.orElse(0);
}
Usage example:
Map<String, List<String>> data = // initializing the map
if (getMaxSize(data) > 2) {
throw new MyException(); // choise the proper exception type
}
I would like to do is essentially print out the keys with a value larger than x.
You can obtain all entries having list sizes greater than the given number in the following way:
public static <K, V> Map<K, List<V>> getGetEntriesLargerThen(Map<K, List<V>> map, int size) {
return map.entrySet().stream()
.filter(e -> e.getValue().size() > size)
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue
));
}
Map<String, List<String>> data = // initializing the map
getGetEntriesLargerThen(data, 2)
.forEach((k, v) -> System.out.println(k + " -> " + v));
I have the following for loop which I would like to replace by a simple Java 8 stream statement:
List<String> words = new ArrayList<>("a", "b", "c");
Map<String, Long> wordToNumber = new LinkedHashMap<>();
Long index = 1L;
for (String word : words) {
wordToNumber.put(word, index++);
}
I basically want a sorted map (by insertion order) of each word to its number (which is incremented at each for loop by 1), but done simpler, if possible with Java 8 streams.
Map<String, Long> wordToNumber =
IntStream.range(0, words.size())
.boxed()
.collect(Collectors.toMap(
words::get,
x -> Long.valueOf(x) + 1,
(left, right) -> { throw new RuntimeException();},
LinkedHashMap::new
));
You can replace that (left, right) -> { throw new RuntimeException();} depending on how you want to merge two elements.
The following should work (though it's not clear why Long is needed because the size of List is int)
Map<String, Long> map = IntStream.range(0, words.size())
.boxed().collect(Collectors.toMap(words::get, Long::valueOf));
The code above works if there's no duplicate in the words list.
If duplicate words are possible, a merge function needs to be provided to select which index should be stored in the map (first or last)
Map<String, Long> map = IntStream.range(0, words.size())
.boxed().collect(
Collectors.toMap(words::get, Long::valueOf,
(w1, w2) -> w2, // keep the index of the last word as in the initial code
LinkedHashMap::new // keep insertion order
));
Similarly, the map can be built by streaming words and using external variable to increment the index (AtomicLong and getAndIncrement() may be used instead of long[]):
long[] index = {1L};
Map<String, Long> map = words.stream()
.collect(
Collectors.toMap(word -> word, word -> index[0]++,
(w1, w2) -> w2, // keep the index of the last word
LinkedHashMap::new // keep insertion order
));
A slightly different solution. The Integer::max is the merge function which gets called if the same word appears twice. In this case it picks the last position since that effectively what the code sample in the question does.
#Test
public void testWordPosition() {
List<String> words = Arrays.asList("a", "b", "c", "b");
AtomicInteger index = new AtomicInteger();
Map<String, Integer> map = words.stream()
.map(w -> new AbstractMap.SimpleEntry<>(w, index.incrementAndGet()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Integer::max));
System.out.println(map);
}
Output:
{a=1, b=4, c=3}
Edit:
Incorporating Alex's suggestions in the comments, it becomes:
#Test
public void testWordPosition() {
List<String> words = Arrays.asList("a", "b", "c", "b");
AtomicLong index = new AtomicLong();
Map<String, Long> map = words.stream()
.collect(Collectors.toMap(w -> w, w -> index.incrementAndGet(), Long::max));
System.out.println(map);
}
I basically want a sorted map (by insertion order) of each word to its
number (which is incremented at each for loop by 1), but done simpler,
if possible with Java 8 streams.
You can do it concisely using the following Stream:
AtomicLong index = new AtomicLong(1);
words.stream().forEach(word -> wordToNumber.put(word, index.getAndIncrement()));
Personally, I think that either
Map<String, Long> wordToNumber = new LinkedHashMap<>();
for(int i = 0; i < words.size(); i++){
wordToNumber.put(words.get(i), (long) (i + 1));
}
or
Map<String, Long> wordToNumber = new LinkedHashMap<>();
for (String word : words) {
wordToNumber.put(word, index++);
}
is simpler enough.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
I have an input array as
["Setra","Mercedes","Volvo","Mercedes","Skoda","Iveco","MAN",null,"Skoda","Iveco"]
expected output should be
{Iveco=2, Mercedes=2, Skoda=2, MAN=1, Setra=1, Volvo=1}
meaning a map with the key as Vehicle brands and the value as their occurrence count and the similar valued elements should be sorted by the keys alphabetically and value.
I have tried like
public static String busRanking(List<String> busModels) {
Map<String, Long> counters = busModels.stream().skip(1).filter(Objects::nonNull)
.filter(bus -> !bus.startsWith("V"))
.collect(Collectors.groupingBy(bus-> bus, Collectors.counting()));
Map<String, Long> finalMap = new LinkedHashMap<>();
counters.entrySet().stream()
.sorted(Map.Entry.comparingByValue(
Comparator.reverseOrder()))
.forEachOrdered(
e -> finalMap.put(e.getKey(),
e.getValue()));
return finalMap.toString();
}
public static void main(String[] args) {
List<String> busModels = Arrays.asList( "Setra","Mercedes","Volvo","Mercedes","Skoda","Iveco","MAN",null,"Skoda","Iveco");
String busRanking = busRanking(busModels);
System.out.println(busRanking);
}
And the output I am getting
{Skoda=2, Mercedes=2, Iveco=2, Setra=1, MAN=1}
Any suggestion? And the output has to be obtained using single stream()
I think the nice sollution would be:
public static void main(String... args) {
List<String> busModels = Arrays.asList( "Setra","Mercedes","Volvo","Mercedes","Skoda","Iveco","MAN",null,"Skoda","Iveco");
Map<String, Long> collect = busModels.stream()
.filter(Objects::nonNull)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
TreeMap<String, Long> stringLongTreeMap = new TreeMap<>(collect);
Set<Map.Entry<String, Long>> entries = stringLongTreeMap.entrySet();
ArrayList<Map.Entry<String, Long>> list = new ArrayList<>(entries);
list.sort((o1, o2) -> o2.getValue().compareTo(o1.getValue()));
String busRanking = list.toString();
System.out.println(busRanking);
}
If you can use a third party library, this should work using Streams with Eclipse Collections:
Comparator<ObjectIntPair<String>> comparator =
Comparators.fromFunctions(each -> -each.getTwo(), ObjectIntPair::getOne);
String[] strings = {"Setra", "Mercedes", "Volvo", "Mercedes", "Skoda", "Iveco",
"MAN", null, "Skoda", "Iveco"};
List<ObjectIntPair<String>> pairs = Stream.of(strings).collect(Collectors2.toBag())
.select(Predicates.notNull())
.collectWithOccurrences(PrimitiveTuples::pair, Lists.mutable.empty())
.sortThis(comparator);
System.out.println(pairs);
Outputs:
[Iveco:2, Mercedes:2, Skoda:2, MAN:1, Setra:1, Volvo:1]
This can also be simplified slightly by not using Streams.
List<ObjectIntPair<String>> pairs = Bags.mutable.with(strings)
.select(Predicates.notNull())
.collectWithOccurrences(PrimitiveTuples::pair, Lists.mutable.empty())
.sortThis(comparator);
Note: I am a committer for Eclipse Collections
'One-liner' using the standard Java API:
Map<String, Long> map = busModels.stream()
.filter(Objects::nonNull)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.sorted(Comparator
.comparing((Entry<String, Long> e) -> e.getValue()).reversed()
.thenComparing(e -> e.getKey()))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (left, right) -> left + right, LinkedHashMap::new));
Here's what happens:
It first filters out nulls.
Then it collects into a map, grouping by the number of occurrences.
Then it walks over the entries of the resulting map, in order to sort it by the value first, and then comparing the key (so if two number of occurrences are the same, then Iveco comes before MAN).
At last, we're extracting the entries into a LinkedHashMap, which preserves the insertion order.
Output:
{Iveco=2, Mercedes=2, Skoda=2, MAN=1, Setra=1, Volvo=1}
This uses a single, method-chaining statement. I wouldn't, however, call this a single stream operation, since in fact two streams are created, one to collect the occurrences, and a new one of the entry set to sort.
Streams are normally not designed to be traversed more than once, at least not in the standard Java API.
Can it be done with StreamEx?
There exists a library called StreamEx, which perhaps contains a method which allows us to do it with a single stream operation.
Map<String, Long> map = StreamEx.of(buses)
.filter(Objects::nonNull)
.map(t -> new AbstractMap.SimpleEntry<>(t, 1L))
.sorted(Comparator.comparing(Map.Entry::getKey))
.collapse(Objects::equals, (left, right) -> new AbstractMap.SimpleEntry<>(left.getKey(), left.getValue() + right.getValue()))
.sorted(Comparator
.comparing((Entry<String, Long> e) -> e.getValue()).reversed()
.thenComparing(e -> e.getKey()))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (left, right) -> left + right, LinkedHashMap::new));
Here's what happens:
We filter out nulls.
We map it to a stream of Entrys, each key being the bus name, and each value being the initial number of occurrences, which is 1.
Then we sort the stream by each key, making sure that all the same buses are adjacent in the stream.
That allows us to use the collapse method, which merges series of adjacent elements which satisfy the given predicate using the merger function. So Mercedes => 1 + Mercedes => 1 becomes Mercedes => 2.
Then we sort and collect as described above.
This at least seems to be doing in a single stream operation.
I am not so familiar with Java 8 (still learning) and looking to see if I could find something equivalent of the below code using streams.
The below code mainly tries to get corresponding double value for each value in String and then sums it up. I could not find much help anywhere on this format. I am not sure if using streams would clean up the code or would make it messier.
// safe assumptions - String/List (Key/Value) cannot be null or empty
// inputMap --> Map<String, List<String>>
Map<String, Double> finalResult = new HashMap<>();
for (Map.Entry<String, List<String>> entry : inputMap.entrySet()) {
Double score = 0.0;
for (String current: entry.getValue()) {
score += computeScore(current);
}
finalResult.put(entry.getKey(), score);
}
private Double computeScore(String a) { .. }
Map<String, Double> finalResult = inputMap.entrySet()
.stream()
.collect(Collectors.toMap(
Entry::getKey,
e -> e.getValue()
.stream()
.mapToDouble(str -> computeScore(str))
.sum()));
Above code iterates over the map and creates a new map with same keys & before putting the values, it first iterates over each value - which is a list, computes score via calling computeScore() over each list element and then sums the scores collected to be put in the value.
You could also use the forEach method along with the stream API to yield the result you're seeking.
Map<String, Double> resultSet = new HashMap<>();
inputMap.forEach((k, v) -> resultSet.put(k, v.stream()
.mapToDouble(s -> computeScore(s)).sum()));
s -> computeScore(s) could be changed to use a method reference i.e. T::computeScore where T is the name of the class containing computeScore.
How about this one:
Map<String, Double> finalResult = inputMap.entrySet()
.stream()
.map(entry -> new AbstractMap.SimpleEntry<String, Double>( // maps each key to a new
// Entry<String, Double>
entry.getKey(), // the same key
entry.getValue().stream()
.mapToDouble(string -> computeScore(string)).sum())) // List<String> mapped to
// List<Double> and summed
.collect(Collectors.toMap(Entry::getKey, Entry::getValue)); // collected by the same
// key and a newly
// calulcated value
The version above could be merged to the single collect(..) method:
Map<String, Double> finalResult = inputMap.entrySet()
.stream()
.collect(Collectors.toMap(
Entry::getKey, // keeps the same key
entry -> entry.getValue()
.stream() // List<String> -> Stream<String>
// then Stream<String> -> Stream<Double>
.mapToDouble(string -> computeScore(string))
.sum())); // and summed
The key parts:
collect(..) performs a reduction on the elements using a certain strategy with a Collector.
Entry::getKey is a shortcut for entry -> entry.getKey. A function for mapping the key.
entry -> entry.getValue().stream() returns the Stream<String>
mapToDouble(..) returns the DoubleStream. This has an aggregating operation sum(..) which sums the elements - together creates a new value for the Map.
Regardless of whether you use the stream-based or the loop-based solution, it would be beneficial and add some clarity and structure to extract the inner loop into a method:
private double computeScore(Collection<String> strings)
{
return strings.stream().mapToDouble(this::computeScore).sum();
}
Of course, this could also be implemented using a loop, but ... that's exactly the point: This method can now be called, either in the outer loop, or on the values of a stream of map entries.
The outer loop or stream could also be pulled into a method. In the example below, I generalized this a bit: The type of the keys of the map does not matter. Neither does whether the values are List or Collection instances.
As an alternative to the currently accepted answer, the stream-based solution here does not fill a new map that is created manually. Instead, it uses a Collector.
(This is similar to other answers, but I think that the extracted computeScore method greatly simplifies the otherwise rather ugly lambdas that are necessary for the nested streams)
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public class ToStreamOrNotToStream
{
public static void main(String[] args)
{
ToStreamOrNotToStream t = new ToStreamOrNotToStream();
Map<String, List<String>> inputMap =
new LinkedHashMap<String, List<String>>();
inputMap.put("A", Arrays.asList("1.0", "2.0", "3.0"));
inputMap.put("B", Arrays.asList("2.0", "3.0", "4.0"));
inputMap.put("C", Arrays.asList("3.0", "4.0", "5.0"));
System.out.println("Result A: " + t.computeA(inputMap));
System.out.println("Result B: " + t.computeB(inputMap));
}
private <T> Map<T, Double> computeA(
Map<T, ? extends Collection<String>> inputMap)
{
Map<T, Double> finalResult = new HashMap<>();
for (Entry<T, ? extends Collection<String>> entry : inputMap.entrySet())
{
double score = computeScore(entry.getValue());
finalResult.put(entry.getKey(), score);
}
return finalResult;
}
private <T> Map<T, Double> computeB(
Map<T, ? extends Collection<String>> inputMap)
{
return inputMap.entrySet().stream().collect(
Collectors.toMap(Entry::getKey, e -> computeScore(e.getValue())));
}
private double computeScore(Collection<String> strings)
{
return strings.stream().mapToDouble(this::computeScore).sum();
}
private double computeScore(String a)
{
return Double.parseDouble(a);
}
}
I found it somewhat shorter:
value = startDates.entrySet().stream().mapToDouble(Entry::getValue).sum();
I am sorting a populated set of MyObject (the object has a getName() getter) in a stream using a predefined myComparator.
Then once sorted, is there a way to collect into a map the name of the MyObject and the order/position of the object from the sort?
Here is what I think it should look like:
Set<MyObject> mySet; // Already populated mySet
Map<String, Integer> nameMap = mySet.stream()
.sorted(myComparator)
.collect(Collectors.toMap(MyObject::getName, //HowToGetThePositionOfTheObjectInTheStream));
For example, if the set contain three objects (object1 with name name1, object2 with name name2, object3 with name name3) and during the stream they get sorted, how do I get a resulting map that looks like this:
name1, 1
name2, 2
name3, 3
Thanks.
A Java Stream doesn't expose any index or positioning of elements, so I know no way of replacing /*HowToGetThePositionOfTheObjectInTheStream*/ with streams magic to obtain the desired number.
Instead, one simple way is to collect to a List instead, which gives every element an index. It's zero-based, so when converting to a map, add 1.
List<String> inOrder = mySet.stream()
.sorted(myComparator)
.map(MyObject::getName)
.collect(Collectors.toList());
Map<String, Integer> nameMap = new HashMap<>();
for (int i = 0; i < inOrder.size(); i++) {
nameMap.put(inOrder.get(i), i + 1);
}
Try this one. you could use AtomicInteger for value of each entry of map. and also to guarantee order of map use LinkedHashMap.
AtomicInteger index = new AtomicInteger(1);
Map<String, Integer> nameMap = mySet.stream()
.sorted(myComparator)
.collect(Collectors
.toMap(MyObject::getName, value -> index.getAndIncrement(),
(e1, e2) -> e1, LinkedHashMap::new));
The simplest solution would be a loop, as a formally correct stream solution that would also work in parallel requires a nontrivial (compared to the rest) merge functions:
Map<String,Integer> nameMap = mySet.stream()
.sorted(myComparator)
.collect(HashMap::new, (m, s) -> m.put(s.getName(), m.size()),
(m1, m2) -> {
int offset = m1.size();
m2.forEach((k, v) -> m1.put(k, v + offset));
});
Compare with a loop/collection operations:
List<MyObject> ordered = new ArrayList<>(mySet);
ordered.sort(myComparator);
Map<String, Integer> result = new HashMap<>();
for(MyObject o: ordered) result.put(o.getName(), result.size());
Both solutions assume unique elements (as there can be only one position). It’s easy to change the loop to detect violations:
for(MyObject o: ordered)
if(result.putIfAbsent(o.getName(), result.size()) != null)
throw new IllegalStateException("duplicate " + o.getName());
Dont use a stream:
List<MyObject> list = new ArrayList<>(mySet);
list.sort(myComparator);
Map<String, Integer> nameMap = new HashMap<>();
for (int i = 0; i < list.size(); i++) {
nameMap.put(list.get(i).getName(), i);
}
Not only will this execute faster than a stream based approach, everyone knows what's going on.
Streams have their place, but pre-Java 8 code does too.