Existing state to JAVA stream 8

How can I convert the condition below to Java 8 stream?

    List<String> name = Arrays.asList("A", "B", "C");
    String id;
    if(name.contains("A")){
        id = "123";
    }else if(name.contains("B")){
        id = "234";
    }else if(name.contains("C")){
        id = "345";
    }

      

I am learning Streams and wondering how I can convert this one. I tried using foreach, map, filter, but it didn't get from it

+3


source to share


4 answers


Another (but compact) solution:



Arrays.asList("B", "C", "A", "D").stream()
        .map(s -> s.equals("A") ? new SimpleEntry<>(1, "123")
                : s.equals("B") ? new SimpleEntry<>(2, "234")
                : s.equals("C") ? new SimpleEntry<>(3, "345")
                : null)
        .filter(x -> x != null)
        .reduce((a, b) -> a.getKey() < b.getKey() ? a : b)
        .map(Entry::getValue)
        .ifPresent(System.out::println);

      

+3


source


I don't understand why you need to convert it to a stream. It doesn't look like API thread to me. But if you want to easily add new elements and make your code more readable, I can suggest you use a map instead.

private static final ImmutableMap<String, String> nameToId = new ImmutableMap.Builder<String, String>()
    .put("A", "123")
    .put("B", "234")
    .put("C", "345")
    .build();

      

Now you can add new elements without changing a lot of code and just call nameToId.get(name)

to get the id by name.



You can add more flexibility here by using streams

Stream.of("A", "B", "C").map(nameToId::get)collect(Collectors.toList());

      

+2


source


The problem you are describing is getting one value (id) from a function application to two input sets: input values ​​and mappings.

id = f(list,mappings)

      

So basically your question is: find f based on streams (in other words, solutions that return a list don't solve your problem).

First of all, the original if-else-if-else construct confuses three problems:

  • confirmation of entry (only considering the value set by "A", "B", "C")
  • mapping from input value to output value ("A" → "123", "B" → "234", "C" → "345")
  • defining implicit prioritization of input values ​​according to their natural order (not sure if this is intentional or coincidental), "A" before "B" before "C"

If you want to apply this to a stream of input, you must make all of them explicit:

  • Filter function that ignores all input values ​​without display
  • Mapper function that maps input to id
  • The decrease function (BinaryOperator) performs the prioritization logic implicit in the if-else-if-else construct

Display function

A transformer is a discrete function that maps input values ​​to a singleton stream of output values:

Function<String,Optional<String>> idMapper = s -> {
        if("A".equals(s)){
            return Optional.of("123");
        } else if("B".equals(s)){
            return Optional.of("234");
        } else if("C".equals(s)){
            return Optional.of("345");
        }
        return Optional.empty();
    } ;

      

For more mappings, an immutable mapping should be used:

Map<String,String> mapping = Collections.unmodifiableMap(new HashMap<String,String>(){{
        put("A", "123");
        put("B", "234");
        put("C", "345");
    }}); //the instance initializer is just one way to initialize the map :)

Function<String,Optional<String>> idMapper = s -> Optional.ofNullable(mapping.get(s));

      

Filter function

Since we only allow input values ​​for which we have a mapping, we could use a set of keys to map:

Predicate<String> filter = s -> mapping.containsKey(s);

      

Reduction function

To find a thread's top priority element using their natural order, use this BinaryOperator:

BinaryOperator<String> prioritizer = (a, b) -> a.compareTo(b) < 0 ? a : b;

      

If there is more logic to prioritize, you need to adapt the implementation accordingly.

This operator is used in the call .reduce()

. If you prioritize based on natural ordering, you can use .min(Comparator.naturalOrder())

in a stream.

Since natur

Pipeline through pipelines

Now you first need to flatten the stream to a single value using priority, the result will be Optional, which you use flatMap by applying idMapper function (flatMap does not end with option>

Optional<String> id = Arrays.asList("C", "B", "A")
                            .stream()
                            .filter(filter) //concern: input validation
                            .reduce(prioritizer) //concern: prioritization
                            .flatMap(idMapper); //concern: id-mapping

      

Final result

To complete it, for your specific problem, the most concise version (without defining functions) using flow and input validation would be:

//define the mapping in an immutable map (that just one way to do it)
final Map<String,String> mapping = Collections.unmodifiableMap(
  new HashMap<String,String>(){{
      put("A", "123");
      put("B", "234");
      put("C", "345");
  }});

Optional<String> result = Arrays.asList("C", "D", "A", "B")
                                .stream()
                                .filter(mapping::containsKey) 
                                .min(Comparator.naturalOrder()) 
                                .flatMap(s -> Optional.ofNullable(mapping.get(s))); 

      

which is the desired f:

BiFunction<List<String>,Map<String,String>,Optional<String>> f = 
   (list,map) -> list.stream()
                     .filter(map::containsKey) 
                     .min(Comparator.naturalOrder()) 
                     .flatMap(s -> Optional.ofNullable(mapping.get(s))); 

      

There is of course some call for this approach, but the elegance and simplicity of the if-else approach cannot be ruled out;)

But for the sake of completeness, let's take a look at the complexity. Assuming the number of mappings and the number of input values ​​are quite large (it wouldn't matter otherwise).

Solutions based on iterating over a map and searching using contains (as in your if-else construct):

  • Best-case: o (1) (first branch in the if-else construction, first element in the list)
  • Worst case: O (n ^ 2) (last branch in if-else, last item in list)

For streaming downsizing solution, you need to iterate over the input list completely (O (n)), and map lookup is O (1)

  • Best option: o (n)
  • Worst case: O (n)

thanks to Hamlezz for the shortening idea and Holger's pointing out that applying the mapping function directly to the stream does not give the same result (since the first match wins, not the first entry in the if-else construct) and min (Comparator.naturalOrder () option).

+2


source


Inspired by Sergey Bishir's answer, to use a card, I also used a card (but ordered) and I would rather look at the card keys rather than a list to find the corresponding id. This is certainly not the best solution, but you can play with Stream

this; -)

Map<String, String> nameToId = new LinkedHashMap<>();
// the following order reflects the order of your conditions! (if your first condition would contain "B", you would move "B" at the first position)
nameToId.put("A", "123");
nameToId.put("B", "234");
nameToId.put("C", "345");

List<String> name = Arrays.asList("A", "B", "C");
String id = nameToId.keySet()
                    .stream()
                    .filter(name::contains)
                    .findFirst()
                    .map(nameToId::get)
                    .orElse(null)

      

You get nothing ... don't try to nest too much in filter predicates or match functions, because then your solution Stream

might not be as readable anymore.

+2


source







All Articles