从集合中获取随机元素

Posted

技术标签:

【中文标题】从集合中获取随机元素【英文标题】:Getting a random element from a Collection 【发布时间】:2022-01-22 19:42:37 【问题描述】:

在 java 中,我希望能够始终维护按物种分类的鱼类集合(因此使用 HashMap),同时能够从所有物种中选择一个随机元素,除了具有恒定时间复杂度的物种。例如,以下代码可以完成这项工作,但复杂度为 O(元素数量):

import java.util.*;

HashMap<String, ArrayList<Fish>> fishesBySpecies = new HashMap<>();

// Insert some fishes...
// Fish has a String attribute that describes its species
// Now we want to pick a random Fish that isn't from the unwantedSpecies

String unwanted = "unwanted species";
ArrayList<Fish> wantedSpecies = new ArrayList<>();
for (String species : fishesBySpecies.keySet()) 
    if (!Objects.equals(species, unwanted)) 
        wantedSpecies.addAll(fishesBySpecies.get(species));
    


// Finally !
int randomIndex = new Random().nextInt(wantedSpecies.size());
Fish randomElement = wantedSpecies.get(randomIndex);

如果可能的话,知道如何以恒定的时间复杂度来做到这一点吗?谢谢!

【问题讨论】:

@Sweeper 抱歉,我编辑了我的问题,然后忘记了最初的标题,现在已修复。 当然,在恒定的时间复杂度下这是不可能的。但显然,您可以通过使用 Stream API 重写代码以使其看起来更复杂,甚至不提及时间复杂度...... @Holger 最后我找到了一个使用地图的 O(物种数量)的解决方案,但它对我的要求太具体了,我无法唤起它。我相信它不太可能帮助其他人。我接受了第一个答案,因为它帮助我更好地理解我想要做什么。 您的问题确实已经有 O(元素数量),正如您自己所说的那样。因此,如果您不想要更好的时间复杂度,这无论如何都是不可能的,那么就不清楚您要的是什么。选择一个没有中间 ArrayList 的随机元素是可能的,如果你愿意,只要问正确的问题。 【参考方案1】:

我能想到的唯一方法是维护ArrayList&lt;Fish&gt; 以及您已有的地图。但是有一个缺点:添加或删除鱼会稍微复杂一些:

Map<String, List<Fish>> fishesBySpecies = new HashMap<>();
List<Fish> wantedFishes = new ArrayList<>();

//...

public void addFish(String species, Fish fish) 
    List<Fish> speciesFishes = fishesBySpecies.get(species);
    if (speciesFishes == null) 
        speciesFishes = new ArrayList<>();
        fishesBySpecies.put(species, speciesFishes);
    
    speciesFishes.add(fish);
    // also maintain the list of wanted fishes
    if (!unwantedSpecies.equals(species)) 
        wantedFishes.add(fish);
    

【讨论】:

【参考方案2】:

您正在执行的是过滤,过滤时您必须检查每个元素是否需要取出。您可以尝试对键使用字母排序,并在键按字母顺序大于过滤(不需要的)键时停止过滤。

您的代码也可以通过使用 java 流来彻底缩短:

HashMap<String, ArrayList<Fish>> fishesBySpecies = new HashMap<>();

// Insert some fishes...
// Fish has a String attribute that describes its species
// Now we want to pick a random Fish that isn't from the unwantedSpecies

String unwanted = "unwanted species";

fishesBySpecies.keySet().stream() // Get the keyset and create a stream out of it
        .filter(key -> !key.equalsIgnoreCase(unwanted)) // If key is not equal to unwanted then leave it in else remove it
        .forEach(filteredKey ->
                wantedSpecies.addAll(fishesBySpecies.get(filteredKey))); // For each key that was left in, we fetch the fishes

fishesBySpecies.keySet().stream() // Get the keyset and create a stream out of it
        .forEach(key ->
                
                    if(!key.equalsIgnoreCase(unwanted))
                    
                        wantedSpecies.addAll(fishesBySpecies.get(unwanted));
                    
                
                ); // iterate and filter at the same time. Faster.

【讨论】:

以上是关于从集合中获取随机元素的主要内容,如果未能解决你的问题,请参考以下文章

python random 从集合中随机选择元素

Python--从集合中随机取出一个元素

Python--从集合中随机取出一个元素

随机获取一个集合(List, Set)中的元素,随机获取一个Map中的key或value

常数时间插入删除和获取随机元素

Leetcode 380. 常数时间插入删除和获取随机元素