Best way to replace nested loop concatenate string using java stream - java

I have a list of names and a list of versions. I want to get all permutations which are constructed by concatenating the string from two lists. I am using two for loop to do this but I want to switch to a more functional style approach. Here is my solution:
List<String> names = new ArrayList<>();
List<String> versions = new ArrayList<>();
List<String> result = new ArrayList<>();
names.forEach(name -> versions.stream().map(version -> result.add(name.concat(version))));
Is there a better way to do it?

You are looking for the "Cartesian Product" of names and versions — basically the return set/list from the aforementioned sets/lists.
final Stream<List<String>> result = names.stream()
.flatMap(s1 -> versions.stream().flatMap(s2 -> Stream.of(Arrays.asList(s1, s2))));
result.forEach(System.out::println);
Keep in mind that operation is super expensive. Google's Guava have this implemented also under com.google.common.collect.Sets.cartesianProduct(s1, s2).

You should look forward to use flatMap while streaming over names and then performing map operation further correctly as:
List<String> result = names.stream() // for each name
.flatMap(name -> versions.stream() // for each version
.map(version -> name.concat(version))) // concat version to the name
.collect(Collectors.toList()); // collect all such names

Or a bit tidier:
final List<String> result = names.stream() // Stream the Names...
.flatMap(name -> versions.stream() // ...together with Versions.
.map (version -> name.concat(version))) // Combine Name+Version
.collect(Collectors.toList()); // & collect in List.

Related

How to filter a list of String down to a distinct list made of string suffices in Java 8 Streams

I am new to Java streams but need to master by practice really!
The collection input is made up of strings e.g. [name][dot][country], example as follows:
JAMES.BRITAIN
JOHN.BRITAIN
LEE.BRITAIN
GEORGE.FRANCE
LEON.FRANCE
MARSELLE.FRANCE
KOFI.GHANA
CHARLIE.GHANA
Please, how do I return a list of unique countries in a single stream statement?
Expected result will be a distinct list as follows:
BRITAIN
FRANCE
GHANA
In the real code the streams statement below gives me the list to be filtered i.e.:
List<String> allSolrCollections = (List<String>) findAllCollections()
.getJsonArray(SOLR_CLOUD_COLLECTION)
.getList()
.stream()
.map(object -> Objects.toString(object, null))
.collect(Collectors.toList());
for the first part of problem, you need to convert each entry to corresponding country. so, you could use String.split function and keep the country part.
for the second part you could take advantage of the Stream.distinct function that will remove duplicates from incoming stream.
finally, this should work:
List<String> res = list.stream()
.map(s -> s.split("\\.")[1])
.distinct()
.collect(Collectors.toList());
Alternative solution
You can use the advantage of the method Pattern#splitAsStream(CharSerquence). Once you split each line into a new Stream, skip the first item, flatMap the result into a new Stream and produce a Set.
final Pattern pattern = Pattern.compile("\\.");
final Set<String> result = list.stream()
.flatMap(string -> pattern.splitAsStream(string).skip(1))
.collect(Collectors.toSet());
[GHANA, FRANCE, BRITAIN]
If you want to be careful about format it would be worth using a regular expression. This also makes the meaning of the code clearer to a reader.
Pattern inputPattern = Pattern.compile("- (?<name>[A-Z]+)\\.(?<country>[A-Z]+)");
list.stream()
.map(inputPattern::match)
.filter(Matcher::matches)
.map(m -> m.group("country"))
.distinct()
.toList();
This ignores lines that don't match the expected format.

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)

How to use java stream map inside java stream filter

I have 2 arrays and want to make a list of role.getRoleName() only with elements that are in both arrays using streams.
final List<String> roleNames = new ArrayList<>();
roleNames = Arrays.stream(roles).filter(role -> role.getRoleId()
== Arrays.stream(permissions).map(permission -> permission.getRoleId()));
when I write the above code I get
Operator '==' cannot be applied to 'int', 'java.util.stream.Stream'
I understand the error, but I don't know the solution of how to make the permissions stream in only permission.getRoleId integers.
There is no way to compare such incompatible types as int and Stream.
Judging from what you've shown, Stream#anyMatch might a good candidate.
roleNames = Arrays.stream(roles)
.map(Role::getRoleId)
.filter(id -> Arrays.stream(permissions).map(Role::getRoleId).anyMatch(p -> p.equals(id)))
.collect(Collectors.toList());
This part Arrays.stream(permissions).map(Role::getRoleId) may be pre-calculated and stored into a Set.
final Set<Integer> set = Arrays.stream(permissions)
.map(Role::getRoleId)
.collect(Collectors.toSet());
roleNames = Arrays.stream(roles)
.filter(role -> set.contains(role.getRoleId()))
.map(Role::getRoleName)
.collect(Collectors.toList());
What you can do is collect unique roleIds for the array of Permissions into a Set as a computed result and perform a contains check as you iterate through the array of Roles. This could be done as :
final Set<Integer> uniqueRoleForPermissions = Arrays.stream(permissions)
.map(Permission::getRoleId)
.collect(Collectors.toSet());
final List<String> roleNames = Arrays.stream(roles)
.filter(role -> uniqueRoleForPermissions.contains(role.getRoleId()))
.map(Role::getRoleName)
.collect(Collectors.toList());

Java 8 Stream filter using list as condition

