簡體   English   中英

將 python 代碼轉成生成器 function

[英]Turn python code into a generator function

如何將此代碼轉換為生成器 function? 或者我可以通過其他方式避免將所有數據讀入 memory 嗎? 現在的問題是我的 memory 已滿。 執行代碼很長一段時間后我被殺死了。

代碼:

data = [3,4,3,1,2]

def convert(data):
    for index in range(len(data)):
        if data[index] == 0:
            data[index] = 6
            data.append(8)
        elif data[index] == 1:
            data[index] = 0
        elif data[index] == 2:
            data[index] = 1
        elif data[index] == 3:
            data[index] = 2
        elif data[index] == 4:
            data[index] = 3
        elif data[index] == 5:
            data[index] = 4
        elif data[index] == 6:
            data[index] = 5
        elif data[index] == 7:
            data[index] = 6
        elif data[index] == 8:
            data[index] = 7

    return data

for i in range(256):
    output = convert(data)
    print(len(output))

Output:

266396864
290566743
316430103
346477329
376199930
412595447
447983143
490587171
534155549
582826967
637044072
692630033
759072776
824183073
903182618
982138692
1073414138
1171199621
1275457000
1396116848
1516813106
Killed

要回答這個問題:要將 function 變成生成器 function,您所要做的就是yield一些東西。 你可以這樣做:

def convert(data):
    for index in range(len(data)):
        ...

        yield data

然后,您可以像這樣遍歷 output:

iter_converted_datas = convert(data)

for _, converted in zip(range(256), iter_converted_datas):
    print(len(converted))

我還建議對此代碼進行一些改進。

首先,如果您打算遍歷整個列表長度,請使用 enumerate:

for _, converted in enumerate(iter_converted_datas):

接下來要做的是擺脫所有這些 elif 語句。

一個有用的事情可能是為您的生成器 function 提供一個字典參數,告訴它如何轉換數據值(第一個是特殊情況,因為它也附加)。

這是該 dict 的樣子:

replacement_dict = {
    0: 6,
    1: 0,
    2: 1,
    3: 2,
    4: 3,
    5: 4,
    6: 5,
    7: 6,
    8: 7,
}

順便說一句:用字典替換一系列 elif 語句是 python 中非常典型的事情。 它並不總是合適的,但通常效果很好。

現在你可以像這樣編寫你的生成器:

def convert(data, replacement_dict):
    for index in range(len(data)):
        if index==0:
            lst.append(8)
        data[index] = replacement_dict[index]
        yield data

並像這樣使用它:

iter_converted_datas = convert(data, replacement_dict)

for _, converted in enumerate(iter_converted_datas):
    print(len(converted))

現在,您可能有多個要對其進行其他修改的索引。 您可以將它們拆分為具有不同目的的多個字典: replacement_dictmodification_dict

修改字典看起來像這樣(它現在只有條目,對於索引 0):

modification_dict = {
    0: lambda data: data.append(8)
}

現在,我們將修改生成器 function 以同時接受 replacement_dict 和 modify_dict:

def convert(data, replacement_dict, modification_dict):
    for index in range(len(data)):
        # this modifies the data
        try:
            modification_dict[index](data)
        except KeyError:
            pass
        # this replaces the data
        data[index] = replacement_dict[index]
        yield data

現在,像這樣使用它:

iter_converted_datas = convert(data, replacement_dict, modification_dict)

for _, converted in enumerate(iter_converted_datas):
    print(len(converted))

但我們需要退后一步:您的 memory 被填滿的原因是您創建了一個增長非常快的例程。 而且,如果您繼續超過 256 次迭代,那么列表會變得更長而沒有盡頭。

如果您想迭代列表的第 X 次迭代而不將整個列表存儲到 memory 中,則必須進行相當多的更改。

我對如何開始的建議:創建一個 function 以獲得任何起始輸入值的第 X 次迭代。

這是一個僅根據替換字典生成輸出的生成器。 根據替換字典的內容,這可能是無限的,或者它可能有一個結束(在這種情況下它會引發KeyError )。

