Split a stream if it contains multiple comma-separated values - java

This code will add all elements from mylist to mylist2.
How can I change it to add elements if it finds a comma, and separates them?
ArrayList<String> mylist = new ArrayList<String>();
mylist.add("test");
mylist.add("test2, test3");
ArrayList<String> mylist2 = new ArrayList<String>();
mylist.stream()
.forEach(s -> mylist2.add(s));
mylist2.stream().forEach(System.out::println);
So, current output is:
test
test2, test3
and I need the second arraylist to contain and then print:
test
test2
test3
I know in Java 8 there are filters. Could I do something like
.filter(p -> p.contains(",")...
then something to split it?

You could do a flatMap including a Pattern::splitAsStream like
Pattern splitAtComma = Pattern.compile("\\s*,\\s*");
List<String> myList2 = mylist.stream().flatMap(splitAtComma::splitAsStream)
.collect(Collectors.toList());

You could split each string and flatMap the resulting array:
mylist.stream()
.flatMap(x -> Arrays.stream(x.split(", ")))
.forEach(System.out::println);

You can use :
mylist.forEach(str -> mylist2.addAll(Arrays.asList(str.split("\\s*,\\s*"))));
Output
test
test2
test3

and I need the second arraylist to contain and then print:
You could create two streams : one for creating the target List and another one for displaying it.
List<String> list2 = mylist.stream()
.flatMap(x -> Arrays.stream(x.split(", ")))
.collect(Collectors.toList());
list2.stream().forEach(System.out::println);
or with one stream by using peak() to output the element of the Stream before collecting into a List :
List<String> list2 = mylist.stream()
.flatMap(x -> Arrays.stream(x.split(", ")))
.peek(System.out::println)
.collect(Collectors.toList());

Related

How to i compare two lists of different types in Java?

I have two lists:
The first list is a list of MyObject which contains an int and a String:
List<MyObject> myObjList = new ArrayList<>();
myObjList.add(new MyObject(1, "Frank"));
myObjList.add(new MyObject(2, "Bob"));
myObjList.add(new MyObject(3, "Nick"));
myObjList.add(new MyObject(4, "Brian"));
The second list is simply a list of strings:
List<String> personList = new ArrayList<>();
personList.add("Nick");
I want to compare the list of strings (personList) with string in the list of MyObject (myObjectList) and return a list of id's with all the matches. So in the examle it should return a list containing only Nicks id -> 3. How do I do that?
UPDATE:
To get a list of id's I simply said:
myObjectList.stream()
.filter(mo -> personList.contains(mo.id))
.map(MyObject::id)
.collect(Collectors.toList());
I'm not entirely clear which way round you want, but if you want the elements in personList which are ids of elements in myObjList:
personList.stream()
.filter(s -> myObjList.stream().anyMatch(mo -> mo.id.equals(s)))
.collect(Collectors.toList());
or, if you want the elements in myObjList whose ids are in personList:
myObjectList.stream()
.filter(mo -> personList.contains(mo.id))
.collect(Collectors.toList());
(In the latter case, it may be better for personList to be a Set<String>).

Any way to make this stream more efficient?

How would one optimise this without adding value into the new ArrayList instead just having the original list updated?
String filterval = filter.toLowerCase();
ArrayList<String> filtredArr = new ArrayList<String>();
listArray.forEach(
valueText -> {
String val = valueText.toLowerCase();
if (val.startsWith(filterval) || val.contains(filterval))
filtredArr.add(valueText);
else {
Arrays.stream(valueText.split(" ")).forEach(
singleWord -> {
String word = singleWord.toLowerCase();
if(word.startsWith(filterval) || word.contains(filterval))
filtredArr.add(valueText);
}
);
}
});
When using streams, it is best not to modify the source of the stream while the latter iterates over the former. (See this for instance.)
Regarding readability and making idiomatic use of stream operations, your code is almost indistinguishable from a plain old for loop (and I would advise you to change it to that if you only use streams to do a forEach, but you could modify it to use a chain of shorter and more "atomic" stream operations, as in the following examples.
To get the list of strings in which at least one word contains filterval:
List<String> filtered = listArray.stream()
.filter(str -> str.toLowerCase().contains(filterval))
.collect(Collectors.toList());
To get the list of strings in which at least one word starts with filterval:
List<String> filtered =
listArray.stream()
.filter(str -> Arrays.stream(str.split(" "))
.map(String::toLowerCase)
.anyMatch(word -> word.startsWith(filterval)))
.collect(Collectors.toList());
To get the list of words (in any of the strings) that contain the filter value:
List<String> filteredWords = listArray.stream()
.map(String::toLowerCase)
.flatMap(str -> Arrays.stream(str.split(" ")))
.filter(word -> word.contains(filterval))
.collect(Collectors.toList());
(I'm assuming listArray, which you don't show in your code snippet, is a List<String>.)
Notes
The condition val.startsWith(filterval) || val.contains(filterval) is completely equivalent to val.contains(filterval). The reason is, if a string starts with filterval, it must also mean that it contains it; one implies the other.
There's no need to compute the lowercase versions of single words since you've already lowercased the whole string (so any words within it will also be lowercase).
Instead of treating single words and space-separated strings separately, we can apply split to all of them and then "concatenate" the sequences of words by using filterMap.
Minimal, complete, verifiable example
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
String filterval = "ba";
List<String> listArray = List.of("foo", "BAR", "spam EGGS ham bAt", "xxbazz");
List<String> filtered1 = listArray.stream()
.filter(str -> str.toLowerCase().contains(filterval))
.collect(Collectors.toList());
List<String> filtered2 =
listArray.stream()
.filter(str -> Arrays.stream(str.split(" "))
.map(String::toLowerCase)
.anyMatch(word -> word.startsWith(filterval)))
.collect(Collectors.toList());
List<String> filtered3 = listArray.stream()
.map(String::toLowerCase)
.flatMap(str -> Arrays.stream(str.split(" ")))
.filter(word -> word.contains(filterval))
.collect(Collectors.toList());
System.out.println(Arrays.toString(filtered1.toArray()));
System.out.println(Arrays.toString(filtered2.toArray()));
System.out.println(Arrays.toString(filtered3.toArray()));
}
}
Output:
[BAR, spam EGGS ham bAt, xxbazz]
[BAR, spam EGGS ham bAt]
[bar, bat, xxbazz]

