【问题标题】:Java: Random element from the most popular categoryJava:来自最流行类别的随机元素
【发布时间】:2012-01-02 00:43:02
【问题描述】:

我需要找到最有效的方法来从最流行的类别中找到随机元素

来自

4 Cheese
1 Olive
2 Mushroom
4 Ham
2 Chicken
4 Salad

我想要CheeseHamSalad。如果有多个顶级类别,我不在乎我会从哪个类别中获得我的物品。

在输入我有Iterator<Foo> Foo 实现

Interface Foo {
    int getCategory();
}

我当前的代码如下所示:

Foo getItem(Iterator<Foo> it) {
    Map<Integer, List<Foo>> categoryMap = new HashMap<Integer, List<Foo>>();
    while(it.hasNext()) {
        Foo foo = it.next();
        int category = foo.getCategory();

        List<Foo> l = categoryMap.get(category);
        if(l == null) {
            l = new ArrayList<Foo>();
            categoryMap.put(category, l);
        }

        l.add(foo);
    }

    int longest_list_size = 0;
    int longest_category_id = -1;

    Set<Integer> categories = categoryMap.keySet()

    for(Integer c:  categories ) {
        int list_size = categoryMap.get(c).size();
        if(list_size  > longest_list_size) {
           longest_list_size = list_size;
           longest_category_id = c;
        }
    }

    if(longest_list_size == 0)
        return null;

    int r = new Random().nextInt(longest_list_size);
    return categoryMap.get(c).get(r);
}

【问题讨论】:

    标签: java algorithm popularity


    【解决方案1】:

    拥有 2 张地图可能会更快:

    Foo getItem(Iterator<Foo> it) {
        Map<Integer, Foo> categoryToFoo = new HashMap<Integer, Foo>();
        Map<Integer, Integer> counts = new HashMap<Integer, Integer>();
        int maxCount = 0;
        while(it.hasNext()) {
            Foo foo = it.next();
            int category = foo.getCategory();
            int categoryCount = 1;
            if ( ! categoryToFoo.contains( category ) ) {
                categoryToFoo.put( category, foo );
            }
            else {
                categoryCount = counts.get( category ) + 1;
            }
            counts.put( category, categoryCount );
            if ( categoryCount > maxCount ) {
                maxCount = categoryCount;
            }
        }
    
        List<Foo> possible = new ArrayList<Foo>()
        for ( Map.Entry entry : counts.entrySet() ) {
            if ( entry.getValue() == maxCount ) {
                possible.add( categoryToFoo.get( entry.getKey() ) );
            }
        }
    
        return possible.get( new Random().nextInt( possible.size() ) );
    }
    

    你可以在很多地方做进一步的优化,但你明白了。

    【讨论】:

    • 您永远不应该在Map 上致电contains。使用Map#get(),查看是否为null
    • @Zoltan 我想这会将哈希查找减少一,所以是的,我同意。但它只是将伪代码拼凑在一起来传达这个想法,并且使用 contains 使它更具可读性 imo...
    【解决方案2】:

    我会这样做:

    1. it 创建一个List&lt;Foo&gt;
    2. 按类别对列表进行排序
    3. 从头开始遍历列表,存储同类别最长区间的开始和结束索引
    4. 在开始和结束索引之间选择一个随机元素

    我认为用更少的代码会更快一些,但你的解决方案也很好。

    如果您真的很关心性能,因为it 可能有数百万个元素,那么您首先不应该使用这个Iterator。在这种情况下,您可能应该将每个类别的流行度存储在一个 Map 中,并将相同项目的列表存储在另一个 Map 中,但我对其余代码一无所知。

    【讨论】:

    • 您真的打算对字符串进行排序吗?这比对整数进行排序更糟糕,这比他已经在做的更糟糕。
    • 不,如果我理解正确,该类别有一个int id
    • 啊,好吧,我以为你指的是类别名称。变量名在他的代码中具有误导性...
    【解决方案3】:

    确实很难(如果不是不可能的话)改进您的方法,至少在复杂性方面是这样。我们来分析一下。你在做

    1. 插入地图 -> O(N)
    2. 计算最大值 -> O(N)

    总计:O(N)

    其他方法:

    1. 优先队列 -> O(N*log(N)) 插入所有元素 + O(1) 检索头部
    2. 对初始映射进行排序 O(N*log(N)) + O(1) 检索第一个
    3. 如果您知道投票计数的间隔,比如 [0..K],它比 N 少或不高,您可以在 O(K) + O(1) 中进行计数排序以获取最大值。

    如果您只需要最大检索一次,那么您的方法就足够了,IMO。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-04
      • 2019-04-26
      • 1970-01-01
      • 1970-01-01
      • 2012-05-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多