代码之家  ›  专栏  ›  技术社区  ›  James Brooks

在python中每n个项拆分一个生成器/iterable(splitever)

  •  28
  • James Brooks  · 技术社区  · 16 年前

    我试图用python编写haskel函数'splitevery'。这是它的定义:

    splitEvery :: Int -> [e] -> [[e]]
        @'splitEvery' n@ splits a list into length-n pieces.  The last
        piece will be shorter if @n@ does not evenly divide the length of
        the list.
    

    它的基本版本工作得很好,但是我想要一个与生成器表达式、列表和迭代器一起工作的版本。 ,如果有一个发电机作为输入,它应该返回一个发电机作为输出!

    测验

    # should not enter infinite loop with generators or lists
    splitEvery(itertools.count(), 10)
    splitEvery(range(1000), 10)
    
    # last piece must be shorter if n does not evenly divide
    assert splitEvery(5, range(9)) == [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
    
    # should give same correct results with generators
    tmp = itertools.islice(itertools.count(), 10)
    assert list(splitEvery(5, tmp)) == [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
    

    当前实施

    这是我目前拥有的代码,但它不适用于一个简单的列表。

    def splitEvery_1(n, iterable):
        res = list(itertools.islice(iterable, n))
        while len(res) != 0:
            yield res
            res = list(itertools.islice(iterable, n))
    

    这个不适用于生成器表达式(感谢jellybean修复它):

    def splitEvery_2(n, iterable): 
        return [iterable[i:i+n] for i in range(0, len(iterable), n)]
    

    必须有一段简单的代码来进行拆分。我知道我可以有不同的功能,但似乎这应该是一件容易的事情。我可能被一个不重要的问题困住了,但它真的困扰着我。


    它类似于石斑鱼 http://docs.python.org/library/itertools.html#itertools.groupby 但我不想让它填充额外的值。

    def grouper(n, iterable, fillvalue=None):
        "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
        args = [iter(iterable)] * n
        return izip_longest(fillvalue=fillvalue, *args)
    

    它确实提到了截断最后一个值的方法。这也不是我想要的。

    保证了算法的从左到右的评价顺序。这使得使用izip(*[iter(s)]*n将数据序列聚类成n个长度的组成为可能。

    list(izip(*[iter(range(9))]*5)) == [[0, 1, 2, 3, 4]]
    # should be [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
    
    13 回复  |  直到 8 年前
        1
  •  42
  •   Roberto Bonvallet    16 年前
    from itertools import islice
    
    def split_every(n, iterable):
        i = iter(iterable)
        piece = list(islice(i, n))
        while piece:
            yield piece
            piece = list(islice(i, n))
    

    一些测试:

    >>> list(split_every(5, range(9)))
    [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
    
    >>> list(split_every(3, (x**2 for x in range(20))))
    [[0, 1, 4], [9, 16, 25], [36, 49, 64], [81, 100, 121], [144, 169, 196], [225, 256, 289], [324, 361]]
    
    >>> [''.join(s) for s in split_every(6, 'Hello world')]
    ['Hello ', 'world']
    
    >>> list(split_every(100, []))
    []
    
        2
  •  18
  •   Elliot Cameron    8 年前

    这是一个简单的版本。就像哈斯克尔的一样,它很懒。

    from itertools import islice, takewhile, repeat
    split_every = (lambda n, it:
        takewhile(bool, (list(islice(it, n)) for _ in repeat(None))))
    

    这需要你使用 iter 之前 打电话 split_every .

    例子:

    list(split_every(5, iter(xrange(9))))
    [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
    

    虽然不是一行程序,但下面的版本不需要您调用 伊特尔 这可能是一个常见的陷阱。

    from itertools import islice, takewhile, repeat
    
    def split_every(n, iterable):
        """
        Slice an iterable into chunks of n elements
        :type n: int
        :type iterable: Iterable
        :rtype: Iterator
        """
        iterator = iter(iterable)
        return takewhile(bool, (list(islice(iterator, n)) for _ in repeat(None)))
    

    (感谢@eli korvigo的改进。)

        3
  •  6
  •   acushner    8 年前

    建立公认的答案并使用 iter (当传递第二个参数时,它会调用第一个参数,直到它接收到第二个参数),您可以很容易地做到这一点:

    Python 3:

    from itertools import islice
    
    def split_every(n, iterable):
        iterable = iter(iterable)
        yield from iter(lambda: list(islice(iterable, n)), [])
    

    Python 2:

    def split_every(n, iterable):
        iterable = iter(iterable)
        for chunk in iter(lambda: list(islice(iterable, n)), []):
            yield chunk
    
        4
  •  5
  •   Elliot Cameron    8 年前

    more_itertools 有一个 chunked 功能:

    import more_itertools as mit
    
    
    list(mit.chunked(range(9), 5))
    # [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
    
        5
  •  3
  •   Andrey Cizov    9 年前

    一个一行、可内联的解决方案(支持v2/v3、迭代器、使用标准库和单个生成器理解):

    import itertools
    def split_groups(iter_in, group_size):
         return ((x for _, x in item) for _, item in itertools.groupby(enumerate(iter_in), key=lambda x: x[0] // group_size))
    
        6
  •  2
  •   Community Mohan Dere    9 年前

    我想 those questions 几乎相等

    稍微改变一下以裁剪最后一个,我认为一个好的解决方案是:

    from itertools import *
    def iter_grouper(n, iterable):
        it = iter(iterable)
        item = itertools.islice(it, n)
        while item:
            yield item
            item = itertools.islice(it, n)
    

    对于支持切片(列表、字符串、元组)的对象,我们可以执行以下操作:

    def slice_grouper(n, sequence):
       return [sequence[i:i+n] for i in range(0, len(sequence), n)]
    

    现在只需要分配正确的方法:

    def grouper(n, iter_or_seq):
        if hasattr(iter_or_seq, "__getslice__"):
            return slice_grouper(n, iter_or_seq)
        elif hasattr(iter_or_seq, "__iter__"):
            return iter_grouper(n, iter_or_seq)
    

    我想你可以再擦亮一点。

        7
  •  2
  •   Ashley Waite    9 年前

    我在尝试分割批处理时也遇到了这个问题,但是在流的生成器上执行,所以这里的大多数解决方案都不适用,或者在python 3中不起作用。

    对于仍在这方面遇到困难的人们,下面是使用itertools的一般解决方案:

    from itertools import islice, chain
    
    def iter_in_slices(iterator, size=None):
        while True:
            slice_iter = islice(iterator, size)
            # If no first object this is how StopIteration is triggered
            peek = next(slice_iter)
            # Put the first object back and return slice
            yield chain([peek], slice_iter)
    
        8
  •  1
  •   Johannes Charra    16 年前

    为什么不这样做呢?看起来像你的 splitEvery_2 功能。

    def splitEveryN(n, it):
        return [it[i:i+n] for i in range(0, len(it), n)]
    

    实际上,它只会从解决方案的切片中去掉不必要的步距。:)

        9
  •  1
  •   justhalf    12 年前

    这是一个对list和generator都有效的答案:

    from itertools import count, groupby
    def split_every(size, iterable):
        c = count()
        for k, g in groupby(iterable, lambda x: next(c)//size):
            yield list(g) # or yield g if you want to output a generator
    
        10
  •  0
  •   Hamish Grubijan    16 年前

    下面是处理列表与迭代器的方法:

    def isList(L): # Implement it somehow - returns True or false
    ...
    return (list, lambda x:x)[int(islist(L))](result)
    
        11
  •  0
  •   Rusty Rob    14 年前
    def chunks(iterable,n):
        """assumes n is an integer>0
        """
        iterable=iter(iterable)
        while True:
            result=[]
            for i in range(n):
                try:
                    a=next(iterable)
                except StopIteration:
                    break
                else:
                    result.append(a)
            if result:
                yield result
            else:
                break
    
    g1=(i*i for i in range(10))
    g2=chunks(g1,3)
    print g2
    '<generator object chunks at 0x0337B9B8>'
    print list(g2)
    '[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81]]'
    
        12
  •  -1
  •   hostingutilities.com    13 年前

    这就行了

    from itertools import izip_longest
    izip_longest(it[::2], it[1::2])
    

    在那里,它是一些可以接受的


    例子:

    izip_longest('abcdef'[::2], 'abcdef'[1::2]) -> ('a', 'b'), ('c', 'd'), ('e', 'f')
    

    让我们把这个分解

    'abcdef'[::2] -> 'ace'
    'abcdef'[1::2] -> 'bdf'
    

    如您所见,切片中的最后一个数字是指定用于拾取项目的间隔。您可以阅读有关使用扩展切片的更多信息 here .

    这个 zip 函数从第一个iterable中获取第一个item,并将其与第一个item和第二个iterable组合。然后,zip函数对第二个和第三个项执行相同的操作,直到其中一个iterable值用完为止。

    结果是一个迭代器。如果需要列表,请对结果使用list()函数。

        13
  •  -1
  •   Carl    9 年前

    如果你想要一个解决方案

    • 仅使用生成器(不使用中间列表或元组)。
    • 适用于非常长(或无限)的迭代器,
    • 适用于大批量生产,

    这就是诀窍:

    def one_batch(first_value, iterator, batch_size):
        yield first_value
        for i in xrange(1, batch_size):
            yield iterator.next()
    
    def batch_iterator(iterator, batch_size):
        iterator = iter(iterator)
        while True:
            first_value = iterator.next()  # Peek.
            yield one_batch(first_value, iterator, batch_size)
    

    它通过查看迭代器中的下一个值并将其作为第一个值传递给生成器来工作。( one_batch() )这将产生它,连同其余的一批。

    偷看的台阶会升高 StopIteration 当输入迭代器用完并且没有更多的批处理时。因为现在是提高 停止迭代 batch_iterator() 方法,不需要捕获异常。

    这将成批处理来自stdin的行:

    for input_batch in batch_iterator(sys.stdin, 10000):
        for line in input_batch:
            process(line)
        finalise()
    

    我发现这对于处理大量数据和将结果批量上传到外部存储非常有用。

    推荐文章