比Collections.shuffle()更快地随机化列表处理?

时间:2015-08-04 18:04:52

标签: java performance random collections shuffle

我正在使用Java开发基于代理的模型。我使用了一个分析器来减少任何低效率,直到唯一阻止它的是 Collections.shuffle()

我的模型中的代理(他们的动物)需要以随机顺序处理,以便在其他代理之前不会始终处理代理。

我正在寻找:一种更快方式来混洗,而不是Java的Collections.shuffle()或处理ArrayList中元素的替代方法以随机顺序显着更快。如果您知道数据结构比ArrayList快,请务必回答。我考虑过LinkedList和ArrayDeque,但它们并没有太大区别。

目前,我正在试图改组的列表中有超过1,000,000个元素。随着时间的推移,这个数量会增加,而且随之而来的效率也越来越低。

是否有替代数据结构或随机化处理元素的方式更快?

我只需要能够以随机顺序存储元素并处理它们。我不使用包含或任何比存储更复杂的东西并迭代它们。

以下是一些示例代码,可以更好地解释我想要实现的目标:

更新:对于ConcurrentModificationException,很抱歉,我没有意识到我已经这样做了,而且我并没有打算让任何人感到困惑。修正了下面的代码。

ArrayList<Agent> list = new ArrayList<>();
void process()
{
    list.add(new Agent("Zebra"));
    Random r = new Random();
    for (int i = 0; i < 100000; i++)
    {
        ArrayList<Agent> newlist = new ArrayList<>();
        Collections.shuffle(list);//Something that will allow the order to be random (random quality does not matter to me), yet faster than a shuffle
        for (String str : list)
        {
            newlist.add(str);
            if(r.nextDouble() > 0.99)//1% chance of adding another agent to the list
            {
                newlist.add(new Agent("Lion"));
            }
        }
        list = newlist;
    }
}

另一个更新 我考虑过做list.remove(rando.nextInt(list.size())但是因为对于ArrayLists的删除是O(n),所以更糟糕的是这样做而不是为这么大的列表大小进行随机播放。

5 个答案:

答案 0 :(得分:3)

我会使用一个简单的ArrayList而根本不会洗牌它。而是选择要处理的随机列表索引。为避免两次处理列表元素,我将从列表中删除已处理的元素。

现在,如果列表非常大,删除随机条目本身就是瓶颈。但是,可以通过删除 last 条目并将其移动到所选条目之前占用的位置来轻松避免这种情况:

public String pullRandomElement(List<String> list, Random random) {
    // select a random list index
    int size = list.size();
    int index = random.nextInt(size);
    String result = list.get(index);
    // move last entry to selected index
    list.set(index, list.remove(size - 1));
    return result;
}

毋庸置疑,您应该选择一个列表实现,其中get(index)和remove(lastIndex)是快速O(1),例如ArrayList。您可能还想添加边缘大小写处理(例如列表为空)。

答案 1 :(得分:1)

你可以使用这个:如果你已经有项目列表,根据它的大小生成一个随机数并获得nextInt。

ArrayList<String> list = new ArrayList<>();    
int sizeOfCollection = list.size();

Random randomGenerator = new Random();
int randomId = randomGenerator.nextInt(sizeOfCollection);
Object x = list.get(randomId);
list.remove(randomId);

答案 2 :(得分:1)

由于您的代码实际上并不依赖于列表的顺序,因此只需在处理结束时将其洗牌一次。

void process() {
    Random r = new Random();
    for (int i = 0; i < 100000; i++) {
        for (String str : list) {
             if(r.nextDouble() > 0.9) {
                list.add(str + str);
            }
        }
    }
    Collections.shuffle(list);
}

虽然这仍然会像原始代码一样抛出ConcurrentModificationException

答案 3 :(得分:0)

Collections.shuffle()使用Fisher-Yates算法的现代变体: 来自https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle

To shuffle an array a of n elements (indices 0..n-1):
  for i from n − 1 downto 1 do
       j ← random integer such that 0 ≤ j ≤ i
       exchange a[j] and a[i]

Colections.shuffle将列表转换为数组,然后执行shuffle,只需使用random.nextInt()然后将所有内容复制回来。 (见http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/Collections.java#Collections.shuffle%28java.util.List%29

通过避免复制数组和写回的开销,您可以更快地完成此操作: 编写自己的ArrayList实现,您可以直接访问支持数组,或访问字段&#34; elementData&#34;你的ArrayList通过反射。

现在使用与该数组上的Collections.shuffle相同的算法,使用正确的size()。 这样可以加快速度,因为如果像Collection.shuffle()那样整个数组都可以避免复制:

通过反射进行访问需要一点时间,因此这种解决方案只有更多元素才能更快。

我不会推荐这个解决方案,除非你想通过执行时间来赢得比赛,拥有精神的洗牌。

与比较速度时一样,确保在开始测量之前运行要测量的算法1000次来预热VM。

答案 4 :(得分:0)

根据文档,Collections.shuffle() O(N)时间运行。

  

此方法以线性时间运行。如果指定的列表未实现RandomAccess接口并且很大,则此实现将指定的列表转储到数组中,然后再将其重新排序,然后将经过改组的数组转储回列表中。这样可以避免因改组“顺序访问”列表而导致的二次行为。

我建议您使用public static void shuffle(List<?> list, Random rnd)重载,尽管性能优势可能会忽略不计。

除非您允许一些偏见,否则,要提高性能将是困难的,例如使用部分改组(每次只有一部分列表被重新改组)或改组不足。混洗不足意味着编写自己的Fisher-Yates例程,并在反向遍历期间跳过某些列表索引;例如,您可以跳过所有奇数索引。但是,列表的结尾将比前面少,这是另一种偏见。

如果列表大小为M,则可以考虑将以下列表中的大量N缓存在不同的固定索引排列中(以随机顺序从0M-1)应用程序启动时的内存。然后,只要您迭代集合并根据先前定义的特定排列进行迭代,就可以随机选择这些预排序之一。如果N大(例如1000或更大),则总体偏差将很小(并且也相对均匀)并且非常快。但是,您注意到您的列表缓慢增长,因此这种方法不可行。