How to use the Optional.ofNullable and Stream

I am doing a test case to use Optional.ofNullable and Stream together, and I faced to ways to do the same, both have the same output.
List<String> list1 = List.of("abc","abc");
Optional.ofNullable(list1).stream().flatMap(List::stream).map(e -> e.concat("def")).collect(Collectors.toList());
List<String> list2 = List.of("abc","abc");
Stream<String> stream = Optional.ofNullable(list2).map(List::stream).orElseGet(Stream::empty);
stream.map(e-> e.concat("def")).collect(Collectors.toList());
My question is, why at the first example I use a flatMap and a map and at the second one I use two map's to do the same job. If I try to use two map's at the first example it doesn't work.
To understand what's happening it can help to look at all the return types:
First example:
List<String> list1 = List.of("abc","abc");
List<String> result =
Optional.ofNullable(list1) // Optional<List<String>>
.stream() // Stream<List<String>>
.flatMap(List::stream) // Stream<String>
.map(e -> e.concat("def")) // Stream<String>
.collect(Collectors.toList()); // List<String>
In this example you go straight from an Optional<List<String>> to a Stream<List<String>> and then use the methods of the latter interface. The methods used here are (in order):
Optional#ofNullable(T)
Optional#stream()
Stream#flatMap(Function)
Stream#map(Function)
Stream#collect(Collector)
Second example:
List<String> list2 = List.of("abc","abc");
List<String> result =
Optional.ofNullable(list2) // Optional<List<String>>
.map(List::stream) // Optional<Stream<String>>
.orElseGet(Stream::empty); // Stream<String>
.map(e-> e.concat("def")) // Stream<String>
.collect(Collectors.toList()); // List<String>
In this example you go from an Optional<List<String>> to an Optional<Stream<String>> (another optional) and then extract the Stream<String> via Optional#orElseGet(Supplier). Afterwards, you use the methods of the Stream interface. The methods used here are (in order):
Optional#ofNullable(T)
Optional#map(Function)
Optional#orElseGet(Supplier)
Stream#map(Function)
Stream#collect(Collector)

ArrayList iteration with Streams

I have list of lists. i need to extract items from these lists based on index and make it individual arraylist. I tried doing it by adding
List<List<String>> multilist = new ArrayList<>();
List<List<String>> totalRecords= totalRecordsList;
List<String> targetList = totalRecords.stream().filter(e ->
e.get(index)!=null).flatMap(List::stream) .collect(Collectors.toCollection(ArrayList::new));
multilist.add(targetList);
But still inside list of lists rather than storing as individual arraylist objects, it is combining all the items. Can you please correct wherever i am wrong.
Thanks
This operation:
.flatMap(List::stream)
flattens everything in the input lists into a stream.
If you just want to take the index-th element of each list, replace this with:
.map(e -> e.get(index))
Overall:
totalRecords.stream()
.filter(e -> e.get(index)!=null)
.map(e -> e.get(index))
.collect(Collectors.toCollection(ArrayList::new))
You can avoid repeating the get by reversing the filter and map:
totalRecords.stream()
.map(e -> e.get(index))
.filter(Object::nonNull)
.collect(Collectors.toCollection(ArrayList::new))

Transform foreach to Java 8 filter stream

I have the following code
Map<String, List<String>> map= new HashMap<>();
map.put("a", Arrays.asList("a1"));
map.put("b", Arrays.asList("a2"));
List<String> result = new ArrayList<>();
List<String> list = new ArrayList<>();
list.add("a");
list.add("c");
for (String names : list) {
if (!map.containsKey(names)) {
result.add(names);
}
}
And I tried to migrate it to Java 8. What am I doing wrong?
list.stream()
.filter(c -> !map.containsKey(Name))
.forEach(c -> result.add(c));
But my condition is not evaluated
First of all, it should be:
list.stream()
.filter(c-> !map.containsKey(c))
.forEach(c->result.add(c));
Second of all, it's better to use collect as the terminal Stream operation when you want to produce a List:
List<String> result =
list.stream()
.filter(c-> !map.containsKey(c))
.collect(Collectors.toList());
list.stream()
.filter(c -> !map.containsKey(c))
.collect(Collectors.toCollection(ArrayList::new));
It should be
list.stream().filter(c-> !map.containsKey(c)).forEach(result::add);
A better way is:
List<String> result = list.stream().filter(c -> !map.constainsKey(c)).collect(Collectors.toList());
Name is not defined anywhere in the scope. The name of the lambda parameter is c. You should use it.
collect(Collectors.toList()) should be used to remove the explicit declaration and make it more expressive.
List<String> result =
list.stream()
.filter(c -> !map.containsKey(c))
.collect(Collectors.toList());

Categories