【问题标题】:Itertools zip_longest with first item of each sub-list as padding values in stead of None by defaultItertools zip_longest,每个子列表的第一项作为填充值,而不是默认情况下为 None
【发布时间】:2019-12-10 21:05:58
【问题描述】:

我有这份清单:

cont_det = [['TASU 117000 0', "TGHU 759933 - 0", 'CSQU3054383', 'BMOU 126 780-0', "HALU 2014 13 3"], ['40HS'], ['Ha2ardous Materials', 'Arm5 Maehinery']]

实际上cont_det 是一个巨大的列表,其中包含许多子列表,每个子列表的长度不规则。这只是一个示范案例。我想得到以下输出:

[['TASU 117000 0', '40HS', 'Ha2ardous Materials'], 
 ['TGHU 759933 - 0', '40HS', 'Arm5 Maehinery'], 
 ['CSQU3054383', '40HS', 'Ha2ardous Materials'], 
 ['BMOU 126 780-0', '40HS', 'Ha2ardous Materials'], 
 ['HALU 2014 13 3', '40HS', 'Ha2ardous Materials']]

这背后的逻辑是zip_longest列表列表,但如果有任何子列表的长度小于子列表所有长度的最大值(这里是第一个子列表的5) , 然后代替默认的fillvalue=None 取该子列表的第一项 - 如在第二个子列表的情况下所见,所有反映的填充值都是相同的,对于第三个,最后三个由第一个值填充。

我已经用这段代码得到了结果:

from itertools import zip_longest as zilo
from more_itertools import padded as pad
max_ = len(max(cont_det, key=len))
for i, cont_row in enumerate(cont_det):
    if len(cont_det)!=max_:
        cont_det[i] = list(pad(cont_row, cont_row[0], max_))
cont_det = list(map(list, list(zilo(*cont_det))))

这给了我预期的结果。相反,如果我完成 list(zilo(*cont_det, fillvalue='')) 我会得到这个:

[('TASU 117000 0', '40HS', 'Ha2ardous Materials'), 
 ('TGHU 759933 - 0', '', 'Arm5 Maehinery'), 
 ('CSQU3054383', '', ''), 
 ('BMOU 126 780-0', '', ''), 
 ('HALU 2014 13 3', '', '')]

是否有任何其他过程(例如映射任何函数左右)到zip_longest 函数的参数fillvalue,这样我就不必遍历列表来填充每个子列表的长度在那之前最长的子列表中,这件事可以在一行中完成,只有zip_longest

【问题讨论】:

  • 这是一个巧妙的拼图。您的解决方案唯一可能的限制是它需要预先设置长度,这使得 zip_longest 和 pad 毫无意义。除此之外,我喜欢它。

标签: python python-3.x list itertools


【解决方案1】:

您可以通过next 查看每个迭代器,以提取第一项(“head”),然后创建一个标记迭代器结束的sentinel 对象,最后chain 将所有内容重新组合在一起通过以下方式:head -> remainder_of_iterator -> sentinel -> it.repeat(head).

这使用it.repeat 在到达迭代器末尾时无限重播第一个项目,因此我们需要引入一种方法来在最后一个迭代器命中其sentinel 对象时停止该过程。为此,我们可以(ab)使用这样一个事实,即如果映射函数引发(或泄漏)StopIteration(例如从在已经耗尽的迭代器上调用的next),map 将停止迭代。或者,我们可以使用 iter 的 2 参数形式在 sentinel 对象上停止(见下文)。

所以我们可以将链式迭代器映射到一个函数上,该函数检查每个项目是否为is sentinel 并执行以下步骤:

  1. if item is sentinel 然后使用一个专用迭代器,该迭代器通过next 产生比迭代器总数少一个项目(因此泄漏StopIteration 用于最后一个标记)并将sentinel 替换为相应的head
  2. else 原件退回即可。

最后,我们可以将 zip 迭代器放在一起 - 它会在最后一个碰到它的 sentinel 对象时停止,即执行“zip-longest”。

总之,以下函数执行上述步骤:

import itertools as it


def solution(*iterables):
    iterators = [iter(i) for i in iterables]  # make sure we're operating on iterators
    heads = [next(i) for i in iterators]  # requires each of the iterables to be non-empty
    sentinel = object()
    iterators = [it.chain((head,), iterator, (sentinel,), it.repeat(head))
                 for iterator, head in zip(iterators, heads)]
    # Create a dedicated iterator object that will be consumed each time a 'sentinel' object is found.
    # For the sentinel corresponding to the last iterator in 'iterators' this will leak a StopIteration.
    running = it.repeat(None, len(iterators) - 1)
    iterators = [map(lambda x, h: next(running) or h if x is sentinel else x,  # StopIteration causes the map to stop iterating
                     iterator, it.repeat(head))
                 for iterator, head in zip(iterators, heads)]
    return zip(*iterators)

如果为了终止 map 迭代器而从映射函数中泄漏 StopIteration 感觉太尴尬,那么我们可以稍微修改 running 的定义以产生额外的 sentinel 并使用 2 参数形式iter 为了停在sentinel

running = it.chain(it.repeat(None, len(iterators) - 1), (sentinel,))
iterators = [...]  # here the conversion to map objects remains unchanged
return zip(*[iter(i.__next__, sentinel) for i in iterators])

