【问题标题】:parse a document with million words解析包含百万字的文档
【发布时间】:2016-11-01 01:50:03
【问题描述】:

我已经实现了一些代码来在 txt sample.txt 文件中查找 anagrams 单词并将它们输出到控制台上。 txt文档的每一行都包含String(word)。

如果我想在 txt.file 中找到百万或 200 亿个单词的字谜单词,那是正确的方法吗?如果不是,在这种情况下我应该使用哪种技术?

感谢您的帮助。

示例

abac
aabc
hddgfs
fjhfhr
abca
rtup
iptu
xyz
oifj
zyx
toeiut
yxz
jrgtoi

输出

abac aabc abca
xyz zyx yxz

代码

package org.reader;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Test {
    // To store the anagram words
    static List<String> match = new ArrayList<String>();
    // Flag to check whether the checkWorld1InMatch() was invoked.
    static boolean flagCheckWord1InMatch;

    public static void main(String[] args) {
        String fileName = "G:\\test\\sample2.txt";
        StringBuilder sb = new StringBuilder();
        // In case of matching, this flag is used to append the first word to
        // the StringBuilder once.
        boolean flag = true;

        BufferedReader br = null;
        try {
            // convert the data in the sample.txt file to list
            List<String> list = Files.readAllLines(Paths.get(fileName));

            for (int i = 0; i < list.size(); i++) {

                flagCheckWord1InMatch = true;
                String word1 = list.get(i);

                for (int j = i + 1; j < list.size(); j++) {

                    String word2 = list.get(j);

                    boolean isExist = false;

                    if (match != null && !match.isEmpty() && flagCheckWord1InMatch) {
                        isExist = checkWord1InMatch(word1);

                    }

                    if (isExist) {
                        // A word with the same characters was checked before
                        // and there is no need to check it again. Therefore, we
                        // jump to the next word in the list.
                        // flagCheckWord1InMatch = true;
                        break;
                    } else {
                        boolean result = isAnagram(word1, word2);
                        if (result) {

                            if (flag) {
                                sb.append(word1 + " ");
                                flag = false;
                            }

                            sb.append(word2 + " ");

                        }
                        if (j == list.size() - 1 && sb != null && !sb.toString().isEmpty()) {
                            match.add(sb.toString().trim());
                            sb.setLength(0);
                            flag = true;

                        }

                    }

                }
            }

        } catch (

        IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        for (String item : match) {
            System.out.println(item);
        }

        // System.out.println("Sihwail");

    }

    private static boolean checkWord1InMatch(String word1) {
        flagCheckWord1InMatch = false;
        boolean isAvailable = false;
        for (String item : match) {
            String[] content = item.split(" ");
            for (String word : content) {
                if (word1.equals(word)) {
                    isAvailable = true;
                    break;

                }
            }
        }
        return isAvailable;
    }

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

}

【问题讨论】:

  • 一个数据库,带有一些REVERSE函数是一种策略
  • 由于您将每个条目与可能的所有其他条目进行比较,因此这种方法非常慢。不要直接比较它们,而是更喜欢通过分组进行相当隐含的比较。使用Map&lt;String, x&gt;,其中键是某个字谜组的代表,在您的情况下是预排序的字符串(aabc 代表aabc, abac, caba 的组,依此类推)。然后该值要么是该组的每个项目的列表/集合,要么是用于写入值的文件处理程序,以避免将它们保留在内存中。

标签: java


【解决方案1】:

对于 200 亿字,您将无法将它们全部保存在 RAM 中,因此您需要一种方法来分块处理它们。

20,000,000,000 字。 Java 需要大量内存来存储字符串,因此您可以计算每个字符 2 个字节和至少 38 个字节的开销。

这意味着一个字符的 20,000,000,000 个单词需要 800,000,000,000 字节或 800 GB,这比我所知道的任何计算机都多。

您的文件将包含少于 20,000,000,000 个不同的单词,因此如果您只将每个单词存储一次(例如,在 Set 中),您可能会避免内存问题。

【讨论】:

  • MrSmith 是对的,如果您不确定要处理的实际字数,最好分块读取文件。例如,您可以逐行读取文件。 1st 实例化一个扫描器:Scanner scanner = new Scanner(fileName); 2ndwhile(scanner.hasNextLine()){ 替换你的 for 循环,然后 然后String word = scanner.nextLine(); 读取每个单词 但是这样,您将没有要比较的单词列表。所以你可能必须找到另一种方法来检查一个单词是否是一个字谜。
  • 要处理数十亿的单词,是否可以将数据存储在Server上的数据库中并使用MySQL进行查询?
  • 一个数据库或使用文件系统。例如,您可以将文件拆分为文件,其中每个文件都有一个特定长度的所有单词。字谜只能存在于相同长度的单词中。如果文件仍然包含太多单词,无法在 RAM 中处理它们,您可以考虑其他方法将问题拆分为更小的部分。
【解决方案2】:

首先是较小的数字。

由于最好使用更强大的数据结构,所以不要读取核心中的所有行,而是逐行读取。

Map<String, Set<String>> mapSortedToWords = new HashMap<>();

Path path = Paths.get(fileName);
try (BufferedReader in = Files.newBufferedReader(Path, StandardCharsets.UTF_8)) {
    for (;;) {
        String word = in.readLine();
        if (word == null) {
            break;
        }
        String key = sorted(word);
        Set<String> words = mapSortedToWords.get(key);
        if (words == null) {
            words = new TreeSet<String>();
            mapSortedToWords.put(key, words);
        }
        words.add(word);
    }
}
for (Set<String> anagrams : mapSortedToWords.values()) {
    if (anagrams.size() > 1) {
        ... anagrams
    }
}

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

这会在地图中存储一组单词。堪比abac aabc abca

对于大量存储(排序字母,单词)的数据库会更好。像 Derby 或 H2 这样的嵌入式数据库不会造成安装问题。

【讨论】:

  • 正如我在评论中所写,OP 还可以使用文件处理程序作为映射值,将每个项目写入文件,而不是将其保存在 ram 中(如果他真的读取大文件)。但是Set 适合较小的文件。我喜欢,我们有相同的想法:D。
【解决方案3】:

对于您指定的文件大小(200 亿字),显然您的代码存在两个主要问题,

List<String> list = Files.readAllLines(Paths.get(fileName)); 

for (int i = 0; i < list.size(); i++)

你程序中的这两行基本上都是疑问,

  1. 您是否有足够的内存一次性读取完整文件?
  2. 迭代 200 亿次可以吗?

对于大多数系统,上述两个问题的答案都是“否”。

因此,您的目标是减少内存占用并减少迭代次数。

因此,您需要逐块读取文件并使用某种搜索数据结构(例如Trie )来存储您的单词。

对于上述两个主题,您会发现许多关于 SO 的问题,例如,

Fastest way to incrementally read a large file

Finding anagrams for a given word

上面的算法说你必须首先为你的单词创建一个字典

无论如何,我相信没有现成的答案给你。拿一个包含十亿字的文件(这本身就是一项非常艰巨的任务),看看哪些有效,哪些无效,但您当前的代码显然不起作用。

希望对你有帮助!!

【讨论】:

    【解决方案4】:

    使用流来读取文件。这样你一次只存储一个单词。

    FileReader file = new FileReader("file.txt"); //filestream
    
    String word;
    
    while(file.ready()) //return true if there a bytes left in the stream
    {
        char c = file.read(); //reads one character
        if(c != '\n') 
        {
            word+=c;
        }
        else {
        process(word); // do whatever you want
        word = "";
        }
    }
    

    【讨论】:

      【解决方案5】:

      更新

      您可以使用地图来查找如下所示的字谜。对于您拥有的每个单词,您可以对其字符进行排序并获得排序后的字符串。所以,这将是你的字谜地图的关键。这个键的值将是其他字谜词。

      public void findAnagrams(String[] yourWords) {
          Map<String, List<String>> anagrams = new HashMap<String, List<String>>();
          for (String word : yourWords) {
              String sortedWord = sortedString(word);
              List<String> values = anagrams.get(sortedWord);
              if (values == null) 
                  values = new LinkedList<>();
      
              values.add(word);
              anagrams.put(sortedWord, values);
          }
      
          System.out.println(anagrams);
      }
      
      private static String sortedString(String originalWord) {
      
          char[] chars = originalWord.toCharArray();
          Arrays.sort(chars);
          String sorted = new String(chars);
          return sorted;
      }
      

      【讨论】:

      • 整数应该是多少?字符串不是更直观吗?
      • 那个字符串是什么?如果有“acab”、“abac”、“baac”和“caab”,你会选择哪一个作为key?我相信整数作为键可能更合适。在这种情况下,字谜图看起来像 {7, ["acab", "abac", "baac","caab" ] }
      • “你会选择哪一个” 显然是“aabc”,因为这是每个带有两个“a”的字谜组的预排序代表,一个“ b”和一个“c”,因为 OP 无论如何都会对 char 数组进行排序。在您的示例中,“7”是什么?为什么不是“8”或“42”?
      • 嗯我明白你的意思了。谢谢。我会尽快更新我的答案。
      • int的问题是,如果你用每个char的值来计算key(例如),那么就有可能得到不同字符串的相同key(比如“ac”和“ bb”,两者都有附加值“196”)。您将需要某种无冲突的哈希码。我猜字符串会更容易。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-30
      • 2017-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多