【问题标题】:Searching anagrams with Java 8使用 Java 8 搜索字谜
【发布时间】:2017-04-06 23:57:43
【问题描述】:

我必须编写程序,该程序应该读取字谜文件并显示单词+他的字谜。 txt文件很大,使用scanner后listOfWords大小为:25000。

输出示例:

word anagram1 anagram2 anagram3 ...
word2 anagram1 anagram2...

我有代码,它可以运行,但是很慢:

  private static List<String> listOfWords = new ArrayList<String>();
  private static List<ArrayList<String>> allAnagrams = new ArrayList<ArrayList<String>>();

  public static void main(String[] args) throws Exception {
    URL url = new URL("www.xxx.pl/textFile.txt");
    Scanner scanner = new Scanner(url.openStream());
    while (scanner.hasNext()) {
      String nextToken = scanner.next();
      listOfWords.add(nextToken);
    }
    scanner.close();

    while (listOfWords.isEmpty() == false) {
      ArrayList<String> anagramy = new ArrayList<String>();
      String wzor = listOfWords.remove(0);
      anagramy.add(wzor);
      char[] ch = wzor.toCharArray();
      Arrays.sort(ch);
      for (int i = 0; i < listOfWords.size(); i++) {
        String slowo = listOfWords.get(i);
        char[] cha = slowo.toCharArray();
        Arrays.sort(cha);
        if (Arrays.equals(ch, cha)) {
          anagramy.add(slowo);
          listOfWords.remove(i);
          i--;
        }
      }
      allAnagrams.add(anagramy);
    }

    for (ArrayList<String> ar : allAnagrams) {
      String result = "";
      if (ar.size() > 1) {
        for (int i = 1; i < ar.size(); i++) {
          result = ar.get(i) + " ";
        }
        System.out.println(ar.get(0) + " " + result);
      }
    }
  }

我必须用 Java 8 - 流来编写它,但我不知道。可以使用 Streams 从 URL + 搜索字谜中读取吗?你能帮我按 Stream 搜索字谜吗?老师告诉我,阅读整个列表时,代码应该比我的短。只有几行,可以吗?

