簡體   English   中英

Itertools zip_longest 將每個子列表的第一項作為填充值而不是默認情況下的 None

[英]Itertools zip_longest with first item of each sub-list as padding values in stead of None by default

我有這個列表列表:

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=''))我完成了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來完成?

你可以窺視到每個通過迭代器的next ,以便提取的第一項(“頭”),然后創建一個sentinel對象標記迭代結束,最后chain一切重新走到一起的方式如下: head -> remainder_of_iterator -> sentinel -> it.repeat(head)

一旦到達迭代器的末尾,這將使用it.repeat無限重播第一個項目,因此我們需要引入一種方法來在最后一個迭代器命中其sentinel對象時停止該過程。 為此,我們可以 (ab) 使用map停止迭代的事實,如果映射函數引發(或泄漏) StopIteration ,例如從next在已經耗盡的迭代器上調用。 或者,我們可以使用iter的 2-argument 形式來停止sentinel對象(見下文)。

因此,我們可以將鏈式迭代器映射到一個函數上,該函數檢查每個項目是否is sentinel並執行以下步驟:

  1. if item is sentinel則使用一個專用迭代器,該迭代器通過next產生的 item 少於迭代器的總數(因此泄漏了最后一個 sentinel 的StopIteration ),並用相應的head替換了sentinel
  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)

如果從映射函數中泄漏StopIteration以終止map迭代器感覺太尷尬,那么我們可以稍微修改running的定義以產生額外的sentinel並使用iter的 2-argument 形式來停止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])

如果sentinel和從映射函數內部running的名稱解析是一個問題,它們可以作為該函數的參數包含在內:

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)]

這看起來像是某種“矩陣旋轉”。

我已經做到了,沒有任何用來讓每個人都清楚的庫。 對我來說這很容易。

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支持任何類型的回調等,我們可以在矩陣中“每個單元格”返回所需的值。

如果您想對任意迭代器以一般方式執行此操作,您可以使用標記值作為默認值,並將其替換為該列的第一個值。 這樣做的優點是它不需要您預先擴展任何東西或知道長度。

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)

答案是不。 fillvalue參數只有一種含義。 無論如何,這里還有另一個答案,很好,但突然被刪除了。 下面的代碼與該代碼非常接近,但它適用於itertools而不是 list 方法。

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))

添加另一個變體

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 工作。 我給出的例子返回列的第一個元素

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM