Is there a better way to count int occurrences with Java8
int[] monthCounter = new int[12];
persons.stream().forEach(person -> monthCounter[person.getBirthday().getMonthValue() - 1]++);
Try:
Map<Integer, Long> counters = persons.stream()
.collect(Collectors.groupingBy(p -> p.getBirthday().getMonthValue(),
Collectors.counting()));
There's a few variations this could take.
You can use Collectors.summingInt() to use Integer instead of the Long in the count.
If you wanted to skip the primitive int array, you could store the counts directly to a List in one iteration.
Count the birth months as Integers
Map<Integer, Integer> monthsToCounts =
people.stream().collect(
Collectors.groupingBy(p -> p.getBirthday().getMonthValue(),
Collectors.summingInt(a -> 1)));
Store the birth months in a 0-based array
int[] monthCounter = new int[12];
people.stream().collect(Collectors.groupingBy(p -> p.getBirthday().getMonthValue(),
Collectors.summingInt(a -> 1)))
.forEach((month, count) -> monthCounter[month-1]=count);
Skip the array and directly store the values to a list
List<Integer> counts = people.stream().collect(
Collectors.groupingBy(p -> p.getBirthday().getMonthValue(),
Collectors.summingInt(a -> 1)))
.values().stream().collect(Collectors.toList());
With Eclipse Collections (formerly GS Collections), you can make use of a data structure called Bag that can hold the number of occurrences of each element.
Using IntBag, the following will work:
MutableList<Person> personsEC = ListAdapter.adapt(persons);
IntBag intBag = personsEC.collectInt(person -> person.getBirthDay().getMonthValue()).toBag();
intBag.forEachWithOccurrences((month, count) -> System.out.println("Count of month:" + month + " is " + count));
If you want to make use of an array to keep track of the count, you can combine with the Arrays.setAll() approach Brian pointed out in another answer.
int[] monthCounter = new int[12];
MutableList<Person> personsEC = ListAdapter.adapt(persons);
IntBag bag = personsEC.collectInt(person -> person.getBirthDay().getMonthValue()).toBag();
Arrays.setAll(monthCounter, bag::occurrencesOf);
System.out.println(IntLists.immutable.with(monthCounter));
This code will also work with Java 5 – 7 if you use anonymous inner classes instead of lambdas.
Note: I am a committer for Eclipse Collections
If you would like to get Integer to Integer map, you can do the following.
Map<Integer, Integer> counters = persons.stream()
.collect(Collectors.groupingBy(
p -> p.getBirthday().getMonthValue(),
Collectors.reducing(0, e -> 1, Integer::sum)));
Already answered. Small Suggestion from my side inorder to eliminate null pointer exception
ie From the stream null will throw java.lang.UnsupportedOperationException, java.lang.NullPointerException
Map<Integer, Long> birthdayCount = persons.stream()
.filter(Objects::nonNull) // filter out null object
.filter(p->Objects.nonNull(p.getBirthday())) // filter out null birthdays
.collect(Collectors.groupingBy(p ->
p.getBirthday().getMonthValue(),
Collectors.counting()));
int size = persons.stream().count()
Related
Given a List<Integer> l and a factor int f, I would like to use a stream to create a Map<Integer, Map<Integer, Long>> m such that the parent map has keys that are the index within l divided by f, and the value is a map of values to counts.
If the list is {1,1,1,4} and the factor is f=2 I would like to get:
0 ->
{
1 -> 2
}
1 ->
{
1 -> 1
4 -> 1
}
Basically, I'm hoping for a stream version of:
Map<Integer, Map<Integer, Long>> m = new HashMap<>();
for (int i = 0; i < l.size(); i++) {
m.computeIfAbsent(i/f, k -> new HashMap<>())
.compute(l.get(i), (k, v) -> v==null?1:v+1);
}
I realize it is fairly similar to this question about collecting a map of maps and I understand how to do a much simpler groupingBy with a count:
Map<Integer, Long> m = l.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
But I do not understand how to put those two ideas together without iterating.
Because I am working with indexes as one of the keys, I imagine that rather than starting with l.stream() I will start with IntStream.range(0, l.size()).boxed() which lets me get the first key (i -> i/f) and the second key(i -> l.get(i)), but I still don't know how to properly collect the counts.
Here is a solution.
public static void main(String[] args) {
final List<Integer> l = List.of(1,1,1,4);
final int f = 2;
final var value = IntStream.range(0,l.size())
.boxed()
.collect(Collectors.groupingBy(i -> i/f, Collectors.groupingBy(l::get, Collectors.counting())));
System.out.println(value);
}
Not sure if this is a personal requirement, but sometime using standard loops over streams is not necessarily a bad thing.
You can wrap your grouping collector in CollectingAndThen collector which takes a downstream collector and a finisher function. In the finisher you can modify the values (sublists) to a map:
List<Integer> list = List.of(1, 1, 1, 4);
int fac = 2;
AtomicInteger ai = new AtomicInteger();
Map<Integer,Map<Integer,Long>> result =
list.stream()
.collect(Collectors.groupingBy(
i -> ai.getAndIncrement() / fac,
Collectors.collectingAndThen(
Collectors.toList(), val -> val.stream()
.collect(Collectors.groupingBy(Function.identity(),
Collectors.counting())))));
System.out.println(result);
I want a specific key from the HashMap based on a specific condition for the values. For example:
My HashMap is of the type <String,Integer>.
Map<String,Integer> getValuesInMap = new HashMap<String,Integer>();
Output = {Python=1, Java=1, OOPS=2, language=1, Ruby=3, Hey=1}
I want to retrieve the keys from this map where the integer count (i.e. value) is more than 1.
Well HashMap is not made for this kind of operations, so you can either do manually, or use Stream:
getValuesInMap.entrySet()
.stream()
.filter(e -> e.getValue() > 1)
.map(e -> e.getKey())
.collect(Collectors.toList());
If you are using Java greater than 1.7, you can use streams for that as shown below
Map<String,Integer> map = new HashMap<>();
map.put("Python", 1);
map.put("Java", 1);
map.put("OOPS", 2);
map.put("Language", 1);
map.put("Ruby", 3);
map.put("Hey", 1);
List<String> collect = map.entrySet().stream()
.filter(entry -> entry.getValue() > 1)
.map(entry -> entry.getKey())
.collect(Collectors.toList());
System.out.println(collect);
Use Collection.removeIf on the map’s values:
getValuesInMap.values().removeIf(count -> count <= 0);
System.out.println(getValuesInMap);
Be aware that the above will actually remove entries from your Map. If you need to preserve the original Map, make a copy:
Map<String, Integer> copy = new LinkedHashMap<>(getValuesInMap);
copy.values().removeIf(count -> count <= 0);
System.out.println(copy);
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.
For my assignment I have to replace for loops with streams that count the frequency of words in a text document, and I am having trouble figuring the TODO part out.
String filename = "SophieSallyJack.txt";
if (args.length == 1) {
filename = args[0];
}
Map<String, Integer> wordFrequency = new TreeMap<>();
List<String> incoming = Utilities.readAFile(filename);
wordFrequency = incoming.stream()
.map(String::toLowerCase)
.filter(word -> !word.trim().isEmpty())
.collect(Collectors.toMap(word -> word, word -> 1, (a, b) -> a + b, TreeMap::new));
int maxCnt = 0;
// TODO add a single statement that uses streams to determine maxCnt
for (String word : incoming) {
Integer cnt = wordFrequency.get(word);
if (cnt != null) {
if (cnt > maxCnt) {
maxCnt = cnt;
}
}
}
System.out.print("Words that appear " + maxCnt + " times:");
I have tried this:
wordFrequency = incoming.parallelStream().
collect(Collectors.toConcurrentMap(w -> w, w -> 1, Integer::sum));
But that is not right and I'm not sure how to incorporate maxCnt into the stream.
Assuming you have all the words extracted from a file in a List<String> this word count for each word can be computed using this approach,
Map<String, Long> wordToCountMap = words.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
The most freequent word can then be computed using the above map like so,
Entry<String, Long> mostFreequentWord = wordToCountMap.entrySet().stream()
.max(Map.Entry.comparingByValue())
.orElse(new AbstractMap.SimpleEntry<>("Invalid", 0l));
You may change the above two pipelines together if you wish like this,
Entry<String, Long> mostFreequentWord = words.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.orElse(new AbstractMap.SimpleEntry<>("Invalid", 0l));
Update
As per the following discussion it is always good to return an Optional from your computation like so,
Optional<Entry<String, Long>> mostFreequentWord = words.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.max(Map.Entry.comparingByValue());
Well, you have done almost everything you needed with that TreeMap, but it seems you don't know that it has a method called lastEntry and that is the only one you need to call after you computed wordFrequency to get the word with the highest frequency.
The only problem is that this is not very optimal, since TreeMap sorts the data on each insert and you don't need sorted data, you need the max. Sorting in case of TreeMap is O(nlogn), while inserting into a HashMap is O(n).
So instead of using that TreeMap, all you need to change is to a HashMap:
wordFrequency = incoming.stream()
.map(String::toLowerCase)
.filter(word -> !word.trim().isEmpty())
.collect(Collectors.toMap(
Function.identity(),
word -> 1,
(a, b) -> a + b,
HashMap::new));
Once you have this Map, you need to find max - this operation is O(n) in general and could be achieved with a stream or without one:
Collections.max(wordFrequency.entrySet(), Map.Entry.comparingByValue())
This approach with give you O(n) for HashMap insert, and O(n) for finding the max - thus O(n) in general, so it's faster than TreeMap
Ok, first of all, your wordFrequency line can make use of Collectors#groupingBy and Collectors#counting instead of writing your own accumulator:
List<String> incoming = Arrays.asList("monkey", "dog", "MONKEY", "DOG", "giraffe", "giraffe", "giraffe", "Monkey");
wordFrequency = incoming.stream()
.filter(word -> !word.trim().isEmpty()) // filter first, so we don't lowercase empty strings
.map(String::toLowerCase)
.collect(Collectors.groupingBy(s -> s, Collectors.counting()));
Now that we got that out of the way... Your TODO line says use streams to determine maxCnt. You can do that easily by using max with naturalOrder:
int maxCnt = wordFrequency.values()
.stream()
.max(Comparator.naturalOrder())
.orElse(0L)
.intValue();
However, your comments make me think that what you actually want is a one-liner to print the most frequent words (all of them), i.e. the words that have maxCnt as value in wordFrequency. So what we need is to "reverse" the map, grouping the words by count, and then pick the entry with highest count:
wordFrequency.entrySet().stream() // {monkey=3, dog=2, giraffe=3}
.collect(groupingBy(Map.Entry::getValue, mapping(Map.Entry::getKey, toList()))).entrySet().stream() // reverse map: {3=[monkey, giraffe], 2=[dog]}
.max(Comparator.comparingLong(Map.Entry::getKey)) // maxCnt and all words with it: 3=[monkey, giraffe]
.ifPresent(e -> {
System.out.println("Words that appear " + e.getKey() + " times: " + e.getValue());
});
This solution prints all the words with maxCnt, instead of just one:
Words that appear 3 times: [monkey, giraffe].
Of course, you can concatenate the statements to get one big do-it-all statement, like this:
incoming.stream() // [monkey, dog, MONKEY, DOG, giraffe, giraffe, giraffe, Monkey]
.filter(word -> !word.trim().isEmpty()) // filter first, so we don't lowercase empty strings
.map(String::toLowerCase)
.collect(groupingBy(s -> s, counting())).entrySet().stream() // {monkey=3, dog=2, giraffe=3}
.collect(groupingBy(Map.Entry::getValue, mapping(Map.Entry::getKey, toList()))).entrySet().stream() // reverse map: {3=[monkey, giraffe], 2=[dog]}
.max(Comparator.comparingLong(Map.Entry::getKey)) // maxCnt and all words with it: 3=[monkey, giraffe]
.ifPresent(e -> {
System.out.println("Words that appear " + e.getKey() + " times: " + e.getValue());
});
But now we're stretching the meaning of "one statement" :)
By piecing together information I was able to successfully replace the for loop with
int maxCnt = wordFrequency.values().stream().max(Comparator.naturalOrder()).get();
System.out.print("Words that appear " + maxCnt + " times:");
I appreciate all the help.
I have an Array
byte[] input = new byte[256];
I have map
Map<Byte, Integer> frequencyMap = new HashMap<>();
How can I put all elements in the order into lambda function?
I do like this, but is the other way to do it?
for (var b : input)
frequencyMap.merge(b, 1, (o1, o2) -> o2 = frequencyMap.get(b) + 1);
How can I do this with out cycle?
That's basically as good as it gets, except your merge function is overcomplicated. Instead, write
frequencyMap.merge(b, 1, Integer::sum);
(or use a Multiset from Guava)
You can get the required result using groupingBy and summingInt collectors.
Map<Byte, Integer> frequencyMap =
IntStream.range(0, input.length)
.mapToObj(i -> input[i])
.collect(Collectors.groupingBy(Function.identity(),
Collectors.summingInt(e -> 1)));
This means you don't need the external enhanced for loop iteration nor the Map::merge.