Java:如何在ArrayList中找到前10个最常见的String +频率?

时间:2016-03-14 16:26:31

标签: java arraylist hashmap duplicates topmost

我试图在ArrayList中找到前10个最常见的字符串+它们的计数(出现频率)。

如何以最佳的时间复杂度做到这一点?

以下代码查找格式(String = int)

中最常见的单词+频率

E.g。的= 2

  public static Entry<String, Integer> get10MostCommon(WordStream words) {

    ArrayList<String> list = new ArrayList<String>();
    Map<String, Integer> stringsCount = new HashMap<>();
    Map.Entry<String, Integer> mostRepeated = null;

    for (String i : words) {
      list.add(i);
    }

    for (String s : list) {
      Integer c = stringsCount.get(s);
      if (c == null)
        c = new Integer(0);
      c++;
      stringsCount.put(s, c);
    }

    for (Map.Entry<String, Integer> e : stringsCount.entrySet()) {
      if (mostRepeated == null || mostRepeated.getValue() < e.getValue())
        mostRepeated = e;
    }
    return mostRepeated;
  }

4 个答案:

答案 0 :(得分:13)

您可以使用Java 8流分两步完成:

for entry in JSON["entries"] { // Here
    print(entry.stringValue)
}

第一个流使用Map<String, Long> map = list.stream() .collect(Collectors.groupingBy(w -> w, Collectors.counting())); List<Map.Entry<String, Long>> result = map.entrySet().stream() .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) .limit(10) .collect(Collectors.toList()); Collectors.groupingBy()将字词映射到其频率。

这将返回一个映射,其条目按照相反的顺序进行流式处理并按映射条目值排序。然后,流被限制为仅保留10个元素,这些元素最终被收集到列表中。

答案 1 :(得分:3)

您必须至少一次从头到尾迭代这些单词,因此您不会比O(n)更好地结束,其中n是单词大小。然后提取m个顶级条目(在您的情况下为10个)。假设您的k字总共有n个唯一字词,要查找m个热门条目,您需要在maxm次搜索k次1}}条目,导致m * k操作,在最坏的情况下(当所有单词都是唯一的)时为您提供O(m * n)。总的来说,这会为您提供O(n * (m + 1))次操作,或者O(11 * n)在您的情况下(10次max次搜索加上初始分组运行)。

这是我的尝试(JDK8 +,未经测试):

public static Collection<Map.Entry<String, Integer>> topOccurences(List<String> words, int topThreshold) {
    Map<String, Integer> occurences = new HashMap<>();
    words.stream().forEach((word) -> {
        int count = 1;
        if (occurences.containsKey(word)) {
            count = occurences.get(word) + 1;
        }
        occurences.put(word, count);
    });

    List<Map.Entry<String, Integer>> entries = new LinkedList<>(occurences.entrySet());
    List<Map.Entry<String, Integer>> tops = new LinkedList<>();
    Comparator<Map.Entry<String, Integer>> valueComp = Comparator.comparing((Map.Entry<String, Integer> t) -> t.getValue());
    int topcount = 0;
    while (topcount < topThreshold && !entries.isEmpty()) {
        Map.Entry<String, Integer> max = Collections.max(entries, valueComp);
        tops.add(max);
        entries.remove(max);
        topcount++;
    }
    return tops;
}

答案 2 :(得分:1)

我会将其分解为两种方法。

第一个除了创建单词频率图之外什么都不做。

第二个将返回n个最高频率的单词。

如果您要求n个最常用的单词但Map的密钥少于该数字,那么您的代码应该怎么做?

您有机会尝试JDK 8 lambdas并有效地过滤频率Map

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Calculate word frequencies from a List of words
 * User: mduffy
 * Date: 3/14/2016
 * Time: 1:07 PM
 * @link http://stackoverflow.com/questions/35992891/java-how-to-find-top-10-most-common-string-frequency-in-arraylist/35993252#35993252
 */
public class WordFrequencyDemo {

    public static void main(String[] args) {
        List<String> words = Arrays.asList(args);
        Map<String, Integer> wordFrequencies = WordFrequencyDemo.getWordFrequencies(words);
        System.out.println(wordFrequencies);
    }

    public static Map<String, Integer> getWordFrequencies(List<String> words) {
        Map<String, Integer> wordFrequencies = new LinkedHashMap<String, Integer>();
        if (words != null) {
            for (String word : words) {
                if (word != null) {
                    word = word.trim();
                    if (!wordFrequencies.containsKey(word)) {
                        wordFrequencies.put(word, 0);
                    }
                    int count = wordFrequencies.get(word);
                    wordFrequencies.put(word, ++count);
                }
            }
        }
        return wordFrequencies;
    }
}

答案 3 :(得分:1)

您总是首先使用哈希来计算单词,这当然会使用O(n)时间和O(n)空间。这是第一步。

然后讨论如何选择前10名。您可以使用至少需要O(nlogn)时间的排序。但有一种更好的方法,即使用堆。假设你的情况下k = 10。您需要将单词对及其频率添加到大小为k的最小堆中,其中我们使用频率作为最小堆的键。如果堆已满,则仅从堆中删除最小元素(顶部)并仅在此字的频率大于堆中的顶部字的频率时添加新的字 - 频率对。一旦我们扫描了地图中的所有单词并且堆已正确更新,那么最小堆中包含的元素是最常见的k个。下面是示例代码。只需稍微修改代码即可获取ArrayList而不是数组将完成您的工作。

class Pair {
    String key;
    int value;

    Pair(String key, int value) {
        this.key = key;
        this.value = value;
    }
}

public class Solution {
    /**
     * @param words an array of string
     * @param k an integer
     * @return an array of string
     */

    private Comparator<Pair> pairComparator = new Comparator<Pair>() {
        public int compare(Pair left, Pair right) {
            if (left.value != right.value) {
                return left.value - right.value;
            }
            return right.key.compareTo(left.key);
        }
    };

    public String[] topKFrequentWords(String[] words, int k) {
        if (k == 0) {
            return new String[0];
        }

        HashMap<String, Integer> counter = new HashMap<>();
        for (String word : words) {
            if (counter.containsKey(word)) {
                counter.put(word, counter.get(word) + 1);
            } else {
                counter.put(word, 1);
            }
        }

        PriorityQueue<Pair> Q = new PriorityQueue<Pair>(k, pairComparator);
        for (String word : counter.keySet()) {
            Pair peak = Q.peek();
            Pair newPair = new Pair(word, counter.get(word));
            if (Q.size() < k) {
                Q.add(newPair);
            } else if (pairComparator.compare(newPair, peak) > 0) {
                Q.poll();
                Q.add(new Pair(word, counter.get(word)));
            }
        }

        String[] result = new String[k];
        int index = 0;
        while (!Q.isEmpty()) {
            result[index++] = Q.poll().key;
        }

        // reverse
        for (int i = 0; i < index / 2; i++) {
            String temp = result[i];
            result[i] = result[index - i - 1];
            result[index - i - 1] = temp;
        }

        return result;
    }
}