簡體   English   中英

對於列表中的每個元素,請選擇該元素和3個隨機非重復元素

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

我有一個大小為202的列表,整數為0 ... 201。 我需要遍歷此列表,對於每個元素,我需要選擇4個元素,其中一個是當前元素,其余3個是隨機的,非重復的元素。

例如,

在第一次迭代中,我需要生成[0,r1,r2,r3],其中r1,r2,r3是1-201之間的不同隨機數;

在第二次迭代中,我需要生成[1,r1,r2,r3],其中r1,r2,r3是0、2-201之間的不同隨機數;

在第三次迭代中,我需要生成[2,r1,r2,r3],其中r1,r2,r3是0-1、3-201之間的不同隨機數;

我知道一種方法可以執行此操作,但它會占用太多時間和空間,而且由於我將在android應用程序的主線程中執行此操作,因此我可能應該做一些更有效的事情。

通過刪除冗余過濾來改進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);
}

如果您不想生成具有所有值可供選擇的數組(如果范圍更大,可能會出現問題),顯而易見的算法是:

將固定編號添加到選擇中。 從0到200中選擇一個隨機整數; 如果等於或大於選擇中的數字,則將其加1; 將其添加到選擇中。 然后從0到199中選擇一個隨機整數; 將其與選擇中的每個數字進行比較; 每當您發現它等於或大於選擇中的數字時,就將其加1; 如果較小,或者您到達列表的末尾,請在此處插入。 繼續執行198、197,直到選擇了足夠的數字。 例:

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]  

這是可能的解決方案之一:

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));
}

這應該給出以下輸出:

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]

... 等等。

還有另一種方法比下面的原始答案更容易。 它需要制作一個單獨的列表,從中選擇內容,但是您不必對其進行隨機組合。

這個想法是您從第二個列表中進行隨機選擇。 進行選擇時,然后將所選項目與列表中的最后一個項目交換,並減少可用於下一個選擇的項目數。 這樣,您就無法再次選擇相同的號碼。 而且,如果您選擇的一項是索引號,則只需交換該數字即可,而不保留它。 這很簡單:

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

隨着時間的推移,這最終會擾亂選擇列表,但這不會影響選擇的“隨機性”。 這確實很容易編寫代碼,不需要重新排列列表,並且比我的原始響應更容易理解。 對於每個數字,它將最多從列表中隨機選擇4個(但通常為3個)。

原始答案

您應該能夠在O(n)時間內執行此操作,而不必生成臨時列表或進行任何改組。

假設您有一個包含10個數字的列表:

0 1 2 3 4 5 6 7 8 9

因此,您從0開始。現在,您需要三個非重復的隨機數,介於1到9之間(含1和9)。 因此,請選擇一個大於或等於1且小於9的隨機數。並將其加1。 假設您選擇5。現在,您的列表分為四個部分:

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

將這兩個范圍[1-4]和[6-9]添加到列表中。 選擇其中一個並從中選擇一個數字。 假設您選擇了第一個列表,然后選擇了數字2。現在您有了范圍:

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

將兩個新范圍([1-1]和[3-4])添加到已經包含[6-9]的范圍列表中。 現在,再次從列表中選擇一個范圍,然后從中選擇一個數字。

事情變得有點奇怪,因為從范圍中選擇一個數字並不總是將其分為兩個。 例如,如果您從范圍[1-4]中選擇了數字4,則將剩下單個范圍[1-3]。 但是我們可以很容易地解決這個問題。

這是一些偽代碼。

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.

請注意,這是偽代碼。 我上面顯示的ranges數組是一個包含元組的數組:范圍的開始和結束。 您需要編寫代碼來創建這些元組對象或包含開始和結束值的對象。

當您選擇第一個索引為0的隨機數時,這種方法很好用。但是,當您選擇第5個索引的隨機數時又如何呢? 沒問題。 我們僅使用偏移量和模運算來映射從0開始的數字。 這很容易,只需使用以下循環包裝上面的代碼:

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

這是如何運作的? 假設i == 4,您選擇的數字是7、9和4。然后:

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

這比每次都對數組進行混洗並將其刪除並將其添加到大型列表的解決方案要快得多。 相反,您正在處理非常小的范圍列表。 該算法稍微復雜一些,但工作量卻少得多。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM