【问题标题】:Java 8 Streams - how to compare elements?Java 8 Streams - 如何比较元素?
【发布时间】:2020-03-26 13:38:22
【问题描述】:

我想使用 Java Stream 在.txt 文件中查找字谜。这是我所拥有的:

try (InputStream is = new URL("http://wiki.puzzlers.org/pub/wordlists/unixdict.txt").openConnection().getInputStream();
     BufferedReader reader = new BufferedReader(new InputStreamReader(is));
     Stream<String> stream = reader.lines()) {

还有变位法:

public boolean isAnagram(String firstWord, String secondWord) {
    char[] word1 = firstWord.replaceAll("[\\s]", "").toCharArray();
    char[] word2 = secondWord.replaceAll("[\\s]", "").toCharArray();
    Arrays.sort(word1);
    Arrays.sort(word2);
    return Arrays.equals(word1, word2);
}

如何使用 Java 8 Stream 检查 unixdict.txt 中的单词是否是字谜?有什么方法可以将一个单词与流中的所有单词进行比较?

【问题讨论】:

    标签: java java-stream inputstream fileinputstream anagram


    【解决方案1】:

    当您要查找所有字谜时,不建议尝试将一个单词与所有其他单词进行比较,因为您最终会将每个单词与其他单词进行比较,这称为二次time complexity。处理 1000 个单词,需要一百万个比较,处理 100,000 个单词,需要 10,000,000,000 个比较,以此类推。

    您可以更改您的 isAnagram 方法,为 HashMap 等数据结构提供查找键:

    static CharBuffer getAnagramKey(String s) {
        char[] word1 = s.replaceAll("[\\s]", "").toCharArray();
        Arrays.sort(word1);
        return CharBuffer.wrap(word1);
    }
    

    CharBuffer 类包装了一个char[] 数组并提供了必要的equalshashCode 方法,而无需复制数组内容,这使得构造一个新的String 更可取。

    附带说明,.replaceAll("[\\s]", "") 可以简化为 .replaceAll("\\s", ""),两者都会消除所有空格字符,但您问题的示例输入根本没有空格字符。要删除所有非单词字符,如撇号和 & 符号,您可以使用 s.replaceAll("\\W", "")

    然后,您可以处理所有单词以在单个线性传递中找到字谜

    URL srcURL = new URL("http://wiki.puzzlers.org/pub/wordlists/unixdict.txt");
    try(InputStream is = srcURL.openStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        Stream<String> stream = reader.lines()) {
    
        stream.collect(Collectors.groupingBy(s -> getAnagramKey(s)))
            .values().stream()
            .filter(l -> l.size() > 1)
            .forEach(System.out::println);
    }
    

    使用此解决方案,对于较大的单词列表,打印可能会成为更昂贵的部分。所以你可能会改变流的操作,例如下面打印前十个字谜组合:

    stream.collect(Collectors.groupingBy(s -> getAnagramKey(s)))
        .values().stream()
        .filter(l -> l.size() > 1)
        .sorted(Collections.reverseOrder(Comparator.comparingInt(List::size)))
        .limit(10)
        .forEach(System.out::println);
    

    【讨论】:

    • 非常感谢!我还有一个问题;你知道如何将这个流限制为最长的数组(一个字谜最多的单词)吗?我知道如何在列表中执行此操作,但不知道如何使用 Java Stream。
    • 好吧,在最后一个示例中,您可以将limit(10) 更改为limit(1),但要获得最大元素,还可以使用更简单的.max(Comparator.comparingInt(List::size)),而不是.sorted(…) .limit(…)
    • 好的,但是如果我们有几个单词具有相同数量的字谜,那么我们总是只能看到第一个:/
    • 显然,如果你想找到所有的字谜,这会更有效。 OP 暗示(至少对我而言)他们只是想找出一个特定的单词是否是一个字谜,所以我认为这是一次性交易。对我来说,你的解决方案最好的地方是CharBuffer。我需要记住这一点!
    • @WJS 我同意关于最终目标有一些猜测。您的解决方案对于检查单个单词的任务来说还不错。回复CharBuffer,我一直希望我对此类问答的回答有助于为其他现实生活任务指出一些有用的工具。如需更详细的用例,请查看this answer的后半部分...
    【解决方案2】:

    这行得通。我首先在流中做了所有的事情,但这样效率更高。

          InputStream is = new URL("http://wiki.puzzlers.org/pub/wordlists/unixdict.txt")
                  .openConnection().getInputStream();
          BufferedReader reader = new BufferedReader(new InputStreamReader(is));
    
          String word = "germany";
          final String sword = sortedWord(word);
          reader.lines().filter(w -> sortedWord(w).compareTo(sword) == 0).forEach(
                System.out::println);
    
          static String sortedWord(String w) {
             char[] chs = w.toCharArray();
             Arrays.sort(chs);
             return String.valueOf(chs);
          }
    
    

    一个可能的改进是首先过滤单词的长度。你可能想试试这个word list,因为里面有更多的词。

    【讨论】:

    • 可以考虑使用sortedWord(w).compareTo(sword) == 0,而不是sortedWord(w).equals(sword)...
    【解决方案3】:

    我认为您最好的选择可能是使用 multimap 收集器将流转换为 Guava multimap,使用字符串的排序版本作为映射的键。有关如何执行此操作的示例,请参阅 Cleanest way to create a guava MultiMap from a java8 stream。如果您只想要生成的字谜集,则可以使用 multimap.asMap().entrySet().stream()... 根据您的需要过滤和收集结果。

    【讨论】:

    • 我不能使用 Guava 库,这是必需的。 ://
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-30
    • 1970-01-01
    相关资源
    最近更新 更多