Java, Collectors.toList() creates only one element - java

I am having troubles getting the following to work. The point of this task is to use streams:
Map<String, List<String>> descriptions = new HashMap<>();
wordArray[2] = "D1_E1^E2^E3^E4~D2_E1^E2";
if(wordArray[2].contains("~")) {
Arrays.stream(wordArray[2].split("~"))
.forEach(s -> descriptions.put(s.split("_")[0],
s.split("_")[1].contains("^")
? Arrays.stream(s.split("_")[1].split("^"))
.collect(Collectors.toList())
: Arrays.asList(s.split("_")[1])));
}
For some reason this Collectors.toList() creates only one String "E1^E2^E3^E4" instead of seperate Strings.
My guess is that this is where I'm wrong : Arrays.stream(s.split("_")[1].split("^")), but I don't know where exactly and how to fix it. More so because this works just fine and is quite similar to the one above:
List<String> synonyms = new ArrayList<>();
wordArray[3] = "S1_S2_S3_S4";
synonyms = wordArray[3].contains("_")
? Arrays.stream(wordArray[3].split("_")).
collect(Collectors.toList())
: Arrays.asList(wordArray[3]);
Thanks!

Not sure what your expected output should be, but if it's something like this
[D1, E1, E2, E3, E4, D2, E1, E2]
then one of the way you can do it with Java streams is:
import java.util.List;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
public class Parser {
public static void main(String[] args) {
String str = "D1_E1^E2^E3^E4~D2_E1^E2";
List<String> strings = Parser.parseString(str);
System.out.println(strings);
}
public static List<String> parseString(String str) {
return Stream.of(str)
.map(s -> s.replaceAll("[_^~]", "-").split("-"))
.flatMap(Stream::of)
.collect(toList());
}
}
From here you can factor out the regex and pass it into parseString as second argument to have more flexibility (if needed).

Related

Remove elements from hashset inside hashmap while iterating through java stream [duplicate]

This question already has answers here:
what does java8 stream map do here?
(4 answers)
Closed 7 months ago.
I have a hashmap in Java with a string key and a HashSet value. The hashset may contain many PlacementBundles inside it.
public Map<String, Set<PlacementBundle>> placementByConcept;
I am trying to remove the value from the HashSet while iterating the map which matches a specific condition.
I tried the below code but cannot remove the matching element from the HashSet.
placementByConcept.entrySet()
.stream()
.map(e -> e.getValue()
.removeIf(s -> s.getBeatObjectiveId().equals("non-scored")));
you can use forEach:
placementByConcept.entrySet().forEach(e -> e.getValue().removeIf(s -> s.getBeatObjectiveId().equals("non-scored")));
public class Remove {
public static void main(String[] args)
{
HashMap<Integer, String>
map = new HashMap<>();
map.put(1, "Stack");
map.put(2, "Overflow");
map.put(3, "StackOverflow");
int keyToBeRemoved = 2;
System.out.println("Original HashMap: "
+ map);
map.entrySet()
.removeIf(
entry -> (keyToBeRemoved == entry.getKey()));
System.out.println("New HashMap: "
+ map);
}
}
Output:
Original HashMap: {1=Stack, 2=Overflow, 3=StackOverflow}
New HashMap: {1=Stack, 3=StackOverflow}
In your case Set<PlacementBundle> is an immutable collection. You can't remove an element from it.
Thank you Holger for pointing out the assumption I made which may not be true for the asked question.
If Set is immutable collection and you use foreach as suggested in the accepted answer, you will get UnsupportedOperationException
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
#Slf4j
public class Test {
public static void main(String[] args) {
Map<String, Set<PlacementBundle>> placementByConcept = new HashMap<>();
placementByConcept.put("concept1", Set.of(
PlacementBundle.builder().beatObjectiveId("scored").build(),
PlacementBundle.builder().beatObjectiveId("non-scored").build())
);
placementByConcept.put("concept2", Set.of(
PlacementBundle.builder().beatObjectiveId("scored").build(),
PlacementBundle.builder().beatObjectiveId("non-scored").build())
);
log.info("Original: {}", placementByConcept);
/* This won't give any exception, neither will remove the entries */
placementByConcept.entrySet()
.stream()
.map(e -> e.getValue()
.removeIf(s -> s.getBeatObjectiveId().equals("non-scored")));
log.info("Does not work: {}", placementByConcept);
/* This will give you the exception UnsupportedOperationException */
// placementByConcept.entrySet().forEach(e -> e.getValue().removeIf(s -> s.getBeatObjectiveId().equals("non-scored")));
/* This is one of the correct way */
for (Map.Entry<String, Set<PlacementBundle>> entry : placementByConcept.entrySet()) {
var filtered = entry.getValue().stream()
.filter(placementBundle -> !placementBundle.getBeatObjectiveId().equals("non-scored"))
.collect(Collectors.toUnmodifiableSet());
log.debug("New Value Set: {}", filtered);
entry.setValue(filtered);
}
log.info("After: {}", placementByConcept);
}
}
#Builder
#Data
class PlacementBundle {
private String beatObjectiveId;
}
Output:
Original: {concept2=[PlacementBundle(beatObjectiveId=scored), PlacementBundle(beatObjectiveId=non-scored)], concept1=[PlacementBundle(beatObjectiveId=scored), PlacementBundle(beatObjectiveId=non-scored)]}
Does not work: {concept2=[PlacementBundle(beatObjectiveId=scored), PlacementBundle(beatObjectiveId=non-scored)], concept1=[PlacementBundle(beatObjectiveId=scored), PlacementBundle(beatObjectiveId=non-scored)]}
After: {concept2=[PlacementBundle(beatObjectiveId=scored)], concept1=[PlacementBundle(beatObjectiveId=scored)]}

