首页 > 解决方案 > 如何从 1 重新编号并为一系列数字增加 1?

问题描述

我有这样的数字

4, 4, 4, 7, 7, 9, 9, 9, 9, 2, 2, 2, 4, 4

我想把它们改成

1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5,从 1 开始重新编号,并以 1 为增量,无论某个数字是否再次出现。

那么在 Python 中,最有效的方法是什么?

这是我的蛋白质 PDB 残基 ID 中的一系列数字。每个残基都有多个原子。pdb 也有多个链和缺失的残基,这就是为什么输入数字在某个点从头开始重新开始并且总是有一些间隙。但我只希望数字是从 1 到最后一个残基的 1 个增量。

标签: pythonpython-3.x

解决方案


与对象itertools.count()一起使用将唯一值映射到递增计数:collections.defaultdict()

from itertools import count
from collections import defaultdict

counter = defaultdict(count(1).__next__)
result = [counter[v] for v in inputlist]

count(1)1从( 与 的默认起始值​​相反 )开始计数,并且每次字典查找使用字典中尚未包含的值时0,该__next__方法都会生成下一个值:counter[v]

>>> counter = defaultdict(count(1).__next__)
>>> counter["foo"]  # not yet in the dictionary
1
>>> counter["foo"]  # already in the dictionary
1
>>> counter["bar"]  # new value, so a new count is assigned
2
>>> counter
defaultdict(<method-wrapper '__next__' of itertools.count object at 0x10b2a7fc0>, {'foo': 1, 'bar': 2})

演示:

>>> from itertools import count
>>> from collections import defaultdict
>>> example = [4, 4, 4, 7, 7, 9, 9, 9, 9, 2, 2, 2]
>>> counter = defaultdict(count(1).__next__)
>>> [counter[v] for v in example]
[1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4]

这确实假设如果输入列表中的给定数字稍后再次出现(因此不在同一个连续组中),则该数字被重用

>>> counter_example = [4, 4, 4, 7, 7, 9, 9, 9, 9, 2, 2, 2, 4, 4, 4, 4]
>>> counter = defaultdict(count(1).__next__)
>>> [counter[v] for v in counter_example]
[1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 1, 1, 1, 1]

如果这是一个问题,请与to number 组连续数字itertools.groupby()一起使用:enumerate()

from itertools import count, groupby

result = [c for c, (k, g) in enumerate(groupby(inputlist), 1) for _ in g]

这里将 1 的起始值enumerate()作为第二个参数传递给 ( enumerate(), like count(), 默认开始计数0)。

groupby()创建连续值相等的组,因此4由其他值分隔的两次运行形成两个单独的组,并且它们各自获得单独的计数。然后,您确实需要重复分配的数字与每组中的值一样多,因此for _ in g循环结束。

演示:

>>> [c for c, (k, g) in enumerate(groupby(example), 1) for _ in g]
[1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4]
>>> [c for c, (k, g) in enumerate(groupby(counter_example), 1) for _ in g]
[1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5]

请注意,任何一种解决方案都可以通过map()附加itertools功能完全惰性化:

# lazy defaultdict lookups

counter = defaultdict(count(1).__next__)
lazy_result = map(counter.__getitem__, inputlist)

# lazy group enumeration

from itertools import chain, repeat

lazy_result = chain.from_iterable(
    repeat(c, sum(1 for _ in g))  # sum() calculates iterator length efficiently
    for c, (k, g) in enumerate(groupby(inputlist), 1)
)

推荐阅读