Java 8, how can I implement a switch statement using streams?

前端 未结 4 1662
忘掉有多难
忘掉有多难 2020-11-28 15:08

I have a text file imgui.ini containing:

[Debug]
Pos=7,79
Size=507,392
Collapsed=0

[ImGui Demo]
Pos=320,5
Size=550,680
Collapsed=0
相关标签:
4条回答
  • 2020-11-28 15:37

    The best way to parse such a file (without using dedicated 3rd party libraries), is via the regex API, and its front-end class Scanner. Unfortunately, the best operations to implement it via Stream API, are currently missing. Namely, Matcher.results() and Scanner.findAll(…) are not there yet. So unless we want to wait until Java 9, we have to create similar methods for a Java 8 compatible solution:

    public static Stream<MatchResult> findAll(Scanner s, Pattern pattern) {
        return StreamSupport.stream(new Spliterators.AbstractSpliterator<MatchResult>(
                1000, Spliterator.ORDERED|Spliterator.NONNULL) {
            public boolean tryAdvance(Consumer<? super MatchResult> action) {
                if(s.findWithinHorizon(pattern, 0)!=null) {
                    action.accept(s.match());
                    return true;
                }
                else return false;
            }
        }, false);
    }
    public static Stream<MatchResult> results(Matcher m) {
        return StreamSupport.stream(new Spliterators.AbstractSpliterator<MatchResult>(
                m.regionEnd()-m.regionStart(), Spliterator.ORDERED|Spliterator.NONNULL) {
            public boolean tryAdvance(Consumer<? super MatchResult> action) {
                if(m.find()) {
                    action.accept(m.toMatchResult());
                    return true;
                }
                else return false;
            }
        }, false);
    }
    

    Using methods with a similar semantic allows us to replace their usage with the standard API methods, once Java 9 is released and becomes commonplace.

    Using these two operations, you can parse your file using

    Pattern groupPattern=Pattern.compile("\\[(.*?)\\]([^\\[]*)");
    Pattern attrPattern=Pattern.compile("(.*?)=(.*)\\v");
    Map<String, Map<String, String>> m;
    try(Scanner s=new Scanner(Paths.get(context.io.iniFilename))) {
        m = findAll(s, groupPattern).collect(Collectors.toMap(
            gm -> gm.group(1),
            gm -> results(attrPattern.matcher(gm.group(2)))
                .collect(Collectors.toMap(am->am.group(1), am->am.group(2)))));
    }
    

    the resulting map m holds all information, mapping from the group names to another map holding the key/value pairs, i.e. you can print an equivalent .ini file using:

    m.forEach((group,attr)-> {
        System.out.println("["+group+"]");
        attr.forEach((key,value)->System.out.println(key+"="+value));
    });
    
    0 讨论(0)
  • 2020-11-28 15:46

    Attempt:

    try {
            Path file = Paths.get("G:\\tmp", "img.ini");
            Stream<String> lines = Files.lines(file);
    
            lines.filter(line->{
                if("pos".equalsIgnoreCase(line.split("=")[0])){
                    //process pos line here
                    System.out.println("pos"+line);
                    return false;
                }
                return true;
            }).filter(line->{
                System.out.println("2"+line);
                if("Collapsed".equalsIgnoreCase(line.split("=")[0])){
                    //process Collapsed line here
                    System.out.println("Collapsed"+line);
                    return false;
                }
                return true;
            }).filter(line->{
                System.out.println("3"+line);
                if("Size".equalsIgnoreCase(line.split("=")[0])){
                    //process Size line here
                    System.out.println("Size"+line);
                    return false;
                }
                return true;
            }).forEach(line->{
                //settings = new Settings();
            });;
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    
    0 讨论(0)
  • 2020-11-28 15:56

    Focusing on the question "is there a way to simulate switch statement behavior", I think the answer is that you could, with a little effort. I asked myself that a couple of years ago, and did the following as an exercise (and then never used it again):

    private static <T> Predicate<T> testAndConsume(Predicate<T> pred, Consumer<T> cons) {
        return t -> {
            boolean result = pred.test(t);
            if (result) cons.accept(t);
            return result;
        };
    }
    
    public static class SwitchConsumer<T> {
        Predicate<T> conditionalConsumer;
        private SwitchConsumer(Predicate<T> pred) {
            conditionalConsumer = pred;
        }
    
        public static <C> SwitchConsumer<C> inCase(Predicate<C> pred, Consumer<C> cons) {
            return new SwitchConsumer<>(testAndConsume(pred, cons));
        }
    
        public SwitchConsumer<T> elseIf(Predicate<T> pred, Consumer<T> cons) {
            return new SwitchConsumer<>(conditionalConsumer.or(testAndConsume(pred,cons)));
        }
    
        public Consumer<T> elseDefault(Consumer<T> cons) {
            return testAndConsume(conditionalConsumer.negate(),cons)::test;   // ::test converts Predicate to Consumer
        }
    }
    

    testAndConsume composes a Predicate and a Consumer, creating a Predicate that returns the same value but calls the Consumer as a side-effect if the value is true. That becomes the basis for each "case" in the "switch". Each "case" is strung together by Predicate.or(), which provides the short-circuiting "else-if" nature of the switch. Finally, the composed Predicate is turned into a Consumer by adding ::test to the Predicate.

    Applying it to your code snippet, it looks like this:

        Stream.of("Pos=320,5", "Size=550,680", "Collapsed=0")
                .map(s -> s.split("="))
                .forEach(SwitchConsumer.<String[]>
                        inCase(arr -> "Pos".equals(arr[0]), arr -> settings.pos = arr[1])
                        .elseIf(arr -> "Size".equals(arr[0]), arr -> settings.size = arr[1])
                        .elseIf(arr -> "Collapsed".equals(arr[0]), arr -> settings.collapsed = arr[1])
                        .elseDefault(arr -> {}));
    

    That's about as switch-ish as it can get without an actual switch in a Consumer body.

    0 讨论(0)
  • 2020-11-28 16:00

    Another way to read your config file:

    public class Main {
        public static void main(String[] args) throws IOException {
            Path path = Paths.get("D:\\Development\\workspace\\Application\\src\\main\\resources\\init.txt");
            String content = new String(Files.readAllBytes(path));
    
            Map<String, Config> configMap = Stream.of(content.split("\\n\\r"))
                .map(config -> Arrays.asList(config.split("\\r")))
                .collect(HashMap<String, Config>::new, (map, list) -> {
                    String header = list.get(0);
                    String pos = list.get(1);
                    String size = list.get(2);
                    String collapsed = list.get(3);
                    map.put(header, new Config(pos.substring(pos.indexOf("=") + 1), size.substring(size.indexOf("=") + 1), collapsed.substring(collapsed.indexOf("=") + 1)));
                }, (m, u) -> {});
    
            System.out.println(configMap);
        }
    }
    
    class Config {
        public String pos;
        public String size;
        public String collapsed;
    
        public Config(String pos, String size, String collapsed) {
            this.pos = pos;
            this.size = size;
            this.collapsed = collapsed;
        }
    
        @Override
        public String toString() {
            return "Config{" +  "pos='" + pos + '\'' + ", size='" + size + '\'' + 
                   ", collapsed='" + collapsed + '\'' + '}';
        }
    }
    

    Result will be map:

    {
        [Debug]=Config{pos='7,79', size='507,392', collapsed='0'}, 
        [ImGui Demo]=Config{pos='320,5', size='550,680', collapsed='0'}
    }
    
    0 讨论(0)
提交回复
热议问题