将列表分成几乎相等的部分并返回边界。

3
我希望能够将列表分割并返回边界,以便在扩展时不会存在单个元素。
例如:
split(list(range(1,101)),2) 
# should return 
[[1,50],[51,100]]

split(list(range(1,101)),3)
# should return
[[1,33],[34,66],[67,100]]

split(list(range(1,6)),3)
# should return
[[1,2],[3,5]] # Ideally last element should merge with last if last one has no pair.

到目前为止,我尝试了以下方法:

def split(l, n):
    x = list(range(1, l+1))
    return [x[i:i+n] for i in range(0, len(x), int(len(x)/n))]

print(split(20, 2))

该函数返回[[1, 2], [11, 12]]而不是[[1, 10], [11, 20]]


这是因为第一次i的值为0。所以它将取x [0:2],因此该位置的值将分别为1和2,下一个循环将是11和12。我希望这能帮助您重新考虑逻辑部分。 - Sumit S Chawla
2
我真的觉得你对其中几个期望结果的描述是错误的,如果不是不一致的话。 - user3483203
1
但是在你的问题中,你把它分成了3个部分。 - user3483203
1
split(list(range(1,6)),3) 不应该给出三个分割吗,而不是两个? - pylang
1
@pylang range(1,6) == [1,2,3,4,5] - 如果分成3个,则得到[[1,2],[3,4],[5,None]]。结果中不应该有“没有范围”的元素,而是应该将其前面的范围延伸。因此结果为[[1,2],[3,5]] - Patrick Artner
显示剩余12条评论
4个回答

2

这里有一个非常快速的解决方案,唯一的限制是子列表中最长的将会在最后而不是最前面(但是分组仍然尽可能均匀):

def fast_chunks(start, stop, chunks):
  l = math.ceil(((stop-start)/chunks)-1)
  fin = []
  for j in range(chunks-1):
    fin.append([start, start+l])
    start += l + 1
  fin.append([start, stop])
  return fin

实际应用中:

In [41]: fast_chunks(1, 100, 2)
Out[41]: [[1, 50], [51, 100]]

In [42]: fast_chunks(1, 100, 3)
Out[42]: [[1, 33], [34, 66], [67, 100]]

In [43]: fast_chunks(1, 6, 2)
Out[43]: [[1, 3], [4, 6]]

使用numpynp.array_split的简单解决方案:

def _split(start, stop, n):
    return [[i[0], i[-1]] for i in np.array_split(np.arange(start, stop+1), n)]

实际应用:

In [54]: _split(1, 20, 2)
Out[54]: [[1, 10], [11, 20]]

In [55]: _split(1, 100, 2)
Out[55]: [[1, 50], [51, 100]]

In [56]: _split(1, 6, 2)
Out[56]: [[1, 3], [4, 6]]

在时间方面,fast_chunks 的表现要比其他方法好很多:

In [45]: %timeit chrisz_fast_chunks(1, 1000000, 100)
24.5 µs ± 496 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [27]: %timeit artner_split(1000000, 100)
58 µs ± 1.02 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [22]: %timeit gahan_split(1000000, 100)
77 µs ± 280 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [23]: %timeit chrisz_split(1, 1000000, 100)
1.77 ms ± 39.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [24]: %timeit pylang_split(1000000, 100)
72 ms ± 445 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

2

这应该会有所帮助,它并没有使用整个列表的完整范围,而是只使用了您感兴趣的“边界”,通过一些创造性的zip()操作和修复创建范围后的问题:

def split(l, n):
    stride =(l//n)
    r = list(range(1, l+stride, stride))  # overbuild needed range

    # adjust zipped value by 1 if needed
    k = [ [a , b-1 ] for a,b in zip(r,r[1:]) ]

    # fix special cases 
    for _ in range(n+1): # guesstimate of fixes needed

        if k[-1][0] == k[-1][1]: # last pair identical 
            k.pop()    # remove it and fix new last index to be l  instead
            k[-1][1] = l        
        elif k[-1][1] != l:
            k[-1][1] = l

    return k

print(split(100, 2))
print(split(100, 4))
print(split(103, 3))
print(split(6, 3))

输出:

[[1, 50], [51, 100]]                          # split(100, 2)
[[1, 25], [26, 50], [51, 75], [76, 100]]      # split(100, 4)
[[1, 34], [35, 68], [69, 103]]                # split(103, 3)
[[1, 2], [3, 4], [5, 6]]                      # split(6, 3)

我用你的例子进行了简单的测试,请检查逻辑是否符合所有特殊情况。

特别是“#需要修复的估计”可能太多了...我有一种预感,最多只需要2个就足够了。


@chrisz 谢谢你提供时间 :) - 但我仍觉得这有点作弊。 - Patrick Artner
1
@chrisz - 打字错误:split(6,3) 导致 [1,3],[4,6] - 现已修复并简化了压缩过程。 - Patrick Artner

1
我相信您期望的结果是不一致的,但这里有一些简单的东西可能会对您有所帮助。
使用 more_itertools,一个第三方库(通过 > pip install more_itertools 安装): 代码
import more_itertools as mit


def split(val, n):
    """Return a list of equally divided intervals."""
    a = [list(c)[0] for c in mit.divide(n, range(1, val+1))]
    b = [list(c)[-1] for c in mit.divide(n, range(1, val+1))]
    return list(zip(a, b))

演示

split(100, 2)
# [(1, 50), (51, 100)]

split(99, 3)
# [(1, 33), (34, 66), (67, 99)]

split(100, 3)
# [(1, 34), (35, 67), (68, 100)]

split(20, 2)
# [(1, 10), (11, 20)]

1
def csplit(m,n):
    div_ = m//n
    step = div_ if div_ > 1 else 2  # determine step for range function (at least 2 'alternate steps')
    lis = []
    for i in range(1, m+1, step):
        if (m-(i+max(1, div_-1))) > 1:
            # append list only if remains at least two elements remains 
            lis.append([i,i+max(1, div_-1)])
        else:
            if not m == i:
                # in case if m and i not equal and not more then one element left then construct list which include that element
                lis.append([i, m])
            break  # break the loop from iterating any further
    return lis

if __name__ == "__main__":
    print(csplit(100, 2))
    print(csplit(100, 3))
    print(csplit(5, 3))

输出:

[[1, 50], [51, 100]]
[[1, 33], [34, 66], [67, 100]]
[[1, 2], [3, 5]]

一行代码实现相同效果:
def csplit(m,n):
    return [[i,i+max(1, m//n-1)] if (m-(i+max(1, m//n-1))) > 1 else [i, m] for i in range(1, m+1, max(m//n, 2)) if not i==m]

@chrisz 我在第二个更新的函数中有一个变量未被包含,但没有语法错误。 - Gahan
我将你的答案与Chrisz的答案合并了。我更看重速度,所以一开始接受了Chrisz的答案。感谢你的帮助,如果我有误判,对不起。 - Panos Kalatzantonakis
@PanosKal; 你也可以在这个帖子里发布自己的答案。我想看看另一种方式。 - Gahan

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