如果映射函数内部的 sentinelrunning 的名称解析是一个问题,则可以将它们作为该函数的参数包含在内:

iterators = [map(lambda x, h, s, r: next(r) or h if x is s else x,
                 iterator, it.repeat(head), it.repeat(sentinel), it.repeat(running))
             for iterator, head in zip(iterators, heads)]

【讨论】:

    【解决方案2】:

    这看起来像是某种“矩阵旋转”。

    我在没有使用任何库的情况下完成了它,以便让每个人都清楚。对我来说这很容易。

    from pprint import pprint
    
    cont_det = [
        ['TASU 117000 0', "TGHU 759933 - 0", 'CSQU3054383', 'BMOU 126 780-0', "HALU 2014 13 3"],
        ['40HS'],
        ['Ha2ardous Materials', 'Arm5 Maehinery'],
    ]
    
    
    def rotate_matrix(source):
        result = []
    
        # let's find the longest sub-list length
        length = max((len(row) for row in source))
    
        # for every column in sub-lists create a new row in the resulting list
        for column_id in range(0, length):
            result.append([])
    
            # let's fill the new created row using source row columns data.
            for row_id in range(0, len(source)):
                # let's use the first value from the sublist values if source row list has it for the column_id
                if len(source[row_id]) > column_id:
                    result[column_id].append(source[row_id][column_id])
                else:
                    try:
                        result[column_id].append(source[row_id][0])
                    except IndexError:
                        result[column_id].append(None)
    
        return result
    
    
    pprint(rotate_matrix(cont_det))
    

    当然还有脚本输出

    
    > python test123.py
    [['TASU 117000 0', '40HS', 'Ha2ardous Materials'],
     ['TGHU 759933 - 0', '40HS', 'Arm5 Maehinery'],
     ['CSQU3054383', '40HS', 'Ha2ardous Materials'],
     ['BMOU 126 780-0', '40HS', 'Ha2ardous Materials'],
     ['HALU 2014 13 3', '40HS', 'Ha2ardous Materials']]
    

    无法理解zip_longest 函数。它是解决方案的要求还是您需要一个“有效”的解决方案:) 因为它看起来不像zip_longest 支持任何类型的回调等,我们可以在矩阵中“每个单元格”返回所需的值。

    【讨论】:

    • zip_longest 不是强制性要求,当且仅当我们能够以比我已经尝试过的方式更短的方式完成整个事情(它已经在工作了)。不过,您的代码可以正常工作并提供所需的输出。
    • 是的,我喜欢“以后易于阅读”的解决方案,而不是复杂的单行代码,如果在编写后几天内不使用谷歌搜索 python 文档就无法阅读:D
    【解决方案3】:

    如果您想以通用方式对任意迭代器执行此操作,您可以使用标记值作为默认值,并将其替换为该列的第一个值。这样做的好处是它无需您预先扩展任何内容或知道长度即可工作。

    def zip_longest_special(*iterables):
        def filter(items, defaults):
            return tuple(d if i is sentinel else i for i, d in zip(items, defaults))
        sentinel = object()
        iterables = zip_longest(*iterables, fillvalue=sentinel)
        first = next(iterables)
        yield filter(first, [None] * len(first))
        for item in iterables:
            yield filter(item, first)
    

    【讨论】:

      【解决方案4】:

      答案是否定的。 fillvalue 参数只有 one 含义。无论如何,这里还有另一个答案,很好,但突然被删除了。下面的代码与该代码非常接近,但它适用于 itertools 而不是列表方法。

      from itertools import chain, repeat
      def zilo(data):
          try:
              i1 = next(it := iter(data))
          except StopIteration:
              return zip()
          return zip(chain(i1, repeat(i1[0], len(max(data, key=len))-len(i1))),
                     *(chain(i, repeat(i[0])) for i in it))
      

      【讨论】:

      • 这将特定于带有海象运算符的 python 3.8 版本!
      • @Arkistarvh Kltzuonstev 当然。但是更换它有什么问题呢?
      【解决方案5】:

      添加另一个变体

      def zipzag(fill, *cols):
         
         sizes = [len(col) for col in cols] # size of individual list in nested list
         
         longest = max(*sizes) 
         
         return [[xs[i] if i < sizes[j] else fill(xs) for j, xs in enumerate(cols)]for i in range(longest)] 
      
      cont_det = [['TASU 117000 0', "TGHU 759933 - 0", 'CSQU3054383', 'BMOU 126 780-0', "HALU 2014 13 3"], ['40HS'], ['Ha2ardous Materials', 'Arm5 Maehinery']] 
                                 
      
      print(zipzag(lambda xs: xs[0], *cont_det))                    
      

      生产,

      [['TASU 117000 0', '40HS', 'Ha2ardous Materials'], ['TGHU 759933 - 0', '40HS', 'Arm5 Maehinery'], ['CSQU3054383', '40HS', 'Ha2ardous Materials'], ['BMOU 126 780-0', '40HS', 'Ha2ardous Materials'], ['HALU 2014 13 3', '40HS', 'Ha2ardous Materials']]
      
      [Program finished]
      

      fill 是一个接收列表的函数,它应该返回一些内容以使列表的长度匹配并使 zip 工作。我给出的示例返回列的第一个元素

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-06-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-16
        • 2022-10-07
        相关资源
        最近更新 更多