【问题标题】:Python islice class is not working as expectedPython islice 类未按预期工作
【发布时间】:2021-10-13 15:20:27
【问题描述】:

我正在编写一个函数,它将列表划分为(几乎)相等的“n”个分布。我希望这个函数返回一个生成器,但生成生成器似乎存在问题。该函数适用于可迭代对象。看看这个sn-p:

import itertools


def divide_list(array, n, gen_length=None):
    """
    :param array: some iterable that you wish to divide
    :param n: the number of lists you would like to return
    :param gen_length: The length of the generator if array is a generator. Not necessary for lists and tuples.
    :return: a generator of the divided list

    Example:
    In: list(divide_list([1, 2, 3, 4, 5, 6, 7, 8, 9], 4))
    Out: [[1, 2, 3], [4, 5], [6, 7], [8, 9]]
    """
    if isinstance(array, (list, tuple)):
        floor, rem = divmod(len(array), n)

        items_index = (0, floor)
        for _ in range(n):
            prev, next_ = items_index[0], items_index[1] + 1 if rem > 0 else items_index[1]
            yield array[prev:next_]
            items_index = (next_, next_ + floor)
            rem -= 1
    else:
        floor, rem = divmod(gen_length, n)

        items_index = (0, floor)
        for _ in range(n):
            prev, next_ = items_index[0], items_index[1] + 1 if rem > 0 else items_index[1]
            yield itertools.islice(array, prev, next_)
            items_index = (next_, next_ + floor)
            rem -= 1


if __name__ == '__main__':
    array_ = iter([12, 7, 9, 31, 13, 11, 7, 3])

    print('Generator:')
    print('----------')
    for value in divide_list(array_, 3, gen_length=8):
        print(list(value))

    print('')
    array_ = [12, 7, 9, 31, 13, 11, 7, 3]

    print('List:')
    print('-----')
    for value in divide_list(array_, 3):
        print(value)

这是输出:

Generator:
----------
[12, 7, 9]
[7, 3]
[]

List:
-----
[12, 7, 9]
[31, 13, 11]
[7, 3]

为什么最后一台发电机耗尽?有时,它会耗尽最后两个生成器。

【问题讨论】:

  • 你告诉islice跳过下一个prev元素。
  • 您能详细说明或更改我的代码吗?我希望islice 跳到prev 并转到next_ 然后再做同样的事情直到它到达列表的末尾。 @KellyBundy
  • “我想让 islice 跳到prev”——不,你不会。您认为要跳过的内容已经从迭代器中删除。您不想跳过任何内容。
  • 不,这与我所说的相反。我的意思是你忘记了你已经切掉了元素,所以你不应该尝试另外跳过它们。这些命令中的第二个不应使用3, 6,而应使用0, 3
  • 顺便说一句,这两种情况没必要分开处理。您可以将所有内容都视为迭代器。

标签: python generator itertools


【解决方案1】:

为什么这不起作用的解释是因为您在提供非零起点时使用islice 跳过元素。这里的关键问题是您应该将迭代器提前一个数量,而不是在每次产量时跳过任何一个。这与序列案例不同,在序列案例中,您为每个案例指定明确的索引。

但是,请注意,您不需要以不同的方式处理这些情况。这是一种处理这两种情况的超级简单方法 - 关键是始终使用迭代器:

def divide(iterable, n, length=None):
    if length is None:
        length = len(iterable)
    it = iter(iterable)
    floor, rem = divmod(length, n)
    while result := list(islice(it, floor + bool(rem))):
        yield result
        rem = max(rem - 1, 0)

在 REPL 中:

>>> from itertools import islice
>>> def divide(iterable, n, length=None):
...     if length is None:
...         length = len(iterable)
...     it = iter(iterable)
...     floor, rem = divmod(length, n)
...     while result := list(islice(it, floor + bool(rem))):
...         yield result
...         rem = max(rem - 1, 0)
...
>>> list(divide([1, 2, 3, 4, 5, 6, 7, 8, 9], 4))
[[1, 2, 3], [4, 5], [6, 7], [8, 9]]
>>> list(divide(iter([1, 2, 3, 4, 5, 6, 7, 8, 9]), 4, length=9))
[[1, 2, 3], [4, 5], [6, 7], [8, 9]]

