简体   繁体   English

对于列表中的每个元素,请选择该元素和3个随机非重复元素

[英]For each element in a list, choose that element and 3 random nonrepeating elements

I have a list of size 202 with the integers 0...201. 我有一个大小为202的列表,整数为0 ... 201。 I need to iterate through this list and for each element I need to choose 4 elements with one being the current element and the remaining 3 being random, nonrepeating elements. 我需要遍历此列表,对于每个元素,我需要选择4个元素,其中一个是当前元素,其余3个是随机的,非重复的元素。

For example, 例如,

At the first iteration I need to generate [0, r1, r2, r3] where r1, r2, r3 are distinct random numbers between 1-201; 在第一次迭代中,我需要生成[0,r1,r2,r3],其中r1,r2,r3是1-201之间的不同随机数;

At the second iteration I need to generate [1, r1, r2, r3] where r1, r2, r3 are distinct random numbers between 0, 2-201; 在第二次迭代中,我需要生成[1,r1,r2,r3],其中r1,r2,r3是0、2-201之间的不同随机数;

At the third iteration I need to generate [2, r1, r2, r3] where r1, r2, r3 are distinct random numbers between 0-1, 3-201; 在第三次迭代中,我需要生成[2,r1,r2,r3],其中r1,r2,r3是0-1、3-201之间的不同随机数;

I know a way to do this but it takes too much time and space and since I will be doing this in the main thread of an android app I should probably do something more efficient. 我知道一种方法可以执行此操作,但它会占用太多时间和空间,而且由于我将在android应用程序的主线程中执行此操作,因此我可能应该做一些更有效的事情。

Improving Miroslav's approach by removing the redundant filtering: 通过删除冗余过滤来改进Miroslav的方法:

List<Integer> integerList = new ArrayList<>();
for(int i = 0; i < 202; i++) {
    integerList.add(i);
}
List<Integer> exclusionList = new ArrayList<>();
exclusionList.addAll(integerList);
for(int element : integerList){
    exclusionList.remove(element);
    Collections.shuffle(exclusionList);
    System.out.println(element + ", " + exclusionList.subList(0, 3));
    exclusionList.add(element);
}

If you dont want to have to generate an array with all the values to choose from (which could become problematic in case of a larger range), the obvious algorithm is this: 如果您不想生成具有所有值可供选择的数组(如果范围更大,可能会出现问题),显而易见的算法是:

Add the fixed number to the selection. 将固定编号添加到选择中。 Choose a random integer from 0 to 200; 从0到200中选择一个随机整数; if it's equal to or greater than the number in the selection, add 1 to it; 如果等于或大于选择中的数字,则将其加1; add it to the selection. 将其添加到选择中。 Then choose a random integer from 0 to 199; 然后从0到199中选择一个随机整数; compare it to each number in the selection; 将其与选择中的每个数字进行比较; whenever you find it's equal to or greater than a number in the selection, add 1 to it; 每当您发现它等于或大于选择中的数字时,就将其加1; if it's smaller, or you come to the end of the list, insert it there. 如果较小,或者您到达列表的末尾,请在此处插入。 Go on with 198, 197, until you have selected enough numbers. 继续执行198、197,直到选择了足够的数字。 Example: 例:

Range: 0-201  
Add fixed number: 0  
Selection: [0]  
Range: 0-200  
Select random number: 34  
34 > 0, 34 + 1 = 35
Selection: [0, 35]  
Range: 0-199  
Select random number: 102  
102 > 0, 102 + 1 = 103, 103 > 35, 103 + 1 = 104  
Selection: [0, 35, 104]  
Range: 0-198  
Select random number: 66  
66 > 0, 66 + 1 = 67, 67 > 35, 67 + 1 = 68, 68 < 102, insert before 102  
Selection: [0, 35, 68, 102]  

Here is one of the possible solutions: 这是可能的解决方案之一:

List<Integer> listOfIntegers = new ArrayList<>();

// add some entries to the list
for(int i = 0; i < 202; i++) {
listOfIntegers.add(i);
}

for(int element : listOfIntegers){
// create a new list that excludes the current element
List<Integer> exclusionList = listOfIntegers.stream().filter(x -> x != element).collect(Collectors.toList());
// shuffle the new list (if you don't do it the output will use the same elements after the first few iterations)
Collections.shuffle(exclusionList);
// print the results and use only the first 3 elements of the shuffled list
System.out.println(element + ", " + exclusionList.subList(0, 3));
}

This should give the following output: 这应该给出以下输出:

0, [38, 193, 51]
1, [60, 179, 30]
2, [46, 21, 13]
3, [43, 201, 74]
4, [28, 14, 97]
5, [38, 24, 22]
6, [177, 106, 53]

... and so on. ... 等等。

There's another way to do this that's easier than my original answer, below. 还有另一种方法比下面的原始答案更容易。 It requires making a separate list from which you select things, but you don't have to shuffle it. 它需要制作一个单独的列表,从中选择内容,但是您不必对其进行随机组合。

The idea is that you make random selections from the second list. 这个想法是您从第二个列表中进行随机选择。 When you make a selection, you then swap the selected item with the last item in the list, and decrease the number of items available for the next selection. 进行选择时,然后将所选项目与列表中的最后一个项目交换,并减少可用于下一个选择的项目数。 That way, you can't re-select the same number again. 这样,您就无法再次选择相同的号码。 And if one of your selections is the index number, you just swap that number but don't keep it. 而且,如果您选择的一项是索引号,则只需交换该数字即可,而不保留它。 It's really simple: 这很简单:

selectionList = array containing 0..maxNumber
for i in 0 to maxNumber
    selectionListLength = selectionList.length
    selections = []
    numSelected = 0
    while numSelected < 3
        pick = random(selectionListLength)
        // if we picked any number other than i, keep it.
        if selectionList[pick] != i
            // keep the number
            selections[numSelected] = selectionList[pick]
            numSelected++
        end if
        // swap selected item with the one at the end of the list
        // and reduce the count available
        temp = selectionList[pick]
        selectionList[pick] = selectionList[selectionListLength-1-numSelected]
        selectionList[selectionListLength-1-numSelected] = temp
        --selectionListLength
    end while
end for

This ends up scrambling the selection list over time, but that shouldn't affect the "randomness" of your selections. 随着时间的推移,这最终会扰乱选择列表,但这不会影响选择的“随机性”。 This is real easy to code, doesn't require re-shuffling lists, and is a whole lot easier to understand than my original response. 这确实很容易编写代码,不需要重新排列列表,并且比我的原始响应更容易理解。 It will make at most 4 (but usually 3) random selections from the list for every number. 对于每个数字,它将最多从列表中随机选择4个(但通常为3个)。

Original answer 原始答案

You should be able to do this in O(n) time, without having to generate a temporary list or do any shuffling. 您应该能够在O(n)时间内执行此操作,而不必生成临时列表或进行任何改组。

Imagine that you have a list of 10 numbers: 假设您有一个包含10个数字的列表:

0 1 2 3 4 5 6 7 8 9

So you start with 0. Now you need three non-repeating random numbers that are between 1 and 9, inclusive. 因此,您从0开始。现在,您需要三个非重复的随机数,介于1到9之间(含1和9)。 So pick a random number greater than or equal to 1, and less than 9. And add 1 to it. 因此,请选择一个大于或等于1且小于9的随机数。并将其加1。 Say you pick 5. Your list is now separated into four sections: 假设您选择5。现在,您的列表分为四个部分:

0 | 1 2 3 4 | 5 | 6 7 8 9

Add those two ranges, [1-4] and [6-9] to a list. 将这两个范围[1-4]和[6-9]添加到列表中。 Pick one of them and select a number from it. 选择其中一个并从中选择一个数字。 Say you picked the first list and you selected the number 2. Now you have the ranges: 假设您选择了第一个列表,然后选择了数字2。现在您有了范围:

0 | 1 | 2 | 3 4 | 5 | 6 7 8 9

Add the two new ranges ([1-1] and [3-4]) to your list of ranges that already includes [6-9]. 将两个新范围([1-1]和[3-4])添加到已经包含[6-9]的范围列表中。 Now pick one of those ranges from the list again and select a number from it. 现在,再次从列表中选择一个范围,然后从中选择一个数字。

Things get a little fiddly because selecting a number from a range doesn't always split it into two. 事情变得有点奇怪,因为从范围中选择一个数字并不总是将其分为两个。 For example, if you selected the number 4 from the range [1-4], then you'd be left with the single range [1-3]. 例如,如果您从范围[1-4]中选择了数字4,则将剩下单个范围[1-3]。 But we can account for that easily enough. 但是我们可以很容易地解决这个问题。

Here's some pseudo code. 这是一些伪代码。

ranges = [(1-9)]  // a single range from 1-9
selections = []  // array for selected numbers
for s in 0,1,2
    x = random(ranges.length)  // select one of the ranges
    range = ranges[x]
    ranges.remove(x)  // remove the range from the list of ranges
    // now, pick a number from that range
    pick = random(range.last-range.first+1) // pick a random number from that range
    selections[s] = range[0]+pick  // and save the selection
    // create new range or ranges that exclude that number
    if (selections[s] != range.first)
        ranges += (range.first, range.first+pick-1)
    if (selections[s] != range.last)
        ranges += (range.first+pick+1, range.last)
end for
// at this point, the selections array contains the numbers you selected.

Note that this is pseudo code. 请注意,这是伪代码。 The ranges array I show above is an array that contains tuples: the start and end of the range. 我上面显示的ranges数组是一个包含元组的数组:范围的开始和结束。 You'd need to write code to create those tuple objects, or an object that contains the start and end values. 您需要编写代码来创建这些元组对象或包含开始和结束值的对象。

That works fine when you're selecting the random numbers that go with the first index, 0. But what about when you're selecting the random numbers that go with 5? 当您选择第一个索引为0的随机数时,这种方法很好用。但是,当您选择第5个索引的随机数时又如何呢? No problem. 没问题。 We just map the 0-based numbers using an offset and modulo arithmetic. 我们仅使用偏移量和模运算来映射从0开始的数字。 It's easy enough, just wrap the code above with this loop: 这很容易,只需使用以下循环包装上面的代码:

for i in 0 to maxNumber
    // insert code above
    // now we map the numbers from the selections array
    selections[0] = (selections[0] + i) % maxNumber
    selections[1] = (selections[1] + i) % maxNumber
    selections[2] = (selections[2] + i) % maxNumber
end for

How does that work? 这是如何运作的? Let's say that i == 4, and the numbers you selected were 7, 9, and 4. Then: 假设i == 4,您选择的数字是7、9和4。然后:

7 + 4 = 11  11 % 10 = 1
9 + 4 = 13  13 % 10 = 3
4 + 4 = 8   8 % 10 = 8

This should run a lot faster than the solutions that have you shuffling an array every time, and removing and adding things to a large list. 这比每次都对数组进行混洗并将其删除并将其添加到大型列表的解决方案要快得多。 Instead, you're working with a very small list of ranges. 相反,您正在处理非常小的范围列表。 The algorithm is a little more complicated, but it's doing a lot less work. 该算法稍微复杂一些,但工作量却少得多。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM