在Python中随机选择数组中连续的元素

8

我有一个索引列表,例如0...365,我想从中选择一些,随机且不重复选择连续的子区间

index = [i+1 for i in range(365) ] 
#n could be 3
for i in range(n):
   exclusion_regions.append( get_random_contiguous_region(index) )

有人对于实现get_random_contiguous_region()有什么建议吗?


1
子区域的大小应该是多少? - Ffisegydd
我想保持这种灵活性,但为了使它更简单,区域的总长度应该在数组的5%到15%之间,并且每个区域应该是其长度的30%。因此,每个区域应该大约有10个项目。 - Edmon
你的代码中的 n 是什么? - Padraic Cunningham
对于这个例子,假设是3。 - Edmon
所以从数组中随机选择三个切片? - Padraic Cunningham
是的,非重叠的。 - Edmon
4个回答

2

您可以做以下事情:

import random

n = 3
index = [i+1 for i in range(10) ] 
slices = sorted(random.sample(range(0, len(index)), 2*n))
[index[start:end] for start, end in zip(slices[::2], slices[1::2])]

这就是为什么我使用了 set,元素是唯一的,所以没有重叠的片段。 - elyase
你最终可能会得到一个单独的切片。 - Padraic Cunningham
你说得对,我在你提到之后注意到了那个 bug,现在应该已经修正了。 - elyase
你可能会得到非常不同大小的切片,我认为这不是要求,你不能用列表推导来做到这一点。 - Padraic Cunningham

2
这是一个相对简单的递归方法:索引列表被随机分成给定大小范围内连续的子序列。然后,三个这样的子序列被选择。
indexes = range(1, 80)
from random import randint, sample 

# recursive division of the sequence
def get_random_division(lst, minsize, maxsize):
    split_index = randint(minsize, maxsize)
    # if the remaining list would get too small, return the unsplit one
    if minsize>len(lst)-split_index:
        return [lst]
    return [lst[:split_index]] + get_random_division(lst[split_index:], minsize, maxsize)

# determine size range of the subdivisions
minsize, maxsize = 5, int(0.15*len(data))
# choose three of the subdivided sequences
sample(get_random_division(indexes, minsize, maxsize), 3)

输出:

[[17, 18, 19, 20, 21, 22, 23, 24, 25, 26],
 [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46],
 [1, 2, 3, 4, 5]]

1
我们需要一个while循环,以确保不会产生重叠,并且您可以检查切片的长度是否满足任何其他标准。使用列表推导式,您无法指定不同的标准:如果您想要随机切片占总列表大小的大约5到15%,并且样本大小约为30%:
from random import choice
from numpy import arange

index = [i + 1 for i in range(365)]
choices = []
seen = set()
ar = arange(0.05,.16, .01)
ln = len(index)
sample_size = 0
while sample_size < ln * .30:
    perc = choice(ar)  # get random 5, 10, 15 percent slices
    size = int(ln * perc)
    ch = choice(index[:-size+1]) # avoid falling off the side
    rn = index[ch:ch+size]
    if len(rn) == size and not seen.intersection(rn):
        seen.update(rn)
        choices.append(rn)
        sample_size += len(rn)
print(choices)

Padraic,如果ch+20超出了索引范围,它会绕回来吗? - Edmon
@Edmon,当获取起始点时,只给出index[:-20]的选择,这样你就永远不会超出范围,如果你想要环绕,也可以,但这需要更多的工作。你想要随机长度的切片还是全部相同长度的切片? - Padraic Cunningham
我希望得到一个随机长度的“安全”长度的切片(即总长度不超过数组的20-30%)。我没有要求它,因为我认为这可能太多了(而且我可以解决这个问题)。如果不会太麻烦,请添加。顺便说一下,我不能点赞,只能取消投票 :-/ - Edmon
@Edmon,我添加了代码,使得切片的大小为总大小的25%,显然n会影响您选择的百分比,如果您想要不同的大小,请选择一个随机数进行除法运算。 - Padraic Cunningham
别担心,生活中有很多事情比得到赞更重要;) 我只是觉得有趣的是,一个实际解决了提问者需求的答案却被踩。 - Padraic Cunningham
显示剩余2条评论

1

这里提供了一种解决方案,它以符号方式处理范围,而不是考虑每个项目。

(对于您正在处理的小基础,这可能有些过度设计,但对于包含数万项的范围,它将更加高效。)


编辑:我已经更新了代码,现在长度可以被指定为整数或返回整数的0参数函数。现在你可以使用分布来指定长度,而不仅仅是一个常数。


import random

def range_intersection(a, b):
    if a.step == b.step == 1:
        return range(max(a.start, b.start), min(a.stop, b.stop), 1)
    else:
        # here be dragons!
        raise NotImplemented

def random_subrange(length, range_):
    start = random.randrange(
        range_.start,
        range_.stop - length * range_.step,
        range_.step
    )
    stop = start + length * range_.step
    return range(start, stop, range_.step)

def const_fn(n):
    def fn():
        return n
    return fn

def random_distinct_subranges(num, length, range_):
    if not callable(length):
        length = const_fn(length)
    ranges = []
    for n in range(num):
        while True:
            new_range = random_subrange(length(), range_)
            if not any(range_intersection(new_range, r) for r in ranges):
                ranges.append(new_range)
                break
    ranges.sort(key = lambda r: r.start)
    return ranges

那么

days = range(1, 366)

# pick 3 periods randomly without overlapping
periods = random_distinct_subranges(3, lambda:random.randint(5,15), days)
print(periods)

这将得到类似于这样的结果

[range(78, 92), range(147, 155), range(165, 173)]

可以像迭代器一样进行迭代。
from itertools import chain

rand_days = chain(*periods)
print(list(rand_days))

给予
[78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 147, 148, 149, 150, 151, 152, 153, 154, 165, 166, 167, 168, 169, 170, 171, 172]

我遇到了一个错误,错误消息说列表没有属性start:def random_subrange(length, range_): start = random.randrange( range_.start, range_.stop - length * range_.step, range_.step ) - Edmon
@Edmon:你使用的是哪个版本的Python?这应该适用于任何Python 3.x版本;我没有尝试过Python 2.x,那可能就是问题所在。 - Hugh Bothwell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接