Generate String Permutations from multiple Set values (Java 8 Streams)

荒凉一梦 提交于 2020-01-25 08:53:05

问题


I have two Sets - country and state. I want to create all possible permutations from both.

import java.util.*;
import java.util.stream.Collectors;

public class HelloWorld{

 public static void main(String []args){
    System.out.println("Hello World");

    Set<String> countryPermutations = new HashSet<>(Arrays.asList("United States of america", "USA"));
    Set<String> statePermutations = new HashSet<>(Arrays.asList("Texas", "TX"));

    Set<String> stateCountryPermutations = countryPermutations.stream()
        .flatMap(country -> statePermutations.stream()
                .flatMap(state -> Stream.of(state + country, country + state)))
        .collect(Collectors.toSet());

    Set<String> finalAliases = Optional.ofNullable(stateCountryPermutations)
            .map(Collection::stream).orElse(Stream.empty())
                .map(sc -> "houston " + sc)
                .collect(Collectors.toSet());
    System.out.println(stateCountryPermutationAliases);
 }
}

The state or country or both permutations can be null. I still want my code to function.

Requirements

  1. If state permutation is null, final output should be [Houston USA, Houston United States of America]

  2. If country permutations is null, final output should be [Houston TX, Houston Texas]

  3. If both are null, then no output

I'm changed my code to below

Set<String> stateCountryPermutations = 
   Optional.ofNullable(countryPermutations)
           .map(Collection::stream)
           .orElse(Stream.empty())
           .flatMap(country -> Optional.ofNullable(statePermutations)
                                       .map(Collection::stream)
                                       .orElse(Stream.empty())
                                       .flatMap(state -> Stream.of(state + country, country + state)))
           .collect(Collectors.toSet());

This satisfies 3. When either permutation is null, 1 & 2 are not satisfied. I get no aliases as response. How do I modify my code?


回答1:


The following code creates all combinations from any number of input sets, ignoring null/empty sets:

Stream<Collection<String>> inputs = Stream.of(Arrays.asList("United States of america", "USA"),
                                              Arrays.asList("Texas", "TX"), 
                                              Arrays.asList("Hello", "World"),
                                              null,
                                              new ArrayList<>());

Stream<Collection<List<String>>> listified = inputs.filter(Objects::nonNull)
                                                   .filter(input -> !input.isEmpty())
                                                   .map(l -> l.stream()
                                                              .map(o -> new ArrayList<>(Arrays.asList(o)))
                                                              .collect(Collectors.toList()));

Collection<List<String>> combinations = listified.reduce((input1, input2) -> {
    Collection<List<String>> merged = new ArrayList<>();
    input1.forEach(permutation1 -> input2.forEach(permutation2 -> {
        List<String> combination = new ArrayList<>();
        combination.addAll(permutation1);
        combination.addAll(permutation2);
        merged.add(combination);
    }));
    return merged;
}).orElse(new HashSet<>());

combinations.forEach(System.out::println);

Output:

[United States of america, Texas, Hello]
[United States of america, Texas, World]
[United States of america, TX, Hello]
[United States of america, TX, World]
[USA, Texas, Hello]
[USA, Texas, World]
[USA, TX, Hello]
[USA, TX, World]

Now you can use your mentioned helper method to create the permutations of each combination. This question shows how to generate all permutations of a list.




回答2:


To rephrase your question, as far as I understood, you have several collections of, let’s call them labels, and create the permutation of all non-null collections, producing an empty stream, if all are null.

This can be done with a straight-forward logic, stream over all collections, filter out the null elements, map them to Streams and reduce them to a single stream using a streamA.stream().flatMap(… -> streamB.map(combiner)) logic, except that streams cannot be used more than once. To solve this, we can implement it by applying the same logic to suppliers of streams. Another detail is that .map(combiner) should be a -> streamB.flatMap(b -> Stream.of(combine a and b, combine b and a)) in your case.

Stream.of(stateLabels, countryLabels) // stream over all collections
      .filter(Objects::nonNull)       // ignore all null elements
      .<Supplier<Stream<String>>>map(c -> c::stream) // map to a supplier of stream
      .reduce((s1,s2) -> // combine them using flatMap and creating a×b and b×a
          () -> s1.get().flatMap(x -> s2.get().flatMap(y -> Stream.of(x+" "+y, y+" "+x))))
      .orElse(Stream::empty) // use supplier of empty stream when all null
      .get() // get the resulting stream
      .map("houston "::concat) // combine all elements with "houston "
      .forEach(System.out::println);

To demonstrate with test cases:

// testcases
List<Collection<String>> countryLabelTestCases = Arrays.asList(
    Arrays.asList("United States of america", "USA"),
    null
);
List<Collection<String>> stateLabelTestCases = Arrays.asList(
    Arrays.asList("Texas", "TX"),
    null
);
for(Collection<String> countryLabels: countryLabelTestCases) {
    for(Collection<String> stateLabels: stateLabelTestCases) {
        // begin test case
        System.out.println(" *** "+(
            countryLabels==null? stateLabels==null? "both null": "countryLabels null":
                                 stateLabels==null? "stateLabels null": "neither null"
            )+":"
        );

        // actual operation:

        Stream.of(stateLabels, countryLabels)
              .filter(Objects::nonNull)
              .<Supplier<Stream<String>>>map(c -> c::stream)
              .reduce((s1,s2) -> () -> s1.get().flatMap(x ->
                                       s2.get().flatMap(y -> Stream.of(x+" "+y, y+" "+x))))
              .orElse(Stream::empty)
              .get()
              .map("houston "::concat)
              .forEach(System.out::println);

        // end of operation
        System.out.println();
    }
}
 *** neither null:
houston Texas United States of america
houston United States of america Texas
houston Texas USA
houston USA Texas
houston TX United States of america
houston United States of america TX
houston TX USA
houston USA TX

 *** stateLabels null:
houston United States of america
houston USA

 *** countryLabels null:
houston Texas
houston TX

 *** both null:

If you want to get the permutations as lists rather than strings, create this helper method

static <T> List<T> merge(List<T> a, List<T> b) {
    return Stream.concat(a.stream(), b.stream()).collect(Collectors.toList());
}

and change the stream operation to

Stream.of(stateLabels, countryLabels)
      .filter(Objects::nonNull)
      .<Supplier<Stream<List<String>>>>map(c ->
          () -> c.stream().map(Collections::singletonList))
      .reduce((s1,s2) -> () -> s1.get().flatMap(x ->
                               s2.get().flatMap(y -> Stream.of(merge(x,y), merge(y,x)))))
      .orElse(Stream::empty)
      .get()
      .map(list -> merge(Collections.singletonList("houston"), list))
      // proceed processing the List<String>s

Mind that for supporting more than the two collections, you only have to change Stream.of(stateLabels, countryLabels), inserting the other collections.



来源:https://stackoverflow.com/questions/47282057/generate-string-permutations-from-multiple-set-values-java-8-streams

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!