Hello I'm beginner when it comes to Java 8 so please be patient for me :)
I have a method that returns custom list of objects. What I need to do: I have got a list of disabledPaymentTypesStrings - and I don't know how many elements it has got. How can I change my code in order to not write every condition like !paymentType.getName().equalsIgnoreCase(disabledPaymentTypesStrings.get(1))? I would like to have somehow my whole list "disabledPaymentTypesStrings" placed here as a condition but I have no idea how to do that. Please give me some hints or advices :)
private List<PaymentType> listOfPaymentTypesForChangePayment(OrderPaymentTypeParameters paymentTypeParameters) {
List<String> disabledPaymentTypesStrings = newArrayList(Splitter.on(COMMA).split(systemUtils.getChangePaymentTypeDisabled()));
return paymentTypeSelector.availablePaymentTypesForChangePayment(paymentTypeParameters).stream()
.filter(paymentType ->
!paymentType.getName().equalsIgnoreCase(disabledPaymentTypesStrings.get(0)) &&
!paymentType.getName().equalsIgnoreCase(disabledPaymentTypesStrings.get(1)) &&
!paymentType.getName().equalsIgnoreCase(disabledPaymentTypesStrings.get(2)))
.collect(toList());
}
A stream approach could consist in the filter() to stream the List of String and keep PaymentType elements where paymentType.getName() don't match with any elements of the List of String :
return paymentTypeSelector.availablePaymentTypesForChangePayment(paymentTypeParameters)
.stream()
.filter(paymentType -> disabledPaymentTypesStrings.stream()
.allMatch(ref -> !ref.equalsIgnoreCase(paymentType.getName())))
.collect(toList());
But you could also compare Strings by using the same case. For example lowercase. It will simplify the filtering.
You can convert the reference list elements to lowercase :
List<String> disabledPaymentTypesStrings = newArrayList(Splitter.on(COMMA).split(systemUtils.getChangePaymentTypeDisabled()))
.stream()
.map(String::toLowerCase)
.collect(toList());
And you can so use List.contains() in the filter() :
return paymentTypeSelector.availablePaymentTypesForChangePayment(paymentTypeParameters)
.stream()
.filter(paymentType -> !disabledPaymentTypesStrings.contains(paymentType.getName().toLowerCase()))
.collect(toList());
Note that for big lists, using a Set would be more efficient.
Use contains(). But you have to think about case sensitivity ignoring
private List<PaymentType> listOfPaymentTypesForChangePayment(OrderPaymentTypeParameters paymentTypeParameters) {
List<String> disabledPaymentTypesStrings = newArrayList(Splitter.on(COMMA).split(systemUtils.getChangePaymentTypeDisabled()));
return paymentTypeSelector.availablePaymentTypesForChangePayment(paymentTypeParameters).stream()
.filter(paymentType -> !disabledPaymentTypesStrings.contains(paymentType)
.collect(toList());
}
Both the steps need to have values in common case(either in uppercase or in lowercase, I preferred lowercase)
List<String> disabledPaymentTypesStringsLowerCase = newArrayList(Splitter.on(COMMA).split(systemUtils.getChangePaymentTypeDisabled()))
.stream()
.map(String::toLowerCase)
.collect(toList());
return paymentTypeSelector.availablePaymentTypesForChangePayment(paymentTypeParameters)
.stream()
.map(paymentType -> paymentType.getName())
.map(String::toLowerCase)
.filter(disabledPaymentTypesStrings::contains)
.collect(toList());
This code can further be refactored if paymentType class is known, assuming class of paymentType is PaymentType code would look like below,
return paymentTypeSelector.availablePaymentTypesForChangePayment(paymentTypeParameters)
.stream()
.map(PaymentType::getName)
.map(String::toLowerCase)
.filter(disabledPaymentTypesStrings::contains)
.collect(toList());

Map to List after filtering on Map's key using Java8 stream

I have a Map<String, List<String>>. I want to transform this map to a List after filtering on the map's key.
Example:
Map<String, List<String>> words = new HashMap<>();
List<String> aList = new ArrayList<>();
aList.add("Apple");
aList.add("Abacus");
List<String> bList = new ArrayList<>();
bList.add("Bus");
bList.add("Blue");
words.put("A", aList);
words.put("B", bList);
Given a key, say, "B"
Expected Output: ["Bus", "Blue"]
This is what I am trying:
List<String> wordsForGivenAlphabet = words.entrySet().stream()
.filter(x-> x.getKey().equalsIgnoreCase(inputAlphabet))
.map(x->x.getValue())
.collect(Collectors.toList());
I am getting an error. Can someone provide me with a way to do it in Java8?
Your sniplet wil produce a List<List<String>> not List<String>.
You are missing flatMap , that will convert stream of lists into a single stream, so basically flattens your stream:
List<String> wordsForGivenAlphabet = words.entrySet().stream()
.filter(x-> x.getKey().equalsIgnoreCase(inputAlphabet))
.map(Map.Entry::getValue)
.flatMap(List::stream)
.collect(Collectors.toList());
You can also add distinct(), if you don't want values to repeat.
Federico is right in his comment, if all you want is to get the values of a certain key (inside a List) why don't you simply do a get (assuming all your keys are uppercase letters already) ?
List<String> values = words.get(inputAlphabet.toUpperCase());
If on the other hand this is just to understand how stream operations work, there is one more way to do it (via java-9 Collectors.flatMapping)
List<String> words2 = words.entrySet().stream()
.collect(Collectors.filtering(x -> x.getKey().equalsIgnoreCase(inputAlphabet),
Collectors.flatMapping(x -> x.getValue().stream(),
Collectors.toList())));
As was previously told after collect you will get List<List<String>> with only one or zero value in it. You can use findFirst instead of collect it will return you Optional<List<String>>.

Categories