将 Python 列表编码为唯一值的索引



我想将任意列表表示为另外两个列表。第一个,称之为values,包含原始列表中的唯一元素,第二个,称之为codes,包含原始列表中每个元素的索引values,以便原始列表可以重建为

orig_list = [values[c] for c in codes]

(注意:这类似于pandas.Categorical表示系列的方式(

我创建了下面的函数来执行此分解:

def decompose(x):
values = sorted(list(set(x)))
codes = [0 for _ in x]
for i, value in enumerate(values):
codes = [i if elem == value else code for elem, code in zip(x, codes)]
return values, codes

这有效,但我想知道是否有更好/更有效的方法来实现这一目标(没有双循环?(,或者标准库中是否有可以为我做到这一点的东西。


更新

下面的答案很棒,对我的功能有很大的改进。我已经按预期对所有工作进行了计时:

test_list = [random.randint(1, 10) for _ in range(10000)]
functions = [decompose, decompose_boris1, decompose_boris2,
decompose_alexander, decompose_stuart1, decompose_stuart2,
decompose_dan1]
for f in functions:
print("-- " + f.__name__)
# test
values, codes = f(test_list)
decoded_list = [values[c] for c in codes]
if decoded_list == test_list:
print("Test passed")
%timeit f(test_list)
else:
print("Test failed")

结果:

-- decompose
Test passed
12.4 ms ± 269 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
-- decompose_boris1
Test passed
1.69 ms ± 21.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
-- decompose_boris2
Test passed
1.63 ms ± 18.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
-- decompose_alexander
Test passed
681 µs ± 2.15 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
-- decompose_stuart1
Test passed
1.7 ms ± 3.42 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
-- decompose_stuart2
Test passed
682 µs ± 5.98 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
-- decompose_dan1
Test passed
896 µs ± 19.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

我接受斯图尔特的回答,认为这是最简单和最快的答案之一。

我对这个解决方案非常满意,尽管我仍在努力寻找更好的解决方案。

<小时 />

代码

def decompose(original_list: List[Any]) -> Tuple[List[int], Dict[int, Any]]: 
code_to_elem = dict(enumerate(set(original_list)))
elem_to_code = {v: k for k, v in code_to_elem.items()}
encoded_list = [elem_to_code[elem] for elem in original_list]
return encoded_list, code_to_elem
<小时 />

测试运行

# t_list for test_list
t_list = [1, 2, 19, 3, 2, 19, 2, 3, 19, 1, 1, 3]
t_encoded, t_decoder = decompose(t_list)
t_decoded = [t_decoder[curr_code] for curr_code in t_encoded]

以下是重要变量的内容:

  • t_list[1, 2, 19, 3, 2, 19, 2, 3, 19, 1, 1, 3]
  • t_encoded[1, 2, 3, 0, 2, 3, 2, 0, 3, 1, 1, 0]
  • t_decoder{0: 3, 1: 1, 2: 2, 3: 19}
  • t_decoded[1, 2, 19, 3, 2, 19, 2, 3, 19, 1, 1, 3]

如果您有任何问题,请告诉我:)

即使它只是对鲍里斯答案的改进,这也算作一个答案。

我会使用index_of_values.append(values.setdefault(elem, len(values)))作为循环体,因为它将三个字典查找减少到一个,并将分支保留在解释器之外。甚至可以为这两种方法创建局部变量,以免重复查找它们。但似乎两者的节省只有7%。

但是使用看起来很疯狂的values = defaultdict(lambda: len(values))给出了23%。

from collections import defaultdict
def decompose(x):
values = defaultdict(lambda: len(values))
index_of_values = []
_append = index_of_values.append
for elem in x:
_append(values[elem])
return list(values), index_of_values

如果将循环替换为映射会更好:

def decompose(x):
values = defaultdict(lambda: len(values))
index_of_values = list(map(values.__getitem__, x))
return list(values), index_of_values

给 57%。如果我一直在查看函数的输出,我会发现这一点。也得到显然不会触发工厂。我不知道为什么没有。

如果词典未保留广告顺序:

return sorted(values, key=values.get), index_of_values

您可以使用简单的index查找:

def decompose(x):
values = sorted(set(x))
return values, [values.index(v) for v in x] 

如果需要更高的时间效率(因为x非常大(,那么可以通过将values表示为字典来实现(以换取一些内存开销(:

def decompose(x):
values = sorted(set(x))
d = {value: index for index, value in enumerate(values)}
return values, [d[v] for v in x]

如果不需要排序(或由于某种原因无法排序(,则sorted替换为上述list

你可以这样做,IIUC,康明:

df['newgroup'] = df.reset_index().groupby('group')['index'].cummin()                                                                                                                            
In [1579]: df                                                                                                                                                                                              
Out[1579]: 
group  newgroup
0       5         0
1       4         1
2       5         0
3       6         3
4       7         4
5       8         5
6       5         0
7       3         7
8       2         8
9       5         0
10      6         3
11      7         4
12      8         5
13      8         5
14      5         0

创建一个字典,其中键是唯一值,它们映射到键中的索引(字典从 CPython 3.6 开始保持顺序(。您可以通过循环访问列表来执行此操作,如果某个元素不在字典中,则将其添加到字典中,并将其映射到添加时字典的长度。然后,在字典中查找元素的索引并将其追加到列表中。然后,仅返回键以及索引列表。

def decompose(x):
values = {}
index_of_values = [values.setdefault(elem, len(values)) for elem in x]
return list(values), index_of_values

这是线性的时空复杂性。像这样使用它:

>>> decompose([2, 1, 1, 1, 131, 42, 2])
([2, 1, 131, 42], [0, 1, 1, 1, 2, 3, 0])

使用列表推导的副作用通常是不受欢迎的,所以你可能想更明确地写出这个函数:

def decompose(x):
values = {}
index_of_values = []
for elem in x:
if elem not in values:
values[elem] = len(values)
index_of_values.append(values[elem])
return list(values), index_of_values

如果你需要像熊猫这样的东西。分类

arbi_arr=[1, 2, 3, 1, 2, 3]
value=list(dict.fromkey(arbi_arr))
code=list(range(0, len(arbi_arr)))