Convert ArrayList<String> to Set<ScopeItem> with Java streams

I want to convert an ArrayList<String> to Set<ScopeItem> with Java streams.
ScopeItem is a enum;
items is an ArrayList<String>;
Set<ScopeItem> scopeItems = items.stream()
.map(scopeString -> ScopeItem.valueOf(scopeString))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
On a string that isn't in the enum this throws the following:
java.lang.IllegalArgumentException: No enum const...
Ideally, I would like to skip past any Strings that don't match.
I think maybe a using flatmap? Any ideas how to do it?
You could add the following method to your ScopeItem:
public static ScopeItem valueOfOrNull(String name) {
try {
return valueOf(name);
} catch (IllegalArgumentException e) {
// no such element
return null;
}
}
and use that to map your enum values:
.map(scopeString -> ScopeItem.valueOfOrNull(scopeString))
Subsequent .filter() on non-null values (which you already have) will filter-out those nulls that correspond to non-matching strings.
You can put a try-catch inside your map to return null instead of throwing an exception:
Set<ScopeItem> scopeItems = items.stream()
.map(scopeString ->
{
try
{
return ScopeItem.valueOf(scopeString);
}
catch (IllegalArgumentException e)
{
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toSet());
You could also use filter beforehand to check whether the array of values contains the string you're looking for:
Set<ScopeItem> scopeItems = items.stream()
.filter(scopeString -> Arrays.stream(ScopeItem.values())
.anyMatch(scopeItem -> scopeItem.name().equals(scopeString)))
.map(ScopeItem::valueOf)
.collect(Collectors.toSet());
Unlike others, I won't recommend using exceptions, as I feel they should be used for exceptional situations, and not for something that will likely to occur. A simple solution, is to have a static set of acceptable strings, and simply check, if a string you want to use valueOf with is in said set.
package test;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class Test {
public static enum ScopeItem {
ScopeA,
ScopeB;
private static Set<String> castableStrings;
static {
castableStrings = new HashSet<>();
for (ScopeItem i : ScopeItem.values()) {
castableStrings.add(i.name());
}
}
public static boolean acceptable(String s) {
return castableStrings.contains(s);
}
}
public static void main(String[] args) {
List<String> items = Arrays.asList("ScopeA", "RandomString", "ScopeB");
Set<ScopeItem> scopeItems = items.stream()
.filter(ScopeItem::acceptable)
.map(ScopeItem::valueOf)
.collect(Collectors.toSet());
System.out.println(scopeItems.size());
}
}
There's a more elegant approach here. You don't have to add any new fields to your enum; you can simply run a stream against it as well and determine if there's any matches in your collection.
The below code assumes an enum declaration of:
enum F {
A, B, C, D, E
}
and looks as thus:
List<String> bad = Arrays.asList("A", "a", "B", "b", "C", "c");
final Set<F> collect = bad.stream()
.filter(e -> Arrays.stream(F.values())
.map(F::name)
.anyMatch(m -> Objects.equals(e, m)))
.map(F::valueOf)
.collect(Collectors.toSet());
Two parts to pay attention to here:
We do the internal filter on the values of our enum, and map that to a String through F::name.
We determine if there's any match on the elements of our base collection with the elements of our enum in a null-safe way (Objects.equals does The Right Thing™ with nulls here)
You could use Apache Common's commons-lang3 EnumUtils.getEnum() instead of valueOf(). This returns null if there is no matching enum entry (which you can then filter exactly as you have in your code).
The easiest and clearest method would be to filter your Enum's values() method on items.contains():
Set<ScopeItem> enumVals = Arrays.stream(ScopeItem.values())
.filter(e -> items.contains(e.name()))
.collect(Collectors.toSet());
No added functionality just to get the stream to do what you want, and it is obvious what this does at a glance.

How to print a list by adding a new line after every 3rd element in a list in java lambda expression?

Suppose I have a list as below
Collection<?> mainList = new ArrayList<String>();
mainList=//some method call//
Currently, I am displaying the elements in the list as
System.out.println(mainList.stream().map(Object::toString).collect(Collectors.joining(",")).toString());
And I got the result as
a,b,c,d,e,f,g,h,i
How to print this list by adding a new line after every 3rd element in a list in java, so that it will print the result as below
a,b,c
d,e,f
g,h,i
Note: This is similar to How to Add newline after every 3rd element in arraylist in java?.But there formatting the file is done while reading itself.
I want to do it while printing the output.
If you want to stick to Java Stream API then your problem can be solved by partitioning initial list to sublists of size 3 and then representing each sublist as a String and joining results with \n.
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
final class PartitionListExample {
public static void main(String[] args) {
final Collection<String> mainList = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i");
final AtomicInteger idx = new AtomicInteger(0);
final int size = 3;
// Partition a list into list of lists size 3
final Collection<List<String>> rows = mainList.stream()
.collect(Collectors.groupingBy(
it -> idx.getAndIncrement() / size
))
.values();
// Write each row in the new line as a string
final String result = rows.stream()
.map(row -> String.join(",", row))
.collect(Collectors.joining("\n"));
System.out.println(result);
}
}
There are 3rd party libraries that provide utility classes that makes list partitioning easier (e.g. Guava or Apache Commons Collections) but this solution is built on Java 8 SDK only.
What it does is:
firstly we collect all elements by grouping by assigned row index and we store values as a list (e.g. {0=[a,b,c],1=[d,e,f],2=[g,h,i]}
then we take a list of all values like [[a,b,c],[d,e,f],[g,h,i]]
finally we represent list of lists as a String where each row is separated by \n
Output Demo
Running following program will print to console following output:
a,b,c
d,e,f
g,h,i
Getting more from following example
Alnitak played even more with following example and came up with a shorter solution by utilizing Collectors.joining(",") in .groupingBy collector and using String.join("\n", rows) in the end instead of triggering another stream reduction.
final Collection<String> rows = mainList.stream()
.collect(Collectors.groupingBy(
it -> idx.getAndIncrement() / size,
Collectors.joining(",")
))
.values();
// Write each row in the new line as a string
final String result = String.join("\n", rows);
System.out.println(result);
}
Final note
Keep in mind that this is not the most efficient way to print list of elements in your desired format. But partitioning list of any elements gives you flexibility if it comes to creating final result and is pretty easy to read and understand.
A side remark : in your actual code, map(Object::toString) could be removed if you replace
Collection<?> mainList = new ArrayList<String>(); by
Collection<String> mainList = new ArrayList<String>();.
If you manipulate Strings, create a Collection of String rather than Collection of ?.
But there formatting the file is done while reading itself.I want to
do it while printing the output.
After gotten the joined String, using replaceAll("(\\w*,\\w*,\\w*,)", "$1" + System.lineSeparator()) should do the job.
Iit will search and replace all series of 3 characters or more separated by a , character by the same thing ($1-> group capturing) but by concatenating it with a line separator.
Besides this :
String collect = mainList.stream().collect(Collectors.joining(","));
could be simplified by :
String collect = String.join(",", mainList);
Sample code :
public static void main(String[] args) {
Collection<String> mainList = Arrays.asList("a","b","c","d","e","f","g","h","i", "j");
String formattedValues = String.join(",", mainList).replaceAll("(\\w*,\\w*,\\w*,)", "$1" + System.lineSeparator());
System.out.println(formattedValues);
}
Output :
a,b,c,
d,e,f,
g,h,i,
j
Another approach that hasn't been answered here is to create a custom Collector.
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
public class PartitionListInPlace {
static class MyCollector implements Collector<String, List<List<String>>, String> {
private final List<List<String>> buckets;
private final int bucketSize;
public MyCollector(int numberOfBuckets, int bucketSize) {
this.bucketSize = bucketSize;
this.buckets = new ArrayList<>(numberOfBuckets);
for (int i = 0; i < numberOfBuckets; i++) {
buckets.add(new ArrayList<>(bucketSize));
}
}
#Override
public Supplier<List<List<String>>> supplier() {
return () -> this.buckets;
}
#Override
public BiConsumer<List<List<String>>, String> accumulator() {
return (buckets, element) -> buckets
.stream()
.filter(x -> x.size() < bucketSize)
.findFirst()
.orElseGet(() -> {
ArrayList<String> nextBucket = new ArrayList<>(bucketSize);
buckets.add(nextBucket);
return nextBucket;
})
.add(element);
}
#Override
public BinaryOperator<List<List<String>>> combiner() {
return (b1, b2) -> {
throw new UnsupportedOperationException();
};
}
#Override
public Function<List<List<String>>, String> finisher() {
return buckets -> buckets.stream()
.map(x -> x.stream()
.collect(Collectors.joining(", ")))
.collect(Collectors.joining(System.lineSeparator()));
}
#Override
public Set<Characteristics> characteristics() {
return new HashSet<>();
}
}
public static void main(String[] args) {
Collection<String> mainList = Arrays.asList("a","b","c","d","e","f","g","h","i", "j");
String formattedValues = mainList
.stream()
.collect(new MyCollector(mainList.size() / 3, 3));
System.out.println(formattedValues);
}
}
Explanation
This is a mutable collector that should not be used in parallel. If your necessities require that you process the stream in parallel you will have to transform this collector to be thread safe, which is pretty easy if you don't care about the order of the elements.
The combiner throws an exception because it is never called since run the stream sequentially.
The set of Characteristics has none that interests us, you can verify this by reading the javadoc
The supplier method will fetch the bucket in which we want to insert the element. The element will be insert in the next bucket that has space, otherwise we will create a new bucket and add it there.
The finisher is quite simple: Join the contents of each bucket by , and join the buckets themselves with System.lineSeparator()
Remember
Do not use this collector to process
Output
a, b, c
d, e, f
g, h, i
j

Java 8 extract first key from matching value in a Map

Suppose I have a map of given name, surname pairs and I want to find the given name of the first entry in that map that has the surname matching a certain value.
How would we do this in a java 8 fashion.
In my test case example below I put two ways that would do it.
However the first one (looking for the given name of the first person with a surname of "Donkey") will throw java.util.NoSuchElementException: No value present so it is not safe.
The second one works but it is not only harder to read but it it is a bit not quite functional.
Just wondering if someone here would suggest me an easier clearer way of achieving this using either stream() or forEach() or both.
#Test
public void shouldBeAbleToReturnTheKeyOfTheFirstMatchingValue() throws Exception {
Map<String, String> names = new LinkedHashMap<>();
names.put("John", "Doe");
names.put("Fred", "Flintstone");
names.put("Jane", "Doe");
String keyOfTheFirst = names.entrySet().stream().filter(e -> e.getValue().equals("Doe")).findFirst().get().getKey();
assertEquals("John", keyOfTheFirst);
try {
names.entrySet().stream().filter(e -> e.getValue().equals("Donkey")).findFirst().get();
} catch (NoSuchElementException e){
// Expected
}
Optional<Map.Entry<String, String>> optionalEntry = names.entrySet().stream().filter(e -> e.getValue().equals("Donkey")).findFirst();
keyOfTheFirst = optionalEntry.isPresent() ? optionalEntry.get().getKey() : null;
assertNull(keyOfTheFirst);
}
Thank you in advance.
To return a default value if there is no match, use Optional#orElse
names.entrySet().stream()
.filter(e -> e.getValue().equals("Donkey"))
.map(Map.Entry::getKey)
.findFirst()
.orElse(null);
From a similar question:
public static <T, E> Set<T> getKeysByValue(Map<T, E> map, E value) {
return map.entrySet()
.stream()
.filter(entry -> Objects.equals(entry.getValue(), value))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}
Then you can select the first, if you want to. Remember that the key is unique, the value is not.
Edit:
The whole code (thanks #Peter Lawrey)
package test;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Map<String, String> names = new LinkedHashMap<>();
names.put("John", "Doe");
names.put("Fred", "Flintstone");
names.put("Jane", "Doe");
Optional<String> firstKey = names.entrySet().stream()
.filter(entry -> Objects.equals(entry.getValue(), "Doe"))
.map(Map.Entry::getKey).findFirst();
if (firstKey.isPresent()) {
System.out.println(firstKey.get());
}
}
}
The solution provided by #Misha is the best one if you don't want to use the third-party code. My library has the special shortcut method ofKeys for such cases as I discovered that it's quite common task:
StreamEx.ofKeys(names, "Donkey"::equals).findFirst().orElse(null);
I like old fashioned:
static <K, V> K findFirstKeyByValue(Map<K, V> map, String value) {
for (Entry<K, V> e : map.entrySet())
if (e.getValue().equals(value))
return e.getKey();
return null;
}
Below is my code snippet to get key from map,
Map<String,String> pageDetails = new HashMap<String,String>();
public String getAssociatedKey(){
pageDetails.entrySet().stream().filter( e -> e.getValue().contains("John").findFirst().get().getKey();
}
In order to avoid null pointer exception if map entry exist with null value:
private String getValueByKey(Map<String, String> map, String key)
{
return map.entrySet().stream().filter(e ->
StringUtils.equalsIgnoreCase(e.getKey(), key)).findFirst().get()
.getValue();
}

How to map two arrays to one HashMap in Java?

I have two String arrays. One having short name.
// days short name
String[] shortNames = {"SUN", "MON", "...", "SAT"};
The other having long name.
// days long name
String[] longNames = {"SUNDAY", "MONDAY", "....", "SATURDAY"};
Both having same number of elements. How can I map short name as KEY and long name as VALUE in HashMap?
HashMap<String, String> days = new HashMap<>();
I know, I can make by looping. Is there a better way?
There are lots of ways you can do this. One that is fairly easy to understand and apply is using Java 8 streams and collectors to map from a stream of indices to key value pairs:
Map<String, String> days = IntStream.range(0, shortNames.length).boxed()
.collect(Collectors.toMap(i -> shortNames[i], i -> longNames[i]));
There are some third party Java libraries that include a 'zip' function to take two streams and produce a map from one to the other. But really they are just neater ways of achieving the same thing as the code above.
The accepted answer did not work for me, as the IntStream does not provide a one-argument collect method.
To nevertheless benefit from the toMap collector you have to box the int primitives into Integer objects first. If you like to preserve the element order, use the extended version of toMap together with LinkedHashMap::new like shown below:
package learning.java8;
import static java.util.stream.Collectors.*;
import static org.junit.Assert.*;
import java.time.DayOfWeek;
import java.time.format.TextStyle;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.stream.IntStream;
import org.junit.Test;
public class IntStreamLT {
#Test
public void q30339679() {
final String[] shortNames = getDayOfWeekNamesInEnglish(TextStyle.SHORT);
final String[] longNames = getDayOfWeekNamesInEnglish(TextStyle.FULL);
final Map<String, String> days = IntStream.range(0, shortNames.length).boxed()
.collect(toMap(i -> shortNames[i], i -> longNames[i]));
System.out.println(days);
final Map<String, String> sorted = IntStream.range(0, shortNames.length).boxed()
.collect(toMap(
i -> shortNames[i], i -> longNames[i],
(i, j) -> i, LinkedHashMap::new));
System.out.println(sorted);
assertEquals("{Mon=Monday, Tue=Tuesday, Wed=Wednesday, Thu=Thursday, "
+ "Fri=Friday, Sat=Saturday, Sun=Sunday}", sorted.toString());
}
private static String[] getDayOfWeekNamesInEnglish(final TextStyle style) {
return Arrays.stream(DayOfWeek.values())
.map(day -> day.getDisplayName(style, Locale.ENGLISH))
.toArray(String[]::new);
}
}
see also: Why don't primitive Stream have collect(Collector)?
You can use org.apache.commons.lang3.ArrayUtils.
Here is an example:
Map colorMap = ArrayUtils.toMap(new String[][] {
{"RED", "#FF0000"},
{"GREEN", "#00FF00"},
{"BLUE", "#0000FF"}});

Categories