简体   繁体   中英

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. 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.

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;

At the second iteration I need to generate [1, r1, r2, r3] where r1, r2, r3 are distinct random numbers between 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;

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.

Improving Miroslav's approach by removing the redundant filtering:

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; if it's equal to or greater than the number in the selection, add 1 to it; add it to the selection. Then choose a random integer from 0 to 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; 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. 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.

Original answer

You should be able to do this in O(n) time, without having to generate a temporary list or do any shuffling.

Imagine that you have a list of 10 numbers:

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. So pick a random number greater than or equal to 1, and less than 9. And add 1 to it. Say you pick 5. Your list is now separated into four sections:

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

Add those two ranges, [1-4] and [6-9] to a list. 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:

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]. 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]. 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. 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? No problem. We just map the 0-based numbers using an offset and modulo arithmetic. 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:

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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