如何对字符串列表进行排序,并在java中查找1000个最常用的值

时间:2017-07-19 15:30:12

标签: java sorting data-structures

在java中(或者使用外部库)我需要获取大约500,000个值的列表并找到最常出现的(模式)1000。尽最大努力将复杂性降至最低。

我到目前为止尝试过,做一个哈希,但我不能因为它必须向后键= count value = string,否则当获得前1000名时,我的复杂性将是垃圾。并且向后的方式并没有真正发挥作用,因为我在搜索我的字符串能够将其移除并将其插入更高的位置时会有一个非常复杂的插入...

我尝试过使用二进制搜索树,但是在计数或字符串上存在与数据排序相同的问题。如果它在字符串上然后获得前1000的计数是坏的,反之亦然插入是坏的。

我可以先对列表进行排序(按字符串)然后遍历列表并保持计数直到它更改字符串。但是我应该使用什么数据结构来跟踪前1000名?

由于

5 个答案:

答案 0 :(得分:9)

我首先会创建一个$reponse = array(print_r($response)); for ($i = 0; $i < count($reponse); $i++) { for ($l = 0; $l < count($reponse[$i]); $l++) { echo $reponse[$i][$l]; echo "<br/>"; }; }; 来存储每个单词的频率。然后,我按降序按值排序此地图,最后我保留第一个Map<String, Long>条目。

在代码中:

1000

您可能会发现将上述内容分为两个步骤更加清晰:首先收集到频率图,然后按值对其条目进行排序并保留前1000个条目。

答案 1 :(得分:3)

我将此分为三个阶段:

  • 计算单词出现次数(例如,使用HashMap<String, Integer>
  • 对结果进行排序(例如,将地图转换为条目列表并按值降序排序)
  • 输出排序结果的前1000个条目

如果计数很小(例如,如果你实际上有500,000个单独的单词),排序会很慢,但如果你期望有很多重复的单词,那就应该没问题了。

答案 2 :(得分:1)

我已经将这个问题打开了几天,并决定反对Federico优雅的Java 8答案,并提交尽可能少的Java 8答案。

以下代码使用了一个将标记与字符串相关联的帮助程序类。

public class TopOccurringValues {
    static HashMap<String, StringCount> stringCounts = new HashMap<>();

    // set low for demo.  Change to 1000 (or whatever)
    static final int TOP_NUMBER_TO_COLLECT = 10;

    public static void main(String[] args) {
        // load your strings in here
        List<String> strings = loadStrings();

        // tally up string occurrences
        for (String string: strings) {
            StringCount stringCount = stringCounts.get(string);
            if (stringCount == null) {
                stringCount = new StringCount(string);
            }
            stringCount.increment();
            stringCounts.put(string, stringCount);
        }

        // sort which have most
        ArrayList<StringCount> sortedCounts = new ArrayList<>(stringCounts.values());
        Collections.sort(sortedCounts);

        // collect the top occurring strings
        ArrayList<String> topCollection = new ArrayList<>();
        int upperBound = Math.min(TOP_NUMBER_TO_COLLECT, sortedCounts.size());
        System.out.println("string\tcount");
        for (int i = 0; i < upperBound; i++) {
            StringCount stringCount = sortedCounts.get(i);
            topCollection.add(stringCount.string);
            System.out.println(stringCount.string + "\t" + stringCount.count);
        }
    }

    // in this demo, strings are randomly generated numbers.
    private static List<String> loadStrings() {
        Random random = new Random(1);
        ArrayList<String> randomStrings = new ArrayList<>();
        for (int i = 0; i < 5000000; i++) {
            randomStrings.add(String.valueOf(Math.round(random.nextGaussian() * 1000)));
        }
        return randomStrings;
    }

    static class StringCount implements Comparable<StringCount> {
        int count = 0;
        String string;
        StringCount(String string) {this.string = string;}
        void increment() {count++;}
        @Override
        public int compareTo(StringCount o) {return o.count - count;}
    }
}

55行代码!这就像反码高尔夫。 String生成器创建了500万个字符串而不是500,000个,因为:为什么不呢?

string  count
-89 2108
70  2107
77  2085
-4  2077
36  2077
65  2072
-154    2067
-172    2064
194 2063
-143    2062

随机生成的字符串可以具有介于-999和999之间的值,但由于我们得到的是高斯值,我们会看到分数越高越接近0的数字。

答案 3 :(得分:0)

您可以使用java stream API执行此操作:

List<String> input = Arrays.asList(new String[]{"aa", "bb", "cc", "bb", "bb", "aa"});

// First we compute a map of word -> occurrences
final Map<String, Long> collect = input.stream()
                                       .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

// Here we sort the map and collect the first 1000 entries
final List<Map.Entry<String, Long>> entries = new ArrayList<>(collect.entrySet());
final List<Map.Entry<String, Long>> result = entries.stream()
                                                    .sorted(Comparator.comparing(Map.Entry::getValue, Comparator.reverseOrder()))
                                                    .limit(1000)
                                                    .collect(Collectors.toList());

result.forEach(System.out::println);

答案 4 :(得分:0)

我选择使用的解决方案是首先使用键值对制作哈希映射。我通过遍历链表并插入键值对来获得计数,在插入之前我会检查是否存在,如果是,则增加计数。那部分非常直接。

我需要根据它的价值对其进行排序的下一部分,我使用了谷歌发布的名为guava的库,它能够很容易地使用他们所谓的值按值而不是键进行排序多图。它们在某种意义上反转了哈希值,并允许将多个值映射到一个键,这样我可以拥有我的所有前1000个,与上面提到的某些不允许的解决方案相反,并且会导致我只需为每个键获取一个值。

最后一步是迭代多图(向后)以获得最常出现的1000次。

如果您有兴趣,请查看该功能的代码

private static void FindNMostFrequentOccurences(ArrayList profileName,int n) {
        HashMap<String, Integer> hmap = new HashMap<String, Integer>();
        //iterate through our data 
        for(int i = 0; i< profileName.size(); i++){

            String current_id = profileName.get(i).toString();
            if(hmap.get(current_id) == null){
                hmap.put(current_id, 1);
            } else {
                int current_count = hmap.get(current_id);
                current_count += 1;
                hmap.put(current_id, current_count);
            }
        }
        ListMultimap<Integer, String> multimap = ArrayListMultimap.create();
        hmap.entrySet().forEach(entry -> {
            multimap.put(entry.getValue(), entry.getKey());
        }); 

        for (int i = 0; i < n; i++){
            if (!multimap.isEmpty()){
                int lastKey = Iterables.getLast(multimap.keys());               
                String lastValue = Iterables.getLast(multimap.values());
                multimap.remove(lastKey, lastValue);
                System.out.println(i+1+": "+lastValue+", Occurences: "+lastKey);
            }
        }
    }