【问题讨论】:

    标签: java java-8 java-stream anagram


    【解决方案1】:

    您可以将文件中的单词读入 List 或直接创建它的 Stream:

    try (InputStream is = new URL("http://www.someurl.pl/file.txt").openConnection().getInputStream();
         BufferedReader reader = new BufferedReader(new InputStreamReader(is));
         Stream<String> stream = reader.lines()) {
           //do something with stream
    }
    

    然后只是流过列表并收集字谜,其中具有相同排序字符列表的所有单词都被视为字谜:

    Map<String, List<String>> anagrams =
        stream.collect(Collectors.groupingBy(w -> sorted(w)));
    

    sorted 方法只是像您在示例中所做的那样对字母进行排序:

    public static String sorted(String word) {
        char[] chars = word.toCharArray();
        Arrays.sort(chars);
        return new String(chars);
    }
    

    【讨论】:

      【解决方案2】:

      让我们创建一个单独的方法来对字母进行排序。您也可以使用 Stream API 做到这一点:

      private static String canonicalize(String s) {
          return Stream.of(s.split("")).sorted().collect(Collectors.joining());
      }
      

      现在您可以阅读一些Reader,从中提取单词并按规范形式对单词进行分组:

      Map<String, Set<String>> map = new BufferedReader(reader).lines()
                   .flatMap(Pattern.compile("\\W+")::splitAsStream)
                   .collect(Collectors.groupingBy(Anagrams::canonicalize, Collectors.toSet()));
      

      接下来,您可以第三次使用 Stream API 删除单个字母组:

      return map.values().stream().filter(list -> list.size() > 1).collect(Collectors.toList());
      

      现在您可以将一些阅读器传递给此代码以从中提取字谜。完整代码如下:

      import java.io.*;
      import java.util.*;
      import java.util.regex.Pattern;
      import java.util.stream.*;
      
      public class Anagrams {
          private static String canonicalize(String s) {
              return Stream.of(s.split("")).sorted().collect(Collectors.joining());
          }
      
          public static List<Set<String>> getAnagrams(Reader reader) {
          Map<String, Set<String>> map = new BufferedReader(reader).lines()
                                           .flatMap(Pattern.compile("\\W+")::splitAsStream)
                                           .collect(Collectors.groupingBy(Anagrams::canonicalize, Collectors.toSet()));
              return map.values().stream().filter(list -> list.size() > 1).collect(Collectors.toList());
          }
      
          public static void main(String[] args) throws IOException {
              getAnagrams(new StringReader("abc cab tat aaa\natt tat bbb"))
                      .forEach(System.out::println);
          }
      }
      

      打印出来

      [att, tat]
      [abc, cab]
      

      如果要使用 URL,只需将 StringReader 替换为 new InputStreamReader(new URL("www.xxx.pl/textFile.txt").openStream(), StandardCharsets.UTF_8)


      如果要提取字谜集合的第一个元素,解决方案应稍作修改:

      public static Map<String, Set<String>> getAnagrams(Reader reader) {
          Map<String, List<String>> map = new BufferedReader(reader).lines()
             .flatMap(Pattern.compile("\\W+")::splitAsStream)
             .distinct() // remove repeating words
             .collect(Collectors.groupingBy(Anagrams::canonicalize));
          return map.values().stream()
             .filter(list -> list.size() > 1)
             .collect(Collectors.toMap(list -> list.get(0), 
                                       list -> new TreeSet<>(list.subList(1, list.size()))));
      }
      

      这里的结果是映射,其中键是字谜集中的第一个元素(首先出现在输入文件中),值是按字母顺序排序的其余元素(我创建了一个子列表来跳过第一个元素,然后移动它们进入TreeSet 以执行排序;另一种方法是list.stream().skip(1).sorted().collect(Collectors.toList()))。

      示例用法:

      getAnagrams(new StringReader("abc cab tat aaa\natt tat bbb\ntta\ncabr\nrbac cab crab cabrc cabr"))
              .entrySet().forEach(System.out::println);
      

      【讨论】:

      • 真的吗? Stream.of(s.split(""))?尽管您在同一个答案中使用Pattern.splitAsStream?更不用说更多更有效率的s.codePoints().sorted() .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();。虽然在这里使用char[] a=s.toCharArray(); Arrays.sort(a); return String.valueOf(a); 可能是更简单的选择。
      • 干得好,你能告诉我在哪里可以添加我自己的排序实现,即排序字谜(没有第一个单词)?
      • @Holger,没有人问最有效的解决方案,只要求基于 Stream API 的解决方案。如果您在此处遇到性能问题,则不应首先使用流(顺便说一句,在这种情况下,使用CharBuffer.wrap(a) 作为键可能更有效)。如果您只想要 Stream API,那么我的解决方案肯定比您的替代方案更短且更易于理解。
      • 我不相信你对“如果你在这里遇到性能问题,你不应该首先使用流”是认真的。
      • @TagirValeev 非常感谢!它有效,我需要最后的帮助,我必须从该地图中对键集进行排序,我需要在我的代码中添加如下内容:map.keySet().stream().sorted((o1, o2) -&gt; o1.compareTo(o2));。它有效,但我不知道如何将该排序添加到地图、整个程序,而不仅仅是那一行。
      【解决方案3】:

      你可以试试这个方法

      //---------------Anagram---------------------------------
          String w1 = "Triangle".toLowerCase(), w2 = "Integral".toLowerCase();
          HashMap<String, Integer> w1Map = new HashMap<String, Integer>();
          HashMap<String, Integer> w2Map = new HashMap<String, Integer>();
      
          w1Map = convertToHashMap(w1);
          w2Map = convertToHashMap(w2);       
      
         if( !(w1.equals(w2)) && (w1Map.keySet().equals(w2Map.keySet()))) 
             System.out.println(w1+" and "+w2+" are anagrams");
         else 
             System.out.println(w1+" and "+w2+" are not anagrams");
      

      调用下面的方法

      public static HashMap<String, Integer> convertToHashMap(String s) {
          HashMap<String, Integer> wordMap = new HashMap<String, Integer>();
          for (int i = 0;i < s.length(); i++){
              wordMap.put(String.valueOf(s.charAt(i)), Integer.valueOf(s.charAt(i)));
          }
          return wordMap;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-12-23
        • 2015-09-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多