How to use double colon in Stream.collect for HashMap? - java

I want to read a csv file into a hashmap by using the first column as a key, the second column as a value, and ignoring the third column.
I wrote the following code and it works. I would like to know how to rewrite the syntax with double colon "::".
I check the API docs, but most of examples are using List instead of Map.
I used a string to mock a csv file: "A,1,!","B,2,#","C,3,#","D,4,$","E,5,%"
Map<String,String> maps = Stream.of("A,1,!","B,2,#","C,3,#","D,4,$","E,5,%")
.collect(() -> new HashMap<String,String>(),
(map, line) -> {String x[] = line.split(","); map.put(x[0],x[1]);},
(map1, map2) -> map1.putAll(map2));
System.out.println(maps);
Thanks,
Ian

Personally I would do this:
Map<String, String> maps = Stream.of("A,1,!", "B,2,#", "C,3,#", "D,4,$", "E,5,%").
map(line -> line.split(",")).
collect(HashMap::new, (map, line) -> map.put(line[0], line[1]), HashMap::putAll);
i.e. separate out the logic into distinct stream transformation operations. Doing the map in the collect clouds the intent of the code.

I do not think you want to be using the concrete collect() with the supplier, accumulator and combiner.
You should rely more on higher level methods, this becomes then:
Map<String, String> map = Stream.of("A,1,!","B,2,#","C,3,#","D,4,$","E,5,%")
.map(line -> line.split(","))
.collect(Collectors.toMap(
array -> array[0],
array -> array[1]
));
Which does the following:
Create a Stream<String>.
Map it to a Stream<String[]>.
Collect the results via a Collectors.toMap which takes a key mapper and a value mapper as arguments.
Here I map the array to array[0] for the key.
Here I map the array to array[1] for the value.
Then to confirm it works I print:
map.forEach((k, v) -> System.out.println("Key = " + k + " / Value = " + v));
Which gives:
Key = A / Value = 1
Key = B / Value = 2
Key = C / Value = 3
Key = D / Value = 4
Key = E / Value = 5

Related

java 8 map.get multiple key values

I have following code where I want to get value for multiple keys which starts with same name:
for example contents_of_a1, contents_of_ab2, contents_of_abc3
Optional.ofNullable(((Map<?, ?>) fieldValue))
.filter(Objects::nonNull)
.map(coverages -> coverages.get("contents_of_%"))
.filter(Objects::nonNull)
.filter(LinkedHashMap.class::isInstance)
.map(LinkedHashMap.class::cast)
.map(contents -> contents.get("limit"))
.map(limit -> new BigDecimal(String.valueOf(limit)))
.orElse(new BigDecimal(number));
How can I pass contents_of%
I don't know the reasons behind the data structure and what you want to achieve.
However, it is not important as this can be easily reproduced.
Using of Optional is a good start, however, for iterating and processing multiple inputs, you need to use java-stream instead and then Optional inside of collecting (I assume you want Map<String, BigDecimal output, but it can be adjusted easily).
Also, note .filter(Objects::nonNull) is meaningless as Optional handles null internally and is never passed to the next method.
final Map<String, Map<?, ?>> fieldValue = Map.of(
"contents_of_a", new LinkedHashMap<>(Map.of("limit", "10")),
"contents_of_b", new HashMap<>(Map.of("limit", "11")), // Different
"contents_of_c", new LinkedHashMap<>(Map.of("amount", "12")), // No amount
"contents_of_d", new LinkedHashMap<>(Map.of("limit", "13")));
final List<String> contents = List.of(
"contents_of_a",
"contents_of_b",
"contents_of_c",
// d is missing, e is requested instead
"contents_of_e");
final int number = -1;
final Map<String, BigDecimal> resultMap = contents.stream()
.collect(Collectors.toMap(
Function.identity(), // key
content -> Optional.of(fieldValue) // value
.map(coverages -> fieldValue.get(content))
.filter(LinkedHashMap.class::isInstance)
// casting here to LinkedHashMap is not required
// unless its specific methods are to be used
// but we only get a value using Map#get
.map(map -> map.get("limit"))
.map(String::valueOf)
.map(BigDecimal::new)
// prefer this over orElse as Optional#orElseGet
// does not create an object if not required
.orElseGet(() -> new BigDecimal(number))));
// check out the output below the code
resultMap.forEach((k, v) -> System.out.println(k + " -> " + v));
Only the content for a is used as the remaining were either not an instance of LinkedHashMap, didn't contain a key limit or were not among requested contents.
contents_of_a -> 10
contents_of_b -> -1
contents_of_e -> -1
contents_of_c -> -1
If you want to filter a map for which key starting with "contents_of_", you can do this for Java 8:
Map<String, Object> filteredFieldValue = fieldValue.entrySet().stream().filter(e -> {
String k = e.getKey();
return Stream.of("contents_of_").anyMatch(k::startsWith);
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Return or Collect value while iterating via Hashmap using java8

Following is the traditional code to check some condition and update a variable.
HashMap<Integer,Integer> testMap= new HashMap<>();
int pair = 0;
for(Integer value: testMap.values()){
pair = pair+value/2;
}
How the same thing can be achieved using java8 streams or lambdas?
stream the Map values, transform them, then sum()
int pair = testMap.values().stream().mapToInt(i -> i / 2).sum();
To make it look a bit more like your original code, you can use a reduce() operation:
int pair = testMap.values()
.stream()
.reduce(0, (p, i) -> p + i / 2);
Basically this starts with the value 0 (the "identity") and then passes the result of applying the reduction function as input, along with the current value, to each value in turn.
P.S. program to the interface:
Map<Integer, Integer> testMap = new HashMap<>();

Java Stream collect - how to deduce type?

I have been given a stream of words, Stream<String> words, and a class Pair<String,Integer> which realizes a simple tuple for (someString, someInt) with getter and setter methods for both elements called getFirst,setFirst,getSecond,setSecond.
I am now supposed to box each word of the stream into a Pair (word, 1), and then use a Collector to somehow make the whole thing tell me how often each word is in the text. Now I've looked up a Collector that should let me do what I want to, and passed it as .collect(...) to the stream.
But the whole thing is looking so complex, and the type inference and deduction and wildcards that are floating around in that topic aren't making it any easier, so that I got now no clue, just what it is I've created.
I've tried deducing it from the API, and tried all the things I could come up with, but none of it seems to match:
words
.map(x -> new Pair<String,Integer>(x,1))
.collect(Collectors.groupingBy(
x -> x.getFirst(),
Collectors.reducing(
(a,b) -> new Pair<String,Integer>(a.getFirst(), a.getSecond() + b.getSecond())
)
));
Try using Collectors.toMap:
Collection<Pair<String, Integer>> values = words.collect(Collectors.toMap(
Function.identity(),
s -> new Pair<>(s, 1),
(a, b) -> {a.setSecond(a.getSecond() + b.getSecond()); return a;}
)).values();
It creates a map from your stream, using provided:
keyMapper - a mapping function to produce keys
valueMapper - a mapping function to produce values
mergeFunction - a merge function, used to resolve collisions between values associated with the same key
So it groups your Pairs by string value to a map, and then you just call .values() to get a collection of Pairs
The easiest (though not necessarily most efficient) solution would be to group to a map and then convert the entries to pairs:
List<Pair<String, Integer>> pairs = words
.collect(Collectors.groupingBy(x -> x, Collectors.summingInt(x -> 1)))
.entrySet()
.stream()
.map(e -> new Pair(e.getKey(), e.getValue()))
.collect(Collectors.toList());
I agree that entering the world of collectors can be a bit frightening at the beginning, particularly if you need to deal with generic type parameters.
There are many ways to solve your problem, both with and without streams.
With streams:
Map<String, Pair<String, Integer>> map = words.stream()
.collect(Collectors.toMap(
word -> word,
word -> new Pair<>(word, 1),
(o, n) -> {
o.setSecond(o.getSecond() + n.getSecond());
return o;
}));
Collection<Pair<String, Integer>> result = map.values();
Collectors.toMap works by transforming each element of the stream into the keys (this is the 1st argument word -> word, which means we leave the word as is, so that it will be the key of the map), and by transforming each element of the stream into the values (this is the 2nd argument word -> new Pair<>(word, 1), which means that we've found the word for the first time, so we're creating a new Pair instance for that word with a count of 1).
The 3rd argument is a merge function that is to be used to merge values when the 1st argument returns a key that already belongs to the map. As maps can't have more than one entry for the same key, we need a way to merge the value that is already in the map for that key, with the new value produced by the 2nd argument. In this case, o stands for the old value and n for the new value. The way I merge values is by summing the counts for the word and setting the new count in the Pair instance that corresponds to the old value. There's no need to create a new instance of Pair with the word and the new count, as it's safe to accumulate the count by mutating the old instance of Pair.
Without streams:
Map<String, Pair<String, Integer>> map = new HashMap<>();
words.forEach(word -> map.merge(
word,
new Pair<>(word, 1),
(o, n) -> {
o.setSecond(o.getSecond() + n.getSecond());
return o;
}));
Collection<Pair<String, Integer>> result = map.values();
This uses Map.merge and has similar semantics as the previous code.

Processing HashMap using Java 8 Stream API

I have a hash table in the form
Map<String, Map<String,Double>
I need to process it and create another one having the same structure.
Following a sample to explain the goal
INPUT HASH TABLE
----------------------------
| | 12/7/2000 5.0 |
| id 1 | 13/7/2000 4.5 |
| | 14/7/2000 3.4 |
...
| id N | .... |
OUTPUT HASH TABLE
| id 1 | 1/1/1800 max(5,4.5,3.4) |
... ...
In particular, the output must have the same keys (id1, ..., id n)
The inner hash table must have a fixed key (1/1/1800) and a processed value.
My current (not working) code:
output = input.entrySet()
.stream()
.collect(
Collectors.toMap(entry -> entry.getKey(),
entry -> Collectors.toMap(
e -> "1/1/2000",
e -> {
// Get input array
List<Object> list = entry.getValue().values().stream()
.collect(Collectors.toList());
DescriptiveStatistics stats = new DescriptiveStatistics();
// Remove the NaN values from the input array
list.forEach(v -> {
if(!new Double((double)v).isNaN())
stats.addValue((double)v);
});
double value = stats.max();
return value;
}));
Where is the issue?
Thanks
The issue is trying to call Collectors.toMap a second type inside the first Collectors.toMap. Collectors.toMap should be passed to a method that accepts a Collector.
Here's one way to achieve what you want:
Map<String, Map<String,Double>>
output = input.entrySet()
.stream()
.collect(Collectors.toMap(e -> e.getKey(),
e -> Collections.singletonMap (
"1/1/1800",
e.getValue()
.values()
.stream()
.filter (d->!Double.isNaN (d))
.mapToDouble (Double::doubleValue)
.max()
.orElse(0.0))));
Note that there's no need for a second Collectors.toMap. The inner Maps of your output have a single entry each, so you can use Collections.singletonMap to create them.
Your original code can be solved using Collections.singletonMap instead of Collectors.toMap
Map<String, Map<String,Double>> output = input.entrySet()
.stream()
.collect(
Collectors.toMap(entry -> entry.getKey(),
entry -> {
// Get input array
List<Object> list = entry.getValue().values().stream()
.collect(Collectors.toList());
DescriptiveStatistics stats = new DescriptiveStatistics();
// Remove the NaN values from the input array
list.forEach(v -> {
if(!new Double((double)v).isNaN())
stats.addValue((double)v);
});
double value = stats.max();
return Collections.singletonMap("1/1/2000", value);
}));
Or make the nested Collectors.toMap a part of an actual stream operation
Map<String, Map<String,Double>> output = input.entrySet()
.stream()
.collect(Collectors.toMap(entry -> entry.getKey(),
entry -> Stream.of(entry.getValue()).collect(Collectors.toMap(
e -> "1/1/2000",
e -> {
// Get input array
List<Object> list = e.values().stream()
.collect(Collectors.toList());
DescriptiveStatistics stats = new DescriptiveStatistics();
// Remove the NaN values from the input array
list.forEach(v -> {
if(!new Double((double)v).isNaN())
stats.addValue((double)v);
});
double value = stats.max();
return value;
}))));
though that’s quiet a baroque solution.
That said, you should be aware that there’s the standard DoubleSummaryStatistics making DescriptiveStatistics unnecessary, though, both are unnecessary if you only want to get the max value.
Further, List<Object> list = e.values().stream().collect(Collectors.toList()); could be simplified to List<Object> list = new ArrayList<>(e.values()); if a List is truly required, but here, Collection<Double> list = e.values(); would be sufficient, and typing the collection with Double instead of Object makes the subsequent type casts unnecessary.
Using these improvements for the first variant, you’ll get
Map<String, Map<String,Double>> output = input.entrySet()
.stream()
.collect(
Collectors.toMap(entry -> entry.getKey(),
entry -> {
Collection<Double> list = entry.getValue().values();
DoubleSummaryStatistics stats = new DoubleSummaryStatistics();
list.forEach(v -> {
if(!Double.isNaN(v)) stats.accept(v);
});
double value = stats.getMax();
return Collections.singletonMap("1/1/2000", value);
}));
But, as said, DoubleSummaryStatistics still is more than needed to get the maximum:
Map<String, Map<String,Double>> output = input.entrySet()
.stream()
.collect(Collectors.toMap(entry -> entry.getKey(),
entry -> {
double max = Double.NEGATIVE_INFINITY;
for(double d: entry.getValue().values())
if(d > max) max = d;
return Collections.singletonMap("1/1/2000", max);
}));
Note that double comparisons always evaluate to false if at least one value is NaN, so using the right operator, i.e. “value possibly NaN” > “current max never NaN”, we don’t need an extra conditional.
Now, you might replace the loop with a stream operation and you’ll end up at Eran’s solution. The choice is yours.

Java List<String> to Map<String, Integer> convertion

I'd like to convert a Map <String, Integer> from List<String> in java 8 something like this:
Map<String, Integer> namesMap = names.stream().collect(Collectors.toMap(name -> name, 0));
because I have a list of Strings, and I'd like to to create a Map, where the key is the string of the list, and the value is Integer (a zero).
My goal is, to counting the elements of String list (later in my code).
I know it is easy to convert it, in the "old" way;
Map<String,Integer> namesMap = new HasMap<>();
for(String str: names) {
map1.put(str, 0);
}
but I'm wondering there is a Java 8 solution as well.
As already noted, the parameters to Collectors.toMap have to be functions, so you have to change 0 to name -> 0 (you can use any other parameter name instead of name).
Note, however, that this will fail if there are duplicates in names, as that will result in duplicate keys in the resulting map. To fix this, you could pipe the stream through Stream.distinct first:
Map<String, Integer> namesMap = names.stream().distinct()
.collect(Collectors.toMap(s -> s, s -> 0));
Or don't initialize those defaults at all, and use getOrDefault or computeIfAbsent instead:
int x = namesMap.getOrDefault(someName, 0);
int y = namesMap.computeIfAbsent(someName, s -> 0);
Or, if you want to get the counts of the names, you can just use Collectors.groupingBy and Collectors.counting:
Map<String, Long> counts = names.stream().collect(
Collectors.groupingBy(s -> s, Collectors.counting()));
the toMap collector receives two mappers - one for the key and one for the value. The key mapper could just return the value from the list (i.e., either name -> name like you currently have, or just use the builtin Function.Identity). The value mapper should just return the hard-coded value of 0 for any key:
namesMap =
names.stream().collect(Collectors.toMap(Function.identity(), name -> 0));

Categories

Resources