python - 如何有效地处理在 Python 中不断附加新项目的列表

标签 python performance for-loop coding-efficiency

目标:

可视化特定生物体在有限时间内的种群规模。

假设:

  • 生物体的生命周期为 age_limit
  • 只有年龄在 day_lay_egg 天的雌性才能产卵,雌性最多可以产卵 max_lay_egg 次。每个繁殖阶段,最多只能产下 egg_no 个卵,产生雄性后代的概率为 50%。
  • 3 个生物体的初始种群由 2 个雌性和 1 个雄性组成

代码片段:

目前,下面的代码应该产生预期的输出

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns


def get_breeding(d,**kwargs):

    if d['lay_egg'] <= kwargs['max_lay_egg'] and d['dborn'] > kwargs['day_lay_egg'] and d['s'] == 1:
            nums = np.random.choice([0, 1], size=kwargs['egg_no'], p=[.5, .5]).tolist()
            npol=[dict(s=x,d=d['d'], lay_egg=0, dborn=0) for x in nums]
            d['lay_egg'] = d['lay_egg'] + 1
            return d,npol

    return d,None



def to_loop_initial_population(**kwargs):

    npol=kwargs['ipol']
    nday = 0
    total_population_per_day = []
    while nday < kwargs['nday_limit']:
        # print(f'Executing day {nday}')

        k = []
        for dpol in npol:
            dpol['d'] += 1
            dpol['dborn'] += 1
            dpol,h = get_breeding(dpol,**kwargs)

            if h is None and dpol['dborn'] <= kwargs['age_limit']:
                # If beyond the age limit, ignore the parent and update only the decedent 
                k.append(dpol)
            elif isinstance(h, list) and dpol['dborn'] <= kwargs['age_limit']:
                # If below age limit, append the parent and its offspring
                h.extend([dpol])
                k.extend(h)

        total_population_per_day.append(dict(nsize=len(k), day=nday))
        nday += 1
        npol = k

    return total_population_per_day


## Some spec and store all  setting in a dict   
numsex=[1,1,0] # 0: Male, 1: Female

# s: sex, d: day, lay_egg: Number of time the female lay an egg, dborn: The organism age
ipol=[dict(s=x,d=0, lay_egg=0, dborn=0) for x in numsex] # The initial population
age_limit = 45 # Age limit for the species
egg_no=3 # Number of eggs
day_lay_egg = 30  # Matured age for egg laying
nday_limit=360
max_lay_egg=2
para=dict(nday_limit=nday_limit,ipol=ipol,age_limit=age_limit,
          egg_no=egg_no,day_lay_egg=day_lay_egg,max_lay_egg=max_lay_egg)


dpopulation = to_loop_initial_population(**para)


### make some plot
df = pd.DataFrame(dpopulation)
sns.lineplot(x="day", y="nsize", data=df)
plt.xticks(rotation=15)
plt.title('Day vs population')
plt.show()

输出:

问题/疑问:

完成执行时间的时间随着nday_limit呈指数增长。我需要提高代码的效率。如何加快运行时间?

其他想法:

我很想按如下方式应用 joblib。令我惊讶的是,执行时间更糟。

def djob(dpol,k,**kwargs):
    dpol['d'] = dpol['d'] + 1
    dpol['dborn'] = dpol['dborn'] + 1
    dpol,h = get_breeding(dpol,**kwargs)

    if h is None and dpol['dborn'] <= kwargs['age_limit']:
        # If beyond the age limit, ignore the that particular subject
        k.append(dpol)
    elif isinstance(h, list) and dpol['dborn'] <= kwargs['age_limit']:
        # If below age limit, append the parent and its offspring
        h.extend([dpol])
        k.extend(h)

    return k
def to_loop_initial_population(**kwargs):

    npol=kwargs['ipol']
    nday = 0
    total_population_per_day = []
    while nday < kwargs['nday_limit']:


        k = []


        njob=1 if len(npol)<=50 else 4
        if njob==1:
            print(f'Executing day {nday} with single cpu')
            for dpols in npol:
                k=djob(dpols,k,**kwargs)
        else:
            print(f'Executing day {nday} with single parallel')
            k=Parallel(n_jobs=-1)(delayed(djob)(dpols,k,**kwargs) for dpols in npol)
            k = list(itertools.chain(*k))
            ll=1


        total_population_per_day.append(dict(nsize=len(k), day=nday))
        nday += 1
        npol = k

    return total_population_per_day

对于

nday_limit=365

最佳答案

您的代码总体上看起来不错,但我可以看到有几个改进点正在显着降低您的代码速度。

但必须注意的是,随着 nday 值的增加,您无法真正帮助代码减慢太多,因为您需要跟踪的人口不断增长,并且您不断重新填充列表以跟踪这一点。预计随着对象数量的增加,循环将需要更长的时间才能完成,但您可以减少完成单个循环所需的时间。

elif isinstance(h, list) and dpol['dborn'] <= kwargs['age_limit']:

这里你每次循环询问h的实例,在确认它是否为None之后。您知道 h 将是一个列表,如果不是,您的代码将在到达无法创建列表的那一行之前无论如何都会出错。

此外,您对 dpolage 进行了冗余条件检查,然后冗余地首先将 h 扩展 dpol 然后 k by h。这个可以连同上一期一起简化为:

if dpol['dborn'] <= kwargs['age_limit']:
    k.append(dpol)

if h:
    k.extend(h)

结果是一样的。

此外,您还传递了很多 **kwargs。这表明您的代码应该是一个类,其中一些不变的参数通过 self.parameter 保存。您甚至可以在此处使用数据类 (https://docs.python.org/3/library/dataclasses.html)

此外,您混合了不必要的功能职责,使您的代码更加困惑。例如:

def get_breeding(d,**kwargs):

    if d['lay_egg'] <= kwargs['max_lay_egg'] and d['dborn'] > kwargs['day_lay_egg'] and d['s'] == 1:
            nums = np.random.choice([0, 1], size=kwargs['egg_no'], p=[.5, .5]).tolist()
            npol=[dict(s=x,d=d['d'], lay_egg=0, dborn=0) for x in nums]
            d['lay_egg'] = d['lay_egg'] + 1
            return d,npol

    return d,None

这段代码包含两个职责:如果满足条件则生成一个新的个体,并检查这些条件,并根据它们返回两个不同的东西。

这最好通过两个单独的函数来完成,一个简单地检查条件,另一个生成一个新的个体,如下所示:

def check_breeding(d, max_lay_egg, day_lay_egg):
    return d['lay_egg'] <= max_lay_egg and d['dborn'] > day_lay_egg and d['s'] == 1


def get_breeding(d, egg_no):
    nums = np.random.choice([0, 1], size=egg_no, p=[.5, .5]).tolist()
    npol=[dict(s=x, d=d['d'], lay_egg=0, dborn=0) for x in nums]
    return npol

如果条件满足,d['lay_egg'] 可以在遍历列表时就地更新。

如果您在迭代列表时编辑列表,您可以通过这种方式进一步加快代码速度(通常不推荐这样做,但如果您知道自己在做什么,这样做是完全没问题的。一定要这样做通过使用索引并将其限制为列表长度的先前边界,并在删除元素时递减索引)

例子:

i = 0
maxiter = len(npol)
while i < maxiter:
    if check_breeding(npol[i], max_lay_egg, day_lay_egg):
        npol.extend(get_breeding(npol[i], egg_no))
    
    if npol[i]['dborn'] > age_limit:
            npol.pop(i)
            i -= 1
            maxiter -= 1

这可以显着减少处理时间,因为您无需创建新列表并在每次迭代时重新附加所有元素。

最后,您可以检查一些人口增长方程和统计方法,您甚至可以将整个代码简化为一个带有迭代的计算问题,尽管那不再是模拟了。

编辑

我已经完全实现了我对改进代码的建议,并使用 %%time 在 jupyter notebook 中为它们计时。我已经从两者中分离出函数定义,这样它们就不会浪费时间,而且结果很能说明问题。我还让雌性在 100% 的时间内生产另一只雌性,以消除随机性,否则它会更快。我比较了两者的结果以验证它们产生相同的结果(它们确实产生了相同的结果,但我删除了 'd_born' 参数,因为它除了设置外没有在代码中使用)。

您的实现,使用 nday_limit=100day_lay_egg=15:
挂墙时间23.5s

我使用相同参数的实现:
挂墙时间18.9s

因此您可以看出差异非常显着,对于较大的 nday_limit 值,差异会变得更远。

已编辑代码的完整实现:​​

from dataclasses import dataclass
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns


@dataclass
class Organism:
    sex: int
    times_laid_eggs: int = 0
    age: int = 0

    def __init__(self, sex):
        self.sex = sex


def check_breeding(d, max_lay_egg, day_lay_egg):
    return d.times_laid_eggs <= max_lay_egg and d.age > day_lay_egg and d.sex == 1


def get_breeding(egg_no): # Make sure to change probabilities back to 0.5 and 0.5 before using it
    nums = np.random.choice([0, 1], size=egg_no, p=[0.0, 1.0]).tolist()
    npol = [Organism(x) for x in nums]
    return npol


def simulate(organisms, age_limit, egg_no, day_lay_egg, max_lay_egg, nday_limit):
    npol = organisms
    nday = 0
    total_population_per_day = []

    while nday < nday_limit:
        i = 0
        maxiter = len(npol)
        while i < maxiter:
            npol[i].age += 1
            
            if check_breeding(npol[i], max_lay_egg, day_lay_egg):
                npol.extend(get_breeding(egg_no))
                npol[i].times_laid_eggs += 1

            if npol[i].age > age_limit:
                npol.pop(i)
                maxiter -= 1
                continue

            i += 1

        total_population_per_day.append(dict(nsize=len(npol), day=nday))
        nday += 1

    return total_population_per_day


if __name__ == "__main__":
    numsex = [1, 1, 0]  # 0: Male, 1: Female

    ipol = [Organism(x) for x in numsex]  # The initial population
    age_limit = 45  # Age limit for the species
    egg_no = 3  # Number of eggs
    day_lay_egg = 15  # Matured age for egg laying
    nday_limit = 100
    max_lay_egg = 2

    dpopulation = simulate(ipol, age_limit, egg_no, day_lay_egg, max_lay_egg, nday_limit)

    df = pd.DataFrame(dpopulation)
    sns.lineplot(x="day", y="nsize", data=df)
    plt.xticks(rotation=15)
    plt.title('Day vs population')
    plt.show()

关于python - 如何有效地处理在 Python 中不断附加新项目的列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71371560/

相关文章:

python - 如何通过用 python 编写解析器来提取与 C 程序的函数定义相关的起始行号?

python - 获取最新数据项 - Google App Engine - Python

performance - Scala speed- Finding Primes 示例

c++ - 简单但频繁使用 std::stringstream 是否过早悲观?

python - 使用 Python 编写图像

python - 使用未观察到的组件模型模拟时间序列

java - 在 Java 中复制和移动文件,不同方法的解释和比较

javascript - 使用 for 循环定位数组的元素而不是其位置时遇到问题

android - GraphView For循环不绘制数据

javascript - 如何循环遍历音频文件的 javascript 数组?