Change List <Optional <T >> to Optional <List <T >> in Java

TL;DR Java version of sequence and traverse functions in "Scala Functional Design & Programming" Scala Functional Design & Programming-A Thorough Guide to Functional by Scala Contributor

What you want to do (one of them)

If you receive a list of ʻOptional and all have values (= isPresent is true), you want to make a list of values without ʻOptional. If at least one value is null (= isPresent is false), I want to return ʻOptional.empty`.

Implementation

Roughly, I think you can write it like this. If null is included in the argument or list element, an error will occur, but it is not considered here. (It's worse to use ~~ null ~~)

public static <T> Optional<List<T>> sequence(List<Optional<T>> xs) {
    List<T> ys = new ArrayList<>();
    for (Optional<T> x : xs) {
        //If there is an element whose value does not exist, the process is terminated at that point.
        if(x.isEmpty()) {
            return Optional.empty();
        }

        //If the value exists, unwrap Optional and add it to the list
        ys.add(x.get());
    }

    //Wrap the list with only values with Optional and return it
    return Optional.of(ys);
}

Try using

List<Optional<String>>  xs = List.of(Optional.of("foo"), Optional.of("bar"));
List<Optional<Integer>> ys = List.of(Optional.of(1), Optional.empty(), Optional.of(100));
List<Optional<Boolean>> zs = List.of(); //Empty list

System.out.println(sequence(xs)); // Optional[[foo, bar]]
System.out.println(sequence(ys)); // Optional.empty
System.out.println(sequence(zs)); // Optional[[]]

It seems to work properly.

application

Suppose you have the following function that converts a string to a number: If the target string can be converted to a number, this function will wrap the number in ʻOptional and return it. If the conversion fails, ʻOptional.empty is returned. [^ 1]

public static Optional<Integer> safeParseInt(String s) {
    try {
        Integer n = Integer.valueOf(s);
        return Optional.of(n);
    } catch (NumberFormatException e) {
        return Optional.empty();
    }
}

Use this function to convert a list of Strings to a list of ʻOptional . Furthermore, if the element contains ʻOptional.empty, as when applying the sequence function, consider a function that also makes the overall result ʻOptional.empty. Name it traverse`.

//This is all convertible to numbers so Optional[[1, 100, 30]]Want to
Optional<List<Integer>> xs = traverse(List.of("1", "100", "30"));

//This is optional because it contains some values that cannot be converted to numbers..I want to make it empty
Optional<List<Integer>> ys = traverse(List.of("1", "3", "hoge", "0", ""));

This could be implemented as follows using the sequence function above.

public static Optional<List<Integer>> traverse(List<String> xs) {
    //Once Optional<Integer>Convert to a list of
    List<Optional<Integer>> ys = new ArrayList<>();
    for (String x : xs) {
        Optional<Integer> o = safeParseInt(x);
        ys.add(o);
    }

    //List with sequence function<Optional<Integer>>Optional<List<Integer>>Conversion to
    return sequence(ys);
}

If you move it, you will see that you are getting the desired result.

System.out.println(traverse(List.of("1", "100", "30")));          // Optional[[1, 100, 30]]
System.out.println(traverse(List.of("1", "3", "hoge", "0", ""))); // Optional.empty

However, the above is a little wasteful writing. See the following example. The travase function is inlined and output based on the intermediate results.

List<String> xs = List.of("1", "hoge", "2", "3", "4", "5", "6", "7", "8", "9");

//This is exactly the same process as the travarse function
List<Optional<Integer>> ys = new ArrayList<>();
for (String x : xs) {
    Optional<Integer> o = safeParseInt(x);
    ys.add(o);
}
Optional<List<Integer>> zs = sequence(ys);

//Output intermediate list and results
System.out.println(ys); // [Optional[1], Optional.empty, Optional[2], Optional[3], Optional[4], Optional[5], Optional[6], Optional[7], Optional[8], Optional[9]]
System.out.println(zs); // Optional.empty

The fact that zs becomes ʻOptional.emptyis as expected. However, this has generated an intermediate listys with the same length as xs. It is a decision to apply the safeParseInt function to the second element " hoge " of xs, and when ʻOptional.empty is completed, the overall result will also be ʻOptional.empty. It feels a bit inefficient to continue the conversion process after knowing the result. If the number of elements is small, it will not be a problem, but if the number of elements is large, it may affect the performance. When ʻOptional.empty is reached, the safeParseInt function is not applied to the elements after that, and I want to return ʻOptional.empty` immediately.

Rewrite the travarse function so that it does.

public static Optional<List<Integer>> traverse(List<String> xs) {
    List<Integer> ys = new ArrayList<>();
    for (String x : xs) {
        //Convert strings to numbers
        Optional<Integer> o = safeParseInt(x);
    
        //If the conversion fails, the process will be terminated at that point.
        if(o.isEmpty()) {
            return Optional.empty();
        }

        //If the conversion is successful, remove the optional wrap and add it to the list.
        ys.add(o.get());           
    }

    //Wrap the list with only numbers with Optional and return it
    return Optional.of(ys);
}

I've stopped using the sequence function and are doing both conversion and branching within a single loop. Now, if the conversion fails in the middle, ʻOptional.empty will be returned immediately at that point. Make safeParseInt a parameter so that you can use any function that can be converted to ʻOptional.

public static <T, R> Optional<List<R>> traverse(List<T> xs, Function<T, Optional<R>> f) {
    List<R> ys = new ArrayList<>();
    for (T x : xs) {
        Optional<R> o = f.apply(x);
        if(o.isEmpty()) {
            return Optional.empty();
        }
        ys.add(o.get());
    }

    return Optional.of(ys);
}

When using it, pass it as a lambda expression or method reference. In summary, it looks like this:

Main.java


import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        //Lambda expression
        Optional<List<Integer>> xs = traverse(List.of("1", "100", "30"), x -> safeParseInt(x));
        //Method reference
        Optional<List<Integer>> ys = traverse(List.of("1", "3", "hoge", "0"), Main::safeParseInt);

        System.out.println(xs); // Optional[[1, 100, 30]]
        System.out.println(ys); // Optional.empty
    }

    public static Optional<Integer> safeParseInt(String s) {
        try {
            Integer n = Integer.valueOf(s);
            return Optional.of(n);
        } catch (NumberFormatException e) {
            return Optional.empty();
        }
    }

    public static <T, R> Optional<List<R>> traverse(List<T> xs, Function<T, Optional<R>> f) {
        List<R> ys = new ArrayList<>();
        for (T x : xs) {
            Optional<R> o = f.apply(x);
            if(o.isEmpty()) {
                return Optional.empty();
            }
            ys.add(o.get());
        }
        return Optional.of(ys);
    }

//No longer used
//    public static <T> Optional<List<T>> sequence(List<Optional<T>> xs) {
//        List<T> ys = new ArrayList<>();
//        for (Optional<T> x : xs) {
//            if(x.isEmpty()) {
//                return Optional.empty();
//            }
//            ys.add(x.get());
//        }
//        return Optional.of(ys);
//    }
}

Bonus --reimplementation of sequence function

I stopped calling the sequence function from the travarse function, but in fact I can use this new travarse function to reimplement the sequence function as follows:

public static <T> Optional<List<T>> sequence(List<Optional<T>> xs) {
    return traverse(xs, Function.identity()); // traverse(xs, x -> x)But yes
}

It may be a little strange, but here Function.identity () (or x-> x [^ 2]) is a function that takes ʻOptional and returns ʻOptional <T>. , Travarse's second argument Function <T, Optional <R >> f can be written as above, as long as the return type is ʻOptional`. [^ 3]

