对原始 python 列表中的元素子集进行就地修改

Inplace modification for subset of elements within original python list

我看到列表理解如何擅长 returning 列表的子集,甚至在没有设置条件的情况下进行全面重新映射。但是,过滤条件将 return 列表中的值可能小于过滤列表。如何获取 returned 值的位置并有条件地更改这些元素?

这是一个简单的例子,

>>> v = ['a' , 'b' , 'c' , 'd' , 'f' , 'k' , 'g' , 'a', 'd']
>>> t = ['a', 'k']

如何修改 tv 的元素以映射到新的新值,例如 'z',以产生类似

的内容
>>> v = ['z' , 'b' , 'c' , 'd' , 'f' , 'z' , 'g' , 'z', 'd']

有很多关于 SO 的问题,来自 Matlab 的人希望做逻辑索引的等价物,但主要只是为了 选择 元素而不是 按照我的意愿修改 它们。为了修改过滤后的元素,我想做相当于逻辑索引的操作。

我也刚刚发现我无法为 Python 列表提供多个索引。我正在使用 Python 3.

>>> v[ [1,3] ] = 'z'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: list indices must be integers, not list
>>> v[ 1:3 ] = 'z'
>>> v
['a', 'z', 'd', 'f', 'k', 'g', 'a', 'd']

filter 可以为我提供实际对象的可迭代对象,但我不知道如何修改它们指向的元素。

>>> v = ['a' , 'b' , 'c' , 'd' , 'f' , 'k' , 'g' , 'a', 'd']
>>> t = ['a', 'k']
>>> p = filter(lambda x: x in t, v)
>>>
>>> for elem in p :
...    print(elem)
...
a
k
a
>>>
>>> for elem in p :
...    p = 'z'
...
>>>
>>> for elem in p :
...    print(elem)
...
>>>

您可以使用 mapv 中的值映射到固定值 z 如果它们也在 t 中,否则保持不变:

>>> print list(map(lambda i: 'z' if i in t else i, v))
['z', 'b', 'c', 'd', 'f', 'z', 'g', 'z', 'd']

或者,您仍然可以使用理解:

>>> v[:] = [i if not (i in t) else 'z' for i in v]
>>> print v
['z', 'b', 'c', 'd', 'f', 'z', 'g', 'z', 'd']

就我个人而言,我会发现定义函数更为明显,尤其是当您使用带有多个参数的函数时。因为通过 functools.partial:

很容易 "freeze" 固定参数
>>> def f(value, lookup, mapping_value):
...     if value in lookup:
...         return mapping_value
...     return value

--

>>> from functools import partial

>>> v = ['a' , 'b' , 'c' , 'd' , 'f' , 'k' , 'g' , 'a', 'd']
>>> t = ['a', 'k']

>>> func = partial(f, lookup=t, mapping_value='z')
>>> v[:] = [func(i) for i in v]
>>> print v
['z', 'b', 'c', 'd', 'f', 'z', 'g', 'z', 'd']

应该这样做:

>>> v = ['a' , 'b' , 'c' , 'd' , 'f' , 'k' , 'g' , 'a', 'd']
>>> t = {'a', 'k'}
>>> new_v = [item if (item not in t) else 'z' for item in v]
>>> new_v
['z', 'b', 'c', 'd', 'f', 'z', 'g', 'z', 'd']
>>> 

如果您尝试更改 t 中的任何匹配值 v,最有效的方法是使用 make t a set 如果元素是可散列的并且只需使用 in:

v = ['a' , 'b' , 'c' , 'd' , 'f' , 'k' , 'g' , 'a', 'd']
st = {'a', 'k'}

v[:] = ["z" if s in st else s for s in v]

或者结合生成器表达式:

v[:] = ("z" if s in st else s for s in v)

使用v[:]会修改原来的object/listv.

如果您要使用 for 循环,当您在 v 中找到一个也在 out 集合中的元素时,您将使用 enumerate 使用索引更新列表:

v = ['a' , 'b' , 'c' , 'd' , 'f' , 'k' , 'g' , 'a', 'd']
st = {'a', 'k'}

for ind,ele in enumerate(v):
    if ele in st:
        v[ind] = "z"

Python 中 MATLAB 数组的等效项是 numpy 数组,而不是列表。您不能在列表上进行逻辑索引,但可以在 numpy 数组上进行。因此,对于您的任务,numpy 数组可以很好地工作:

>>> import numpy as np
>>>
>>> v = np.array(['a' , 'b' , 'c' , 'd' , 'f' , 'k' , 'g' , 'a', 'd'])
>>>
>>> v[v == 'a'] = 'z'
>>> v[v == 'k'] = 'z'
>>> print(v)
['z' 'b' 'c' 'd' 'f' 'z' 'g' 'z' 'd']

当你有一个更大的序列时,这会变得复杂。在这种情况下,您可以使用 np.in1d ,其中 returns True 用于第一个序列中存在于第二个序列中的任何元素,而 False 用于任何不存在的元素.这也可以用于逻辑索引:

>>> t = ['a', 'c', 'f', 'k']
>>> v[np.in1d(v, t)] = 'z'
>>> print(v)
['z' 'b' 'z' 'd' 'z' 'z' 'g' 'z' 'd']

在这里使用集合会更快 (t = {'a', 'c', 'f', 'k'}),但我试图让事情接近你的例子。

这种方法大致等同于:

>>> t = ['a', 'c', 'f', 'k']
>>> for ti in t:
...     v[v == ti] = 'z'
...

numpy 数组也支持多个索引,尽管索引序列本身必须是一个 numpy 数组。

正如其他人所指出的,您可以使用列表理解和成员测试进行简单的替换。但是,你也可以使用字典,这可能会更简单,而且在我看来更清晰:

>>> t = ['a', 'k']
>>> tdict = dict.fromkeys(t, 'z')
>>> v2 = [tdict.get(vi, vi) for vi in v]
>>> print(v2)
['z', 'b', 'c', 'd', 'f', 'z', 'g', 'z', 'd']

dict.fromkeys方法创建一个dict,其中键是t的元素,值都是z。相当于{ti: 'z' for ti in t}.

dict.get(x, y)获取keyy对应的字典的值,如果没有这样的key则returnsx。我的代码所做的是遍历列表中的每个元素。如果该元素在 tdict 中,它会用 tdict 中的相应值替换该值。如果不是,它会用自己替换该值(也就是说,它什么都不做)。

这并不比成员测试示例简单多少。但是,如果您需要具有多个目标和多个替换的更复杂的替换,dict 方法将变得 much cleaner:

>>> repdict = {'a': 'z', 'k': 'z', 'i': 'y', 'd': 'y', 'b': 't'}
>>> v2 = [repdict.get(ti, ti) for ti in v]
>>> print(v2)
['z', 't', 'c', 'y', 'f', 'z', 'g', 'z', 'y']