【讨论】:

  • 这是一种更好的方法。
  • 嗯,实际上可能还是不对。参数n 记录为“您想要返回的列表数量”,并且OP 的代码确实将list(divide_list([1, 2], 3)) 变成[[1], [2], []] 而不是您的[[1], [2]]。再说一次,“愿意”不是“必须”,他们的代码可能有问题:-)
  • @KellyBundy 是对的。我第一次没有抓住它,但第一种方法不会产生我想要的分布。第二个是我想要的。
  • @GabeMorris 是的,我编辑并添加了一个答案......在长度小于n 的情况下,我所拥有的可能与您期望的不同。无论如何,直到认为只处理迭代器案例的原则都适用。您可以添加一个计数器并检查该计数器是否为 完全 n 个子列表,而我的有 至少 n 个子列表,但是不会产生空的,只是为了达到 n
【解决方案2】:

问题是你没有考虑到你已经使用了迭代器

>>> import itertools
>>> array_ = iter([12, 7, 9, 31, 13, 11, 7, 3])
>>> list(itertools.islice(array_,0,3))
[12, 7, 9]
>>> list(itertools.islice(array_,3,6)) #where are 31,13 and 11? you skipped them bacause, see below 
[7, 3]
>>> array_ = iter([12, 7, 9, 31, 13, 11, 7, 3])
>>> list(itertools.islice(array_,0,3))
[12, 7, 9]
>>> list(array_) #this is what remains in the iterator
[31, 13, 11, 7, 3]
>>> 

【讨论】:

    【解决方案3】:

    感谢@KellyBundy,我能够修改代码以获得预期的结果。我不知道islice 正在切断生成器,然后将这些值移回索引 0 处的开头。我将其视为在我切断的索引处留下了一个空值。这是修改后的代码:

    import itertools
    
    
    def divide_list(array, n, gen_length=None):
        """
        :param array: some iterable that you wish to divide
        :param n: the number of lists you would like to return
        :param gen_length: The length of the generator if array is a generator. Not necessary for lists and tuples.
        :return: a generator of the divided list
    
        Example:
        In: list(divide_list([1, 2, 3, 4, 5, 6, 7, 8, 9], 4))
        Out: [[1, 2, 3], [4, 5], [6, 7], [8, 9]]
        """
        if isinstance(array, (list, tuple)):
            floor, rem = divmod(len(array), n)
    
            items_index = (0, floor)
            for _ in range(n):
                prev, next_ = items_index[0], items_index[1] + 1 if rem > 0 else items_index[1]
                yield array[prev:next_]
                items_index = (next_, next_ + floor)
                rem -= 1
        else:
            floor, rem = divmod(gen_length, n)
    
            up_to = floor
            for _ in range(n):
                up_to = up_to + 1 if rem > 0 else up_to
                yield itertools.islice(array, up_to)
                up_to = floor
                rem -= 1
    
    
    if __name__ == '__main__':
        array_ = iter([12, 7, 9, 31, 13, 11, 7, 3])
    
        print('Generator:')
        print('----------')
        for value in divide_list(array_, 3, gen_length=8):
            print(list(value))
    
        print('')
        array_ = [12, 7, 9, 31, 13, 11, 7, 3]
    
        print('List:')
        print('-----')
        for value in divide_list(array_, 3):
            print(value)
    
    

    这将输出预期的结果:

    Generator:
    ----------
    [12, 7, 9]
    [31, 13, 11]
    [7, 3]
    
    List:
    -----
    [12, 7, 9]
    [31, 13, 11]
    [7, 3]
    

    【讨论】:

      猜你喜欢
      • 2017-02-26
      • 1970-01-01
      • 1970-01-01
      • 2019-02-18
      • 2017-05-02
      • 2020-10-04
      • 2019-06-20
      • 2017-08-14
      • 1970-01-01
      相关资源
      最近更新 更多