For the sake of explanation, let's match the type parameters of the travarse function to this new sequence function.

public static <T> Optional<List<T>> traverse(List<Optional<T>> xs, Function<Optional<T>, Optional<T>> f) {
    List<T> ys = new ArrayList<>();
    for (Optional<T> x : xs) {
        Optional<T> o = f.apply(x); //f is Function#Since it is identity, x and o are the same instance
        if(o.isEmpty()) {
            return Optional.empty();
        }
        ys.add(o.get()); //After all, it's the same as packing the contents of x
    }
    return Optional.of(ys);
}

As with the sequence function defined at the beginning, you can see that we just removed the Optional of each element of List <Optional <T >>.

Finally

I don't think Java defines general-purpose functions that take these functions as arguments (it's hard to standardize because there are many functional interfaces), but I thought of it somehow, so I wrote it. Aside from its practicality, it is recommended because it is fun to be a brain teaser.

Thank you for reading for me until the end. If you have any questions or deficiencies, please contact us in the comments section or Twitter.

[^ 1]: Optional is used in Optional Int in order to connect to a general-purpose explanation that does not depend on the type of the contents of Optional. [^ 2]: You can write it like x-> x, but this way of writing will generate an object of typeFunction <T, T>every time, so unless you have a specific reason I think it's better to use Function.identity (). [^ 3]: It may be confusing because it is covered by the type parameter T, but what the travarse T and sequence T point to here is a different type. T in travarse corresponds to ʻOptional in sequence. Since the type parametersT, R of travarse can be the same type, the Function <Optional , Optional > `type satisfies the requirement of the second argument of travarse.

Recommended Posts

Change List <Optional <T >> to Optional <List <T >> in Java
[java] sort in list
Sample code to convert List to List <String> in Java Stream
Change java encoding in windows
[Java] How to use Optional ②
List aggregation in Java (Collectors.groupingBy)
Java8 to start now ~ Optional ~
[Java] How to use Optional ①
[Java] Change language and locale to English in JVM options
Easy way to check method / field list in Java REPL
Multithreaded to fit in [Java] template
How to learn JAVA in 7 days
Log output to file in Java
[Java] Convert 1-to-N List to Map
List of members added in Java 9
[Java] How to use List [ArrayList]
How to use classes in Java?
How to name variables in Java
Try to implement Yubaba in Java
List of types added in Java 9
[Java] Conversion from array to List
How to concatenate strings in java
Cast an array of Strings to a List of Integers in Java
How to implement date calculation in Java
How to implement Kalman filter in Java
Multilingual Locale in Java How to use Locale
How to change app name in rails
Try to solve Project Euler in Java
Easy to make Slack Bot in Java
Java reference to understand in the figure
[Java] How to add data to List (add, addAll)
How to do base conversion in Java
Convert SVG files to PNG files in Java
How to implement coding conventions in Java
How to embed Janus Graph in Java
Immutable (immutable) List object conversion function in Java8
How to get the date in java
Arrylist and linked list difference in java
Add footnotes to Word documents in Java
Process every arbitrary number in Java List
[Java Bronze] 5 problems to keep in mind
Sample to unzip gz file in Java
[Java] Learn how to use Optional correctly
Java to C and C to Java in Android Studio
Do not declare variables in List in Java
Add SameSite attribute to cookie in Java
How to get Excel sheet name list in Java (POI vs SAX)
[Java] From two Lists to one array list
Two ways to start a thread in Java + @
I want to send an email in Java.
How to display a web page in Java
CORBA seems to be removed in Java SE 11. .. ..
Java Optional type
Code to escape a JSON string in Java
Clone Java List.
Try to create a bulletin board in Java
Procedure to change lower_case_table_names = 1 in MySQL 8.0 of CentOS 8.3
Changes in Java 11
[Java] Various methods to acquire the value stored in List by iterative processing
Studying Java 8 (Optional)
I tried to implement deep learning in Java