def process_replacements(index, replacement_dict):
    while True:
        yield (index := replacement_dict[index])        

接下來我們可以編寫 function 來處理第 X 次迭代以獲得起始值:

def process_xth(index, xth, replacement_dict):
    # emit the xth value from the original value
    for _ in range(xth):
        index = process_replacements(index, replacement_dict)
    return index

現在您可以處理起始數據列表中任何值的第 X 次迭代:

index = 0
xth = 256
process_xth(data[index], xth, data, replacement_dict)

但是,每當我們遇到 0 值時,我們都沒有附加到data列表中。 我們可以這樣做,但正如您所發現的,最終 8 的列表會變得太大。 相反,我們需要做的是保持 COUNT 我們在末尾添加了多少個 8。

您可以采取以下措施對其進行優化:

您可以使用enumerate(data)而不是range(len(data)) ) 。 這使您可以訪問元素及其索引。 例子:

編輯:根據這篇文章, rangeenumerate快。 如果您關心速度,則可以忽略此更改。

for index, element in enumerate(data):
    if element == 0:
        data[index] = 6

其次,大多數if語句具有可預測的模式。 所以你可以像這樣重寫它們:

def convert(data):
    for idx, elem in enumerate(data):
        if elem == 0:
            data[idx] = 6
            data.append(8)

        if elem <= 8:
            data[index] = elem - 1

由於list是可變的,因此您不需要返回data 它就地修改它。

我看到您詢問生成器功能,但這並不能解決您的 memory 問題。 你用完了 memory 因為,好吧,你把所有東西都保存在 memory 中......

您的解決方案的 memory 復雜度為 O*((8/7)^n) 其中 n 是要轉換的調用次數。 這是因為每次調用 convert() 時,數據結構都會擴展 1/7 的元素(平均而言)。 之所以如此,是因為結構中的每個數字都有 1/7 的概率為零。

所以 memory 復雜度是 O*((8/7)^n),因此是指數的。 但我們能做得更好嗎?

我們可以。 我們可以在 memory 中只保留調用 convert() 時結構中存在的零的數量。 這樣,我們將有一個線性的 memory 復雜度 O*(n)。 這需要付出代價嗎?

是的。 元素訪問時間不再具有恆定的復雜度 O(1),但它具有線性復雜度 O(n),其中 n 是對 convert() 的調用次數(至少這是我想出的)。 但它解決了內存不足的問題。

這是一個代碼:

from copy import deepcopy  # to keep original list untouched ;)

class Data:
    def __init__(self, seed):
        self.seed = deepcopy(seed)
        self.iteration = 0
        self.zero_counts = list()
        self.len = len(seed)

    def __len__(self):
        return self.len

    def __iter__(self):
        return SeededDataIterator(self)

    def __repr__(self):
        return "[" + (", ".join(f"{n}" for n in self)) + "]"

    def __getitem__(self, index: int):
        if index >= self.len:
            raise IndexError

        if index < len(self.seed):
            ret = self.seed[index] - self.iteration
        else:
            inner_it_idx = index - len(self.seed)
            for i, cnt in enumerate(self.zero_counts):
                if inner_it_idx < cnt:
                    ret = 9 + i - self.iteration
                    break
                else:
                    inner_it_idx -= cnt

        ret = ret if ret > 6 else ret % 7
        return ret

    def convert(self):
        zero_count = sum((self[i] == 0) for i, _ in enumerate(self.seed))

        for i, count in enumerate(self.zero_counts):
            i = 9 + i - self.iteration
            i = i if i > 6 else i % 7
            if i == 0:
                zero_count += count

        self.zero_counts.append(zero_count)
        self.len += self.zero_counts[self.iteration]
        self.iteration += 1


class DataIterator:
    """Iterator class for the Data class"""
    def __init__(self, seed_data):
        self.seed_data = seed_data
        self.index = 0
    
    def __next__(self):
        if self.index >= self.seed_data.len:
            raise StopIteration

        ret = self.seed_data[self.index]
        self.index += 1
        return ret

暫無
暫無

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

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