I am new to java8 stream & sorry about the stupid question . Here is my code which i am trying to create a map of id & value, but i am getting this error, not able to fix. Can anyone help me what is the alternative?
public static Map<Integer, String> findIdMaxValue(){
Map<Integer, Map<String, Integer>> attrIdAttrValueCountMap = new HashMap<>();
Map<Integer, String> attrIdMaxValueMap = new HashMap<>();
attrIdAttrValueCountMap.forEach((attrId, attrValueCountMap) -> {
attrValueCountMap.entrySet().stream().sorted(this::compareAttrValueCountEntry).findFirst().ifPresent(e -> {
attrIdMaxValueMap.put(attrId, e.getKey());
});
});
}
and sorting method
public static int compareAttrValueCountEntry(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) {
int diff = e1.getValue() - e2.getValue();
if (diff != 0) {
return -diff;
}
return e1.getKey().compareTo(e2.getKey());
}
I am getting this error
"Cannot use this in a static context"
There are several issues with your code. While this::compareAttrValueCountEntry would be easy to
fix by changing it to ContainingClassName::compareAttrValueCountEntry, this method is unnecessary
as there are several factory methods like Map.Entry.comparingByKey, Map.Entry.comparingByValue,
Comparator.reversed and Comparator.thenComparing, which can be combined to achieve the same goal
This guards you from the errors made within compareAttrValueCountEntry. It’s tempting to compare int
values by subtracting, but this is error prone as the difference between two int values doesn’t always
fit into the int range, so overflows can occur. Also, negating the result for reversing the order is
broken, as the value might be Integer.MIN_VALUE, which has no positive counterpart, hence, negating it
will overflow back to Integer.MIN_VALUE instead of changing the sign.
Instead of looping via forEach to add to another map, you may use a cleaner stream operation producing
the map and you can simplify sorted(…).findFirst() to min(…) which in not only shorter, but a
potentially cheaper operation.
Putting it together, we get
Map<Integer, String> attrIdMaxValueMap =
attrIdAttrValueCountMap.entrySet().stream()
.filter(e -> !e.getValue().isEmpty())
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().entrySet().stream()
.min(Map.Entry.<String, Integer>comparingByValue().reversed()
.thenComparing(Map.Entry.comparingByKey())).get().getKey()));
Note that I prepended a filter operation rejecting empty maps, which ensures that there will always be
a matching element, so there is no need to deal with ifPresent or such alike. Instead, Optional.get
can be called unconditionally.
Since this method is called findIdMaxValue, there might be a desire to reflect that by calling max
on the Stream instead of min, wich is only a matter of which comparator to reverse:
Map<Integer, String> attrIdMaxValueMap =
attrIdAttrValueCountMap.entrySet().stream()
.filter(e -> !e.getValue().isEmpty())
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().entrySet().stream()
.max(Map.Entry.<String, Integer>comparingByValue()
.thenComparing(Map.Entry.comparingByKey(Comparator.reverseOrder())))
.get().getKey()));
Unfortunately, such constructs hit the limitations of the type inference, which requires us to either,
use nested constructs (like Map.Entry.comparingByKey(Comparator.reverseOrder()) instead of
Map.Entry.comparingByKey().reversed()) or to insert explicit types, like with
Map.Entry.<String, Integer>comparingByValue(). In the second variant, reversing the second comparator,
we are hitting the litimation twice…
In this specific case, there might be a point in creating the comparator only once, keeping it in a variable and reuse it within the stream operation:
Comparator<Map.Entry<String, Integer>> valueOrMinKey
= Map.Entry.<String, Integer>comparingByValue()
.thenComparing(Map.Entry.comparingByKey(Comparator.reverseOrder()));
Map<Integer, String> attrIdMaxValueMap =
attrIdAttrValueCountMap.entrySet().stream()
.filter(e -> !e.getValue().isEmpty())
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().entrySet().stream().max(valueOrMinKey).get().getKey()));
Since the method compareAttrValueCountEntry is declared static,
replace the method reference
this::compareAttrValueCountEntry
with
<Yourclass>::compareAttrValueCountEntry
Related
I have List<City> cities. I need to convert cities into a map Map<String, Integer>, where the value (Integer) has to be auto-generated.
I tried this, but it seems not allowed to use counter like that because of atomic error. Ho to solve this task?
public Map<String, Integer> convertListToMap(List<City> cities) {
Integer counter=0;
return cities.stream().forEach(elem->tollFreeVehicles.put(elem.getName(), counter++));
}
Local variables that are allowed to be used in the lambda expressions needs to be final or effectively final.
Have a look at the Oracle's tutorial on lambdas. A short excerpt:
a lambda expression can only access local variables and parameters of
the enclosing block that are final or effectively final. In this
example, the variable z is effectively final; its value is never
changed after it's initialized.
You can construct a map from a list using indices of items in the list as values by utilizing IntStream.range().
Also note that forEach() doesn't return a value. In order to generate a map as a result of the execution of the stream pipeline that will be returned from the method, you need to use collect() as terminal operation.
Map<String, Integer> result =
IntStream.range(0, cities.size())
.boxed()
.collect(Collectors.toMap(i -> cities.get(i).getName(),
Function.identity()));
I have a Map<String, Integer>, which has some keys and values. I want to associate all keys with the values as the key's length.
I have been able to solve this in pure java and java-8, but somehow I don't think that appending a terminal operation at the end like .collect(Collectors.toList()); which is not required for me in my code.
My code: ( Java ) works fine
Map<String, Integer> nameLength = new HashMap<>();
nameLength.put("John", null);
nameLength.put("Antony", 6);
nameLength.put("Yassir", 6);
nameLength.put("Karein", 6);
nameLength.put("Smith", null);
nameLength.put("JackeyLent",null);
for(Entry<String, Integer> length: nameLength.entrySet()){
if(length.getValue() == null){
nameLength.put(length.getKey(),length.getKey().length());
}
}
Java-8 also works fine but the terminal operation is useless, how I avoid it without using .foreach().
nameLength.entrySet().stream().map(s->{
if(s.getValue() == null){
nameLength.put(s.getKey(),s.getKey().length());
}
return nameLength;
}).collect(Collectors.toList());
System.out.println(nameLength);
Any other way in which I can do the above logic in Java-8 and above??
If you're going to use streams then you should avoid side effects. Functional programming is all about pure operations where the output depends only on the input and functions have no side effects. In other words, create a new map instead of modifying the existing one.
If you do that you might as well just throw away the partially-filled-out map and recompute everything from scratch. Calling String.length() is cheap and it's not really worth the effort to figure out which values are null and which aren't. Recompute all the lengths.
Map<String, Integer> newMap = nameLength.keySet().stream()
.collect(Collectors.toMap(
name -> name,
name -> name.length()
));
On the other hand if you just want to patch up your current map streams don't really buy you anything. I'd just modify it in place without involving streams.
for (Map.Entry<String, Integer> entry: nameLength.entrySet()) {
if (entry.getValue() == null) {
entry.setValue(entry.getKey().length());
}
}
Or, as discussed above, you could simplify matters by replacing all of the lengths:
nameLength.replaceAll((name, __) -> name.length());
(__ signifies a variable that isn't used and so doesn't get a meaningful name.)
You almost there, just use the filter to identify the entries with null values and then use Collectors.toMap to collect them into Map with key length as value
Map<String, Integer> nameLengths = nameLength.entrySet()
.stream()
.filter(entry->entry.getValue()==null)
.collect(Collectors.toMap(Map.Entry::getKey, entry->entry.getKey().length()));
Or more simpler way you have that check in Collectors.toMap
Map<String, Integer> nameLengths = nameLength.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry->entry.getValue() == null ? entry.getKey().length() : entry.getValue()));
I want to convert a list to a map where the key is just a counter and it needs to adhere to the order of the list. I currently have this code:
private static Map<String, String> convertListToMap(final List<String> list) {
AtomicInteger counter = new AtomicInteger(0);
Map<String, String> map = list.stream().collect(Collectors.toMap((c) -> {
Integer integer = counter.incrementAndGet();
return integer.toString();
}, (c) -> c));
return map;
}
I have two questions:
In a simple console app test on my desktop, the counter is preserving the order of the list. Can we be sure the order will always be preserved when executed anywhere else?
Is there a better way to code this?
Try it this way.
static Map<String, String> convert(List<String> list) {
return IntStream.range(0, list.size()).boxed()
.collect(Collectors.toMap(n -> String.valueOf(n+1), list::get,
(a, b) -> a, LinkedHashMap::new));
}
Notes:
The Merge function (a, b) -> a is not really contributing to this.
The supplier of LinkedHashMap::new ensures order is retained. Unfortunately, there is not a Collector.toMap that permits a Supplier without the merge function.
Probably you can use IntStream to map index as key to value, and use LinkedHashMap for preserving order
IntStream.range(0, list.size())
.mapToObj(i -> new AbstractMap.SimpleEntry<>(String.valueOf(i+1), list.get(i)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> 1, LinkedHashMap::new));
Regarding your first question: As long as your number will not change, your order would be preserved. A better solution would be a LinkedList (https://docs.oracle.com/javase/7/docs/api/java/util/LinkedList.html) where entries are ordered by the sequence you add them (It may be easier, I do not know your application).
Regarding your second question: The AtomicInteger mainly advances due to better thread safety (Performance Difference of AtomicInteger vs Integer). If you are not performing any concurrent operations there should be no noticable difference. Therefore one could use a normal Integer.
This is a continuation of my previous question at Group, Sum byType then get diff using Java streams.
As suggested, I should post as a separate thread instead of updating the original one.
So with my previous set of question, I have achieved that, and now, with the continuation.
Background:
I have the following dataset
Sample(SampleId=1, SampleTypeId=1, SampleQuantity=5, SampleType=ADD),
Sample(SampleId=2, SampleTypeId=1, SampleQuantity=15, SampleType=ADD),
Sample(SampleId=3, SampleTypeId=1, SampleQuantity=25, SampleType=ADD),
Sample(SampleId=4, SampleTypeId=1, SampleQuantity=5, SampleType=SUBTRACT),
Sample(SampleId=5, SampleTypeId=1, SampleQuantity=25, SampleType=SUBTRACT)
Sample(SampleId=6, SampleTypeId=2, SampleQuantity=10, SampleType=ADD),
Sample(SampleId=7, SampleTypeId=2, SampleQuantity=20, SampleType=ADD),
Sample(SampleId=8, SampleTypeId=2, SampleQuantity=30, SampleType=ADD),
Sample(SampleId=9, SampleTypeId=2, SampleQuantity=15, SampleType=SUBTRACT),
Sample(SampleId=10, SampleTypeId=2, SampleQuantity=35, SampleType=SUBTRACT)
I am currently using this:
sampleList.stream()
.collect(Collectors.groupingBy(Sample::getTypeId,
Collectors.summingInt(
sample -> SampleType.ADD.equalsIgnoreCase(sample.getSampleType())
? sample.getSampleQuantity() :
-sample.getSampleQuantity()
)));
And also this
sampleList.stream()
.collect(Collectors.groupingBy(Sample::getSampleTypeId,
Collectors.collectingAndThen(
Collectors.groupingBy(Sample::getSampleType,
Collectors.summingInt(Sample::getSampleQuantity)),
map -> map.getOrDefault(SampleType.ADD, 0)
- map.getOrDefault(SampleType.SUBTRACT, 0))));
as the accepted answer to get the desired output to group in a Map<Long, Integer>:
{1=15, 2=10}
With that, I was wondering, if this could be expanded into something more.
First, how could I have it return as a Map<String, Integer> instead of the original Map<Long, Integer>. Basically, for the SampleTypeId; 1 refers to HELLO, 2 refers to WORLD.
So I would need like a .map (or maybe other function) to transform the data from 1 to HELLO and 2 to WORLD by calling a function say convertType(sampleTypeId)?. So the expected output would then be {"HELLO"=15, "WORLD"=10}. Is that right? How should I edit the current suggested solution to this?
Lastly, I would like to know if it is also possible to return it to a Object instead of a Map. So let's say I have a Object; SummaryResult with (String) name and (int) result. So it returns a List<SummaryResult> instead of the original Map<Long, Integer>. How can I use the .map (or other) feature to do this? Or is there other way to doing so? The expected output would be something along this line.
SummaryResult(name="hello", result=15),
SummaryResult(name="world", result=10),
Would really appreciate it with the explanation in steps as given previously by #M. Prokhorov.
Update:
After updating to
sampleList.stream()
.collect(Collectors.groupingBy(sample -> convertType(sample.getSampleTypeId()),
Collectors.collectingAndThen(
Collectors.groupingBy(Sample::getSampleType,
Collectors.summingInt(Sample::getSampleQuantity)),
map -> map.getOrDefault(SampleType.ADD, 0)
- map.getOrDefault(SampleType.SUBTRACT, 0))));
private String convertType(int id) {
return (id == 1) ? "HELLO" : "WORLD";
}
For first part, considering you have somewhere the method
String convertType(int typeId)
You simply need to change first classifier from this
groupingBy(SampleType::getTypeId)
to this
groupingBy(sample -> convertType(sample.getTypeId()))
Everything else remains the same.
Latter type is a little trickier, and technically doesn't benefit from it being a stream-related solution at all.
What you need is this:
public List<SummaryResult> toSummaryResultList(Map<String, Integer> resultMap) {
List<SummaryResult> list = new ArrayList<>(resultMap.size());
for (Map.Entry<String, Integer> entry : resultMap.entrySet()) {
String name = entry.getKey();
Integer value = entry.getValue();
// replace below with construction method you actually have
list.add(SummaryResult.withName(name).andResult(value));
}
return list;
}
You can use this as part of collector composition, where your whole collector will get wrapped into a collectingAndThen call:
collectingAndThen(
groupingBy(sample -> convertType(sample.getTypeId()),
collectingAndThen(
groupingBy(Sample::getSampleType,
summingInt(Sample::getSampleQuantity)),
map -> map.getOrDefault(SampleType.ADD, 0)
- map.getOrDefault(SampleType.SUBTRACT, 0))),
result -> toSummaryResultList(result))
However, as you can see, it is the whole collector that gets wrapped, so there is no real benefit in my eyes to the above version to a simpler and easier to follow (at least to me) version below that uses an intermediate variable, but isn't so much of a wall of code:
// do the whole collecting thing like before
Map<String, Integer> map = sampleList.stream()
.collect(Collectors.groupingBy(sample -> convertType(sample.getTypeId()),
Collectors.collectingAndThen(
Collectors.groupingBy(Sample::getSampleType,
Collectors.summingInt(Sample::getSampleQuantity)),
map -> map.getOrDefault(SampleType.ADD, 0)
- map.getOrDefault(SampleType.SUBTRACT, 0))));
// return the "beautified" result
return toSummaryResultList(map);
Another point to consider in above is: convertType method will be called as many times as there are elements in sampleList, so if convertType call is "heavy" (for example, uses database or IO), then it's better to call it as part of toSummaryResultList conversion, not as stream element classifier. In which case you will be collecting from map of type Map<Integer, Integer> still, and using convertType inside the loop. I will not add any code with this in consideration, as I view this change as trivial.
You could indeed use a map() function
sampleList.stream()
.collect(Collectors.groupingBy(Sample::getSampleTypeId,
Collectors.collectingAndThen(
Collectors.groupingBy(Sample::getSampleType,
Collectors.summingInt(Sample::getSampleQuantity)),
map -> map.getOrDefault(SampleType.ADD, 0)
- map.getOrDefault(SampleType.SUBTRACT, 0))))
.entrySet()
.stream()
.map(entry->new SummaryResult(entry.getKey()),entry.getValue())
.collect(Collectors.toList());
ToIntFunction<Sample> signedQuantityMapper= sample -> sample.getQuantity()
* (sample.getType() == Type.ADD ? 1 : -1);
Function<Sample, String> keyMapper = s -> Integer.toString(s.getTypeId());
Map<String, Integer> result = sampleList.stream().collect(
Collectors.groupingBy(
keyMapper,
Collectors.summingInt(signedQuantityMapper)));
I have a List<Item> collection.
I need to convert it into Map<Integer, Item>
The key of the map must be the index of the item in the collection.
I can not figure it out how to do this with streams.
Something like:
items.stream().collect(Collectors.toMap(...));
Any help?
As this question is identified as possible duplicate I need to add that my concrete problem was - how to get the position of the item in the list and put it as a key value
You can create a Stream of the indices using an IntStream and then convert them to a Map :
Map<Integer,Item> map =
IntStream.range(0,items.size())
.boxed()
.collect(Collectors.toMap (i -> i, i -> items.get(i)));
One more solution just for completeness is to use custom collector:
public static <T> Collector<T, ?, Map<Integer, T>> toMap() {
return Collector.of(HashMap::new, (map, t) -> map.put(map.size(), t),
(m1, m2) -> {
int s = m1.size();
m2.forEach((k, v) -> m1.put(k+s, v));
return m1;
});
}
Usage:
Map<Integer, Item> map = items.stream().collect(toMap());
This solution is parallel-friendly and does not depend on the source (you can use list without random access or Files.lines() or whatever).
Don't feel like you have to do everything in/with the stream. I would just do:
AtomicInteger index = new AtomicInteger();
items.stream().collect(Collectors.toMap(i -> index.getAndIncrement(), i -> i));
As long as you don't parallelise the stream this will work and it avoids potentially expensive and/or problematic (in the case of duplicates) get() and indexOf() operations.
(You cannot use a regular int variable in place of the AtomicInteger because variables used from outside a lambda expression must be effectively final. Note that when uncontested (as in this case), AtomicInteger is very fast and won't pose a performance problem. But if it worries you you can use a non-thread-safe counter.)
This is updated answer and has none of the problems mentioned in comments.
Map<Integer,Item> outputMap = IntStream.range(0,inputList.size()).boxed().collect(Collectors.toMap(Function.identity(), i->inputList.get(i)));
Using a third party library (protonpack for example, but there are others) you can zip the value with its index and voila:
StreamUtils.zipWithIndex(items.stream())
.collect(Collectors.toMap(Indexed::getIndex, Indexed::getValue));
although getIndex returns a long, so you may need to cast it using something similar to:
i -> Integer.valueOf((int) i.getIndex())
Eran's answer is usually the best approach for random-access lists.
If your List isn't random access, or if you have a Stream instead of a List, you can use forEachOrdered:
Stream<Item> stream = ... ;
Map<Integer, Item> map = new HashMap<>();
AtomicInteger index = new AtomicInteger();
stream.forEachOrdered(item -> map.put(index.getAndIncrement(), item));
This is safe, if the stream is parallel, even though the destination map is thread-unsafe and is operated upon as a side effect. The forEachOrdered guarantees that items are processed one-at-a-time, in order. For this reason it's unlikely that any speedup will result from running in parallel. (There might be some speedup if there are expensive operations in the pipeline before the forEachOrdered.)