【问题标题】:Random contiguous slice of list in Python based on a single random integerPython中基于单个随机整数的列表的随机连续切片
【发布时间】:2015-05-29 14:24:01
【问题描述】:

使用单个随机数和一个列表,如何返回该列表的随机切片?

例如,给定列表[0,1,2],随机连续切片有七种种可能性:

  1. [ ]
  2. [ 0 ]
  3. [ 0, 1 ]
  4. [ 0, 1, 2 ]
  5. [ 1 ]
  6. [ 1, 2]
  7. [ 2 ]

必须有一种方法来生成单个随机数并使用该值来计算起始索引和结束/长度,而不是获取随机起始索引和随机结束索引。

我需要这样,以确保这 7 种可能性具有相等的概率。

【问题讨论】:

  • list_slice[random.randint(0,len(list)) :]

标签: python python-2.7 math random


【解决方案1】:

首先创建所有可能的切片索引。

[0:0][1:1] 等是等价的,所以我们只包括其中一个。

最后你选择一个随机索引对,并应用它。

import random

l = [0, 1, 2]

combination_couples = [(0, 0)]
length = len(l)

# Creates all index couples.
for j in range(1, length+1):
    for i in range(j):
        combination_couples.append((i, j))

print(combination_couples)

rand_tuple = random.sample(combination_couples, 1)[0]
final_slice = l[rand_tuple[0]:rand_tuple[1]]

print(final_slice) 

为了确保我们得到所有这些:

for i in combination_couples:
    print(l[i[0]:i[1]])

或者,用一些数学...

对于长度为 3 的列表,有 0 到 3 个可能的索引号,即 n=4。你有 2 个,即 k=2。第一个索引必须小于第二个,因此我们需要计算组合as described here

from math import factorial as f    

def total_combinations(n, k=2):
    result = 1

    for i in range(1, k+1):
        result *= n - k + i
    result /= f(k)
    # We add plus 1 since we included [0:0] as well.
    return result + 1

print(total_combinations(n=4))    # Prints 7 as expected.

【讨论】:

  • 非常直接,因此非常适合小型列表。对于大型列表,生成所有元组可能需要太多内存,因为元组的数量是原始列表长度的二次方。
【解决方案2】:

将空列表赋予与其他列表相同的权重有点奇怪。如果列表上有 n 个元素,则空列表的权重为 0 或 n+1 倍其他列表更自然。但是,如果您希望它具有相同的权重,则可以这样做。

有 n*(n+1)/2 个非空的连续子列表。您可以通过终点(从 0 到 n-1)和起点(从 0 到终点)来指定这些值。

生成一个从 0 到 n*(n+1)/2 的随机整数 x。

如果 x=0,则返回空列表。否则,x 从 1 到 n(n+1)/2 不均匀分布。

计算 e = floor(sqrt(2*x)-1/2)。这需要值 0、1、1、2、2、2、3、3、3、3 等。

计算 s = (x-1) - e*(e+1)/2。这需要值 0, 0, 1, 0, 1, 2, 0, 1, 2, 3, ...

返回从索引 s 开始到索引 e 结束的区间。

(s,e) 取值 (0,0),(0,1),(1,1),(0,2),(1,2),(2,2),...

import random
import math

n=10

x = random.randint(0,n*(n+1)/2)

if (x==0):
    print(range(n)[0:0]) // empty set
    exit()

e = int(math.floor(math.sqrt(2*x)-0.5))
s = int(x-1 - (e*(e+1)/2))

print(range(n)[s:e+1]) // starting at s, ending at e, inclusive

【讨论】:

  • 无论何时s==e,您的代码都会重复生成空切片。
  • @MvG:正如我在正文中所述,我的意思是将 [s,e] 解释为以索引 s 开头、以索引 e 结尾的子列表。我会寻找一种不那么模糊的方式来打印输出。
【解决方案3】:

只需固定一个顺序,对所有可能的切片进行排序,然后找出一种方法将所有切片列表中的索引转回切片端点。例如,您使用的顺序可以描述为

  • 空切片在所有其他切片之前
  • 非空切片按起点排序
  • 起点相同的切片按终点排序

所以索引0 应该返回空列表。索引 1n 应该返回 [0:1][0:n]。指数n+1n+(n-1)=2n-1 将是[1:2][1:n]2nn+(n-1)+(n-2)=3n-3 将是 [2:3][2:n] 等等。您在此处看到一个模式:给定起点的最后一个索引的形式为n+(n-1)+(n-2)+(n-3)+…+(n-k),其中k 是序列的起始索引。那是arithmetic series,所以总和是(k+1)(2n-k)/2=(2n+(2n-1)k-k²)/2。如果您将该术语设置为等于给定索引,并将solve that 设置为k,您将得到一些涉及平方根的公式。然后,您可以使用上限函数将其转换为 k 的整数值,对应于该起点的最后一个索引。一旦知道k,计算终点就相当容易了。

