【问题标题】:For each element in a list, choose that element and 3 random nonrepeating elements对于列表中的每个元素,选择该元素和 3 个随机非重复元素
【发布时间】:2018-02-16 13:32:08
【问题描述】:

我有一个大小为 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 应用程序的主线程中执行此操作,所以我可能应该做一些更有效的事情。

【问题讨论】:

  • 那么问题到底是什么?
  • random.ints(i, list.size()).distinct().limit(3).mapToInt(list::get).toArray()
  • 1.可能只是不要在主线程中执行此操作。 2. 也许你应该实现它,看看它有多糟糕。
  • @M.Prokhorov 如何实现它或者至少是伪代码
  • @Nico 什么时候变成了代码编写服务?

标签: java algorithm random permutation


【解决方案1】:

下面还有另一种比我原来的答案更容易的方法。它需要制作一个单独的列表,您可以从中选择内容,但您不必随机排列。

这个想法是您从第二个列表中进行随机选择。当您进行选择时,您会将所选项目与列表中的最后一个项目交换,并减少可用于下一个选择的项目数。这样,您就不能再次重新选择相同的号码。如果您的选择之一是索引号,您只需交换该数字但不要保留它。真的很简单:

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

这应该比让你每次都洗牌一个数组、删除和添加东西到一个大列表的解决方案快得多。相反,您正在使用一个非常小的范围列表。该算法稍微复杂一些,但它做的工作要少得多。

【讨论】:

  • 我已经实现了其他用户提供的算法。然而,这看起来很感兴趣。明天我会读一读,也许会改变它。谢谢!
  • @Nico:请查看我的更新答案。新解决方案比我原来的解决方案更简单、更快捷。
【解决方案2】:

如果您不想生成一个包含所有可供选择的值的数组(如果范围较大,这可能会出现问题),那么显而易见的算法是这样的:

将固定数字添加到选择中。从 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]  

【讨论】:

    【解决方案3】:

    通过去除冗余过滤来改进 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);
    }
    

    【讨论】:

    • 感谢 Nico 的指正。更新了答案。现在应该是正确的。
    • @mav3n:是的,这绝对是一个很好的解决方案。我完全忘记了简单地删除一个元素并使用流:D
    【解决方案4】:

    这是一种可能的解决方案:

    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]
    

    ...等等。

    【讨论】:

    • 刚刚在 NetBeans 上试用过,效果很好,谢谢!但是我无法在我的项目中实际实现这一点,因为 Java 8 功能 java.util.stream 仅在 API 级别 24 或更高级别上可用,并且我在项目中支持的最低要求是 16 :(
    • @Nico:没问题。如果您不能使用流,也许您可​​以编写一个执行相同操作的方法(返回一个新列表,但没有当前元素)?其余代码应该是一样的。
    • 参考我的答案以获得更好的性能和更少的空间并且没有 java 8 功能
    猜你喜欢
    • 1970-01-01
    • 2011-01-09
    • 2012-01-12
    • 1970-01-01
    • 2021-12-30
    • 2014-07-28
    • 2021-10-23
    • 2014-02-07
    • 1970-01-01
    相关资源
    最近更新 更多