How to use lambda with array Java - java

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.

Related

Create a map of maps with counts from list

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);

How do I get key based on some specific condition from values in a HashMap in Java?

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);

Collect to map the order/position value of a sorted stream

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.

Creating Map composed of 2 Lists using stream().collect in Java

As for example, there are two lists:
List<Double> list1 = Arrays.asList(1.0, 2.0);
List<String> list2 = Arrays.asList("one_point_zero", "two_point_zero");
Using Stream, I want to create a map composed of these lists, where list1 is for keys and list2 is for values. To do it, I need to create an auxiliary list:
List<Integer> list0 = Arrays.asList(0, 1);
Here is the map:
Map<Double, String> map2 = list0.stream()
.collect(Collectors.toMap(list1::get, list2::get));
list0 is used in order list1::get and list2::get to work. Is there a simpler way without creation of list0? I tried the following code, but it didn't work:
Map<Double, String> map2 = IntStream
.iterate(0, e -> e + 1)
.limit(list1.size())
.collect(Collectors.toMap(list1::get, list2::get));
Instead of using an auxiliary list to hold the indices, you can have them generated by an IntStream.
Map<Double, String> map = IntStream.range(0, list1.size())
.boxed()
.collect(Collectors.toMap(i -> list1.get(i), i -> list2.get(i)));
Indeed the best approach is to use IntStream.range(startInclusive, endExclusive) in order to access to each element of both lists with get(index) and finally use Math.min(a, b) to avoid getting IndexOutOfBoundsException if the lists are not of the exact same size, so the final code would be:
Map<Double, String> map2 = IntStream.range(0, Math.min(list1.size(), list2.size()))
.boxed()
.collect(Collectors.toMap(list1::get, list2::get));
This works for me but is O(n^2):
Map<Double, String> collect =
list1.stream()
.collect(
toMap(Double::doubleValue,
item -> list2.get(list1.indexOf(item))));

Count int occurrences with Java8

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()

Categories