简体   繁体   English

Python中嵌套`for`循环的替代方法

[英]Alternative to nested `for` loops in Python

I am trying to compare a list of forbidden folders read from a file. 我正在尝试比较从文件中读取的禁止文件夹的列表。 But we have to check if the task has the parent ID of the folder and then if that folder matches the forbidden folder. 但是我们必须检查任务是否具有该文件夹的父ID,然后检查该文件夹是否与禁止的文件夹匹配。 The lists I am looping through can contain many items. 我正在遍历的列表可以包含许多项目。

for task in tasks:
    #Check the task is on their timesheet
    if task["id"] == entry["item_id"]:
        for folder in folders:
            #See if the task is inside the folder
            if task["parent_id"] == folder["id"]:
                for forbiddenFolder in forbiddenFolders:
                    #If the folder is on the list
                    if forbiddenFolder == folder["name"]:
                        body_of_email +=( "Using a task within a folder that is not permiited " + forbiddenFolder + "\r\n" )
                        folder["name"]
                        break

This code uses three nested for loops, which might be slow. 此代码使用三个嵌套的for循环,这可能很慢。 Can I make this more efficient? 我可以提高效率吗?

You can reduce the number of lines of code and make it easier to understand like this: 您可以减少代码行的数量,并使之更易于理解,如下所示:

tasks_id = [task in tasks if task["id"] == entry["item_id"]]
folders_dict = dict()
for folder in folders:
    folders_dict[folder["id"]] = folder

for task in tasks_id:
    if task["parent_id"] in folders_dict.keys() and folder_dict[task["parent_id"]] in forbiddenFolders:
        body_of_email +=( "Using a task within a folder that is not permiited " + forbiddenFolder + "\r\n" )

What you're trying to do here is look up an item (task, folder) based on the id. 您要在此处执行的操作是根据ID查找项目(任务,文件夹)。 Python's dictionaries provide an easy way to do this. Python的字典提供了一种简便的方法。 You will only save if you'll be doing the searches several times (eg if there are many tasks with the same id, or if you'll be running the function several times). 仅当您要进行多次搜索时(例如,如果有许多具有相同ID的任务,或者您要多次运行该函数),您才会保存。

Additionally, for forbiddenFolders you just have a list of names (you're not looking up an item, you're just checking if it's present) for which Python's sets are suitable. 此外,对于forbiddenFolders,您只有一个名称列表(您没有在查找任何项目,只是在检查它是否存在)以适合Python的集合。

Anyway, here is how you build the dictionaries and sets: 无论如何,这是构建字典和集合的方式:

tasks_dict = dict((task['id'], task) for task in tasks)
folders_dict = dict((folder['id'], folder) for folder in folders)
forbidden_folders_set = set(forbiddenFolders)

Now, task = tasks_dict[id] is a task such that task['id'] == id , and similarly for folders, so you can replace the loops above with these expressions. 现在, task = tasks_dict[id]task['id'] == id ,对于文件夹也是如此,因此您可以使用这些表达式替换上面的循环。 The set doesn't allow this, but it allows you to check for presence with folder in forbidden_folders_set . 该设置不允许这样做,但是它允许您使用folder in forbidden_folders_set检查是否存在。

(Bear in mind that each of those dict(...) operations may take longer than running through one of the for loops above, but they are an investment for faster lookup in future.) (请记住,每个dict(...)操作可能比在上面的for循环之一中运行要花费更长的时间,但它们是将来进行更快查找的一项投资。)

if entry['item_id'] in tasks_dict:
    task = tasks_dict[entry['item_id']]
    if task['parent_id'] in folders_dict:
        folder = folders_dict[task['parent_id']]
        if folder in forbidden_folders_set:
            body_of_email += ...

The x in y and ..._dict[x] operations above are very efficient. 上面的x in y..._dict[x]操作非常有效。

A method which lets you keep the original datastructures is the following: 一种使您可以保留原始数据结构的方法如下:

for task in (t for t in tasks if entry["item_id"]==t["id"]):
    for folder in (f for f in folders if task["parent_id"]==f["id"]):
        for forbiddenFolder in (ff for ff in forbiddenFolders if ff==folder["name"]):
            body_of_email += "Using a task within a folder that is not permitted %s \r\n"% forbiddenFolder
            break

This makes use of generators , which are very memory-efficient and thus preserve speed, and conditional for-loops of the form x for x in range(y) if condition . 这利用了生成器 ,这些生成器非常节省内存,因此可以保持速度,并且x for x in range(y) if condition形式的条件for循环。 I recommend taking a look at both. 我建议同时看看两者。

taking a step back from the intricate details in the original inquiry, and put attention on just some "Alternatives to nested for loops" in general, how about the following: 从原始查询中的复杂细节上退后一步,并通常只关注一些“​​为循环嵌套的替代方法”,以下内容如何:

tasks = {
    'design': 'later',
    'testing': 'desired',
    'vacation': 'fall'
}

folders = {
    'summer': 'red',
    'fall': 'gold',
    'winter': 'white',
    'spring': 'green'
}

def apply_some_logic(a, b, c, d):
    return '_'.join([a, b, c, d]) if b == c else (a, b, c, d)


if __name__ == '__main__':
    results = (apply_some_logic(a, b, c, d) for a, b in tasks.items() for c, d in folders.items())
    for result in results:
        print(result)


$ python3.6 --version
Python 3.6.4

output:
('design', 'later', 'Summer', 'Red')
('design', 'later', 'Fall', 'Gold')
('design', 'later', 'Winter', 'White')
('design', 'later', 'Spring', 'Green')
('implementation', 'urgent', 'Summer', 'Red')
('implementation', 'urgent', 'Fall', 'Gold')
('implementation', 'urgent', 'Winter', 'White')
('implementation', 'urgent', 'Spring', 'Green')
('testing', 'desired', 'Summer', 'Red')
('testing', 'desired', 'Fall', 'Gold')
('testing', 'desired', 'Winter', 'White')
('testing', 'desired', 'Spring', 'Green')
('vacation', 'Fall', 'Summer', 'Red')
vacation_Fall_Fall_Gold
('vacation', 'Fall', 'Winter', 'White')
('vacation', 'Fall', 'Spring', 'Green')

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM