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

时间:2019-12-03 03:38:02

标签: 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)

我接受Stuart的回答,因为它是最简单也是最快的。

6 个答案:

答案 0 :(得分:1)

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


代码

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]

让我知道您是否有任何问题:)

答案 1 :(得分:1)

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

我将使用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

答案 2 :(得分:0)

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

答案 3 :(得分:0)

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

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

答案 4 :(得分: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

答案 5 :(得分:-1)

如果您需要pandas.Caterogical之类的东西

arbi_arr=[1, 2, 3, 1, 2, 3]

value=list(dict.fromkey(arbi_arr))
code=list(range(0, len(arbi_arr)))