但是上面解中的二次方程让事情变得非常丑陋。因此,您最好使用其他订单。现在我想不出一种方法可以避免这样的二次项。 his answer 中使用的道格拉斯顺序并没有避免平方根,但至少他的平方根更简单一些,因为他首先按端点排序。您的问题和我的答案中的顺序称为lexicographical order,他的将称为reverse lexicographical,并且通常更容易处理,因为它不依赖于n。但由于大多数人首先考虑的是正常(前向)字典顺序,因此这个答案对许多人来说可能更直观,甚至可能是某些应用程序所需的方式。

这是一段 Python 代码,它按顺序列出所有序列元素,并按照我上面描述的方式从索引 i 转换到端点 [k:m]

from math import ceil, sqrt
n = 3
print("{:3} []".format(0))
for i in range(1, n*(n+1)//2 + 1):
    b = 1 - 2*n
    c = 2*(i - n) - 1
    # solve k^2 + b*k + c = 0
    k = int(ceil((- b - sqrt(b*b - 4*c))/2.))
    m = k + i - k*(2*n-k+1)//2
    print("{:3} [{}:{}]".format(i, k, m))

c 中的 - 1 术语并非来自我上面介绍的数学公式。这更像是从i 的每个值中减去 0.5。这确保即使sqrt 的结果稍微太大,您最终也不会得到太大的k。因此,该术语说明了数字不精确性,应该使整个事情变得非常稳健。

术语k*(2*n-k+1)//2 是属于起点k-1 的最后一个索引,因此i 减去该术语就是所考虑的子序列的长度。

您可以进一步简化事情。您可以在循环之外执行一些计算,如果您必须重复选择随机序列,这可能很重要。您可以将b 除以 2,然后在其他许多地方消除该因素。结果可能如下所示:

from math import ceil, sqrt
n = 3
b = n - 0.5
bbc = b*b + 2*n + 1
print("{:3} []".format(0))
for i in range(1, n*(n+1)//2 + 1):
    k = int(ceil(b - sqrt(bbc - 2*i)))
    m = k + i - k*(2*n-k+1)//2
    print("{:3} [{}:{}]".format(i, k, m))

【讨论】:

    【解决方案4】:

    必须有一种方法可以生成单个随机数并使用该值来计算起始索引和结束/长度。

    很难说哪种方法最好,但如果您只想将单个随机数绑定到您的连续切片,您可以使用模数。

    给定一个列表 l 和一个随机数 r 你可以像这样得到你的连续切片:

    l[r % len(l) : some_sparkling_transformation(r) % len(l)]
    

    some_sparkling_transformation(r) 是必不可少的。这取决于您的需求,但由于我在您的问题中没有看到任何特殊要求,例如:

    l[r % len(l) : (2 * r) % len(l)]
    

    这里最重要的是切片的左右边缘都与r相关。这使得定义这种不遵循任何可观察模式的连续切片成为一个问题。上面的示例(使用2 * r)生成的切片始终为空列表或遵循[a : 2 * a] 的模式。

    让我们使用一些直觉。我们知道我们希望以连续切片的形式找到数字r 的良好随机表示。结果我们需要找到两个数字:ab,它们分别是切片的左右边缘。假设r 是一个很好的随机数(我们在某种程度上喜欢它),我们可以说a = r % len(l) 是一个很好的方法。

    现在让我们尝试查找b。生成另一个好的随机数的最佳方法是使用支持 seed(两者)的随机数生成器(randomnumpy)。 random 模块示例:

    import random
    def contiguous_slice(l, r):
        random.seed(r)
        a = int(random.uniform(0, len(l)+1))
        b = int(random.uniform(0, len(l)+1))
        a, b = sorted([a, b])
        return l[a:b]
    

    祝你好运,玩得开心!

    【讨论】:

    • 您在帖子末尾的代码将过度表示具有大 a 的切片,并且还会过度表示空切片。
    • 没错,谢谢!我更正了大的a(我认为?),我同意空切片将被过度代表。这里需要稍微不同的方法......
    猜你喜欢
    • 2018-11-06
    • 1970-01-01
    • 1970-01-01
    • 2011-08-11
    • 1970-01-01
    • 1970-01-01
    • 2014-05-15
    • 1970-01-01
    • 2011-05-09
    相关资源
    最近更新 更多