Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/290.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python “有效地”;“取消”;列表中的操作_Python_Algorithm_List - Fatal编程技术网

Python “有效地”;“取消”;列表中的操作

Python “有效地”;“取消”;列表中的操作,python,algorithm,list,Python,Algorithm,List,我有一个被要求执行的新操作的列表。只有两种类型,订阅和取消订阅,或+和-操作。每个操作都有一个id。由于某些原因,在这个列表中可能有两个操作可以有效地相互抵消-a+和a-操作,它们都具有相同的id,cancel out-并且由于每个操作都有些昂贵,我不想执行超出必要的操作。所以我想搜索列表并取消相反项。这听起来是一个足够简单的问题,的确如此,但在给定的列表中可能有大量(300个)操作。这不是一个大问题,但我试图找到一个在效率和清洁度之间找到最佳点的算法,我不知道这类问题的具体术语,所以我无法通过

我有一个被要求执行的新操作的列表。只有两种类型,订阅和取消订阅,或+和-操作。每个操作都有一个
id
。由于某些原因,在这个列表中可能有两个操作可以有效地相互抵消-a+和a-操作,它们都具有相同的id,cancel out-并且由于每个操作都有些昂贵,我不想执行超出必要的操作。所以我想搜索列表并取消相反项。这听起来是一个足够简单的问题,的确如此,但在给定的列表中可能有大量(300个)操作。这不是一个大问题,但我试图找到一个在效率和清洁度之间找到最佳点的算法,我不知道这类问题的具体术语,所以我无法通过搜索找到任何实质性的东西

当然,一些基本的代码可以很好地工作。例如在Python中(尽管这个问题并不是专门针对Python):

def执行动作(动作列表):
新订阅=[]
新的取消订阅=[]
对于操作列表中的操作:
id=action.id_
如果IssuSubscribeType(操作):#等待真正的检查
如果id在新的取消订阅中:
新的取消订阅。删除(id)
持续
新的取消订阅。追加(id)
其他:
如果新订阅中的id为:
新订阅。删除(id)
持续
新的取消订阅。追加(id)
对于新订阅中的操作:
#订阅
对于新的取消订阅,请执行以下操作:
#退订
这是可行的,但是逻辑上有相当多的重复,对于这样一件简单的事情来说,它感觉像是太多的机器了。更不用说效率很低了

因此,从本质上讲,如何使此函数更清晰、更高效,而不在最后执行太多昂贵的操作?

您需要使用a(也称为映射或字典)来跟踪订阅和取消订阅,其中关键是操作id。哈希表提供O(1)恒定时间查找,因此,测试一个动作id是否已经被处理过是很便宜的。在Python中,
dict
类型就是这样一个哈希表。使用哈希表,您可以在O(N)时间内处理N个操作的操作,因此是线性时间

另一方面,使用Python列表是没有效率的,因为列表(数组、序列)需要完全扫描来测试成员资格。这意味着它们需要O(N)个时间来测试之前是否已经看到一个动作id,并且随着添加更多动作,算法会变慢,代码需要O(N^2)(N乘以N)个步骤来处理所有N个动作。随着操作列表的增大,处理该列表需要二次时间

哈希表的另一个优点是,只列出用于订阅或取消订阅(而不是两者)的操作将消除重复。行动A被列为订阅两次,最终只会被订阅一次

因此,要在Python中实现这一点,请使用
dict
类型。为了更容易地测试是否已经为相反的更改处理了操作id,您可以创建一个包含两个字典的元组。这些映射每个id的订阅和取消订阅。元组由“unsubscribe”(
0
)和“subscribe”(
1
)的索引寻址,您可以通过从1中减去,轻松地调整此索引以查看“相反”的存储桶。因此,如果订阅了操作A(索引1),那么您将签入元组中的
1-1
>项
0
,反之亦然

这里我假设
action.change
是一个字符串值,设置为
'subscribe'
'unsubscribe'
,该字符串可用于映射到具有额外字典的索引:

changes = ({}, {})  # unsub, sub
changemap = {'unsubscribe': 0, 'subscribe': 1}
for action in action_list:
    change = changemap[action.change]  # unsubscribe / subscribe -> 0 or 1
    if action.id_ in changes[1 - change]:  # 0 becomes 1, 1 becomes 0
        # action is listed twice for both subscribe and unsubscribe
        # cancel opposite and skip this action
        del changes[1 - change][action.id_]
        continue

    changes[change][action.id_] = action
现在,您有两个包含取消订阅和订阅的词典,它们可以单独处理:

for action in changes[0].values():
    # unsubscribe action

for action in changes[1].values():
    # subscribe action
如果您使用的是Python 3.6或更高版本,那么字典将按插入顺序生成其键和值,因此上述操作将按照
操作列表中列出的相同相对顺序处理所有取消订阅,这同样适用于订阅

如果您只需要
action.id
属性来订阅或取消订阅操作,则可以将字典替换为集合,并仅存储操作id。但是,集合不记得插入顺序

如果操作至少列出两次且有冲突的更改(例如,两次订阅和一次取消订阅),则您也需要一个单独的“取消”集,以跟踪从考虑中删除的ID:

changes = ({}, {})  # unsub, sub
changemap = {'unsubscribe': 0, 'subscribe': 1}
cancelled = set()
for action in action_list:
    if action.id_ in cancelled:
        # this action.id_ has been observed to both subscribe and unsubscribe
        # and has been cancelled altogether.
        continue

    change = changemap[action.change]  # unsubscribe / subscribe -> 0 or 1)
    if action.id_ in changes[1 - change]:
        # action is listed twice for both subscribe and unsubscribe
        # cancel opposite and ignore all further references to this action id
        del changes[1 - change][action.id_]
        cancelled.add(action.id_)
        continue

    changes[change][action.id_] = action

最简单的方法是使用单个散列映射,订阅数为+1,取消订阅数为-1,然后相应地订阅/取消订阅。使用Python
dict
defaultdict
计数器可以非常轻松地完成此操作。其中每一个都有O(1)的查找,对于n个操作,总复杂度为O(n)。您说顺序并不重要,但对于Python3.6和更高版本,字典实际上会以它们第一次插入的相同顺序保留条目

我不知道您的操作是如何表示的,所以我将使用类似
“+1”
的字符串来表示“订阅用户1”。这应该很容易适应你的行动模式

actions = ["+1", "-1", "+2", "+1", "+3", "+4", "-2", "-5"]

# get final (un)subscriptions
from collections import defaultdict
remaining = defaultdict(int)
for what, who in actions:
    remaining[who] += +1 if what == "+" else -1
print(remaining) # {'1': 1, '2': 0, '3': 1, '4': 1, '5': -1})
如果不存在任何“无效”操作(例如,取消订阅已取消订阅的用户),则dict不能保存+1(订阅)、-1(取消订阅)或0(取消)以外的值。如果存在无效(取消)订阅,则很容易检查当前值并相应放弃操作,例如,只需将新值上限设置为
max(-1,min(value,+1))

然后,只需迭代字典中的值,然后打印剩下的值,并使用
+1# print remaining (un)subscriptions
for k, v in remaining.items():
    if v == +1:
        print("subscribe", k)
    elif v == -1:
        print("unsubscribe", k)
subscribe 1
subscribe 3
subscribe 4
unsubscribe 5