【发布时间】:2018-06-16 20:05:55
【问题描述】:
我创建一个列表如下:
['v0' if x%4==0 else 'v1' if x%4==1 else 'v2' if x%4==2 else 'v3' for x in list_1]
如何泛化这样一个列表的创建,使其可以很容易地被更大数量的变量和后续条件扩展?
【问题讨论】:
标签: python list if-statement
我创建一个列表如下:
['v0' if x%4==0 else 'v1' if x%4==1 else 'v2' if x%4==2 else 'v3' for x in list_1]
如何泛化这样一个列表的创建,使其可以很容易地被更大数量的变量和后续条件扩展?
【问题讨论】:
标签: python list if-statement
这是我对通用解决方案的尝试。一、设置——
list_1 = [1, 2, 4, 5, 10, 4, 3]
前两个选项基于纯 python,而后两个使用数字库(numpy 和 pandas)。
dict.get
生成一个mapping 的键值。在列表推导中,查询dict.get -
mapping = {0 : 'v0', 1 : 'v1', 2 : 'v2'}
r = [mapping.get(x % 4, 'v3') for x in list_1]
r
['v1', 'v2', 'v0', 'v1', 'v2', 'v0', 'v3']
这里,'v3' 是当x % 4 的结果不作为mapping 中的键存在时返回的默认值。
这适用于任何组任意条件和值,而不仅仅是问题中概述的条件(模算术)。
collections.defaultdict
使用defaultdict 可以实现类似的解决方案 -
from collections import defaultdict
mapping = defaultdict(lambda: 'v3', {0: 'v0', 1: 'v1', 2: 'v2', 3: 'v3'})
r = [mapping[x % 4] for x in list_1]
r
['v1', 'v2', 'v0', 'v1', 'v2', 'v0', 'v3']
这类似于 选项 1。
numpy.char.add
如果您使用numpy,那么您可能对涉及模算术和广播加法的矢量化解决方案感兴趣 -
r = np.char.add('v', (np.array(list_1) % 4).astype('<U8'))
r
array(['v1', 'v2', 'v0', 'v1', 'v2', 'v0', 'v3'],
dtype='<U9')
如果您需要一个列表作为最终结果,您可以致电r.tolist()。请注意,此解决方案针对您的特定用例进行了优化。使用numpy 使用np.where/np.select 可以实现更通用的方法。
pd.Series.mod + pd.Series.radd
类似的解决方案也适用于pandas mod + radd -
r = pd.Series(list_1).mod(4).astype(str).radd('v')
r
0 v1
1 v2
2 v0
3 v1
4 v2
5 v0
6 v3
dtype: object
r.tolist()
['v1', 'v2', 'v0', 'v1', 'v2', 'v0', 'v3']
【讨论】:
def condition(rule, out):
return lambda x: out(x) if rule(x) else None
def rule1(x): return x%4 == 0
def out1(x): return 'v0'
def rule2(x): return x%4 == 1
def out2(x): return 'v1'
def rule3(x): return x%4 == 2
def out3(x): return 'v2'
lastrule = lambda x: True
lastout = lambda x: 'v3'
check1 = condition(rule1, out1)
check2 = condition(rule2, out2)
check3 = condition(rule3, out3)
check_last = condition(lastrule, lastout)
def tranform(*check_list):
def trans_value(x):
for trans in check_list:
if trans(x) is not None:
return trans(x)
return trans_value
list_1=[4,5,6,7,8]
print([tranform(check1, check2, check3, check_last)(x) for x in list_1])
对于长检查,首先形成一个条件列表可能更容易。假设条件公式和输出都是x的函数,没有其他输入参数。下面的方法节省了一些输入,同时保持了长时间检查的可加性。
为了实现更通用的方法(更复杂的条件,多个参数),一些复合程序可能会有所帮助(比如both(greater, either(smaller, identity)),整个程序需要重新重组,这意味着程序的可加性还没有理想,因为它还不够通用。
outconstants = ['v0', 'v1', 'v2', 'v3']
# for this specific example. In general, only outf is needed (see below)
n = len(outconstant)
outf = lambda out: lambda x: out
outs = [outf(out) for out in outconstants]
# define your own outf formula, if not output constant
# define multiple formulas and put into list, if different type of outputs are needed
rights = map(lambda constant: lambda x: constant, range(n-1))
lefts = [lambda x: x%4 for _ in range(n-1)]
# right and left formulas can be also defined separately and then put into list
def identity(a, b): return lambda x: a(x) == b(x)
# define other rules if needed and form them into rules list with proper orders
# e.g., def greater(a, b): return lambda x: a(x) > b(x), ...
lastrule=lambda x: True
rules = list(map(identity, lefts, rights))
rules.append(lastrule)
# in complex case, each unique rule needs to be defined separately and put into list
# if new rule is needed, define it and append here before lastrule (additive)
def transform(rules, outs):
def trans_value(x):
for rule, out in zip(rules, outs):
if rule(x):
return out(x)
return trans_value
list_1=[4,5,6,7,8]
print([transform(rules, outs)(x) for x in list_1])
【讨论】:
在给定的示例中,很明显我们可以“压缩”条件,从而得出此处给出的具体解决方案。但在一般情况下,我们不能假设有一些“技巧”可以在一行中快速写出所有可能的条件。
我会在函数中写出所有条件:
def conditions(x):
if x == <option a>:
return <result a>
elif x == <option b>:
return <result b>
.
.
.
else:
return <default option>
如果您只是使用比较操作,则可以只使用collections.defaultdict,如其他回复所示。如果条件更复杂,那么您可能不得不写出如图所示的整个函数。
现在你列出理解,你可以这样做:
values = [conditions(x) for x in my_list_of_values]
【讨论】:
为什么不在这里使用模运算,并进行字符串格式化,例如:
['v{}'.format(x%4) for x in list_1]
因此,我们在这里计算x%4,并将其附加到字符串中的'v'。好消息是我们可以轻松地将4 更改为另一个数字。
如果输出字符串不遵循这样的结构,我们可以构造一个列表或元组来保存值。喜欢:
# in case the values do not follow a certain structure
vals = ('v0', 'v1', 'v2', 'v3')
[vals[x%4] for x in list_1]
通过以这种方式对其进行索引,可以清楚地知道哪些值将映射到哪个索引。这很好用,考虑到操作的结果——这里是x%4——映射到一个ℕn(有一个合理的小n)。
如果操作没有映射到 ℕn,但仍然映射到有限数量的 hashable 项,我们可以使用字典。例如:
d = {0: 'v0', 1: 'v1', 2: 'v2', 3: 'v3'}
或者如果我们想要一个“后备”值,则在查找失败时使用的值:
from collections import defaultdict
d = defaultdict(lambda: 1234, {0: 'v0', 1: 'v1', 2: 'v2', 3: 'v3'})
这里1234作为一个备用值,然后我们可以使用:
[d[x%4] for x in list_1]
使用d[x%4] 而不是d.get(x%4) 给定d 是一个字典,如果我们想防止失败的查找被忽视,它会更有用。在这种情况下会出错。尽管错误通常不是一个好兆头,但在查找失败的情况下引发错误比添加默认值更好,因为这可能是某些事情无法正常工作的征兆。
【讨论】:
.get(..),那么一个潜在的问题是错误通过静默,这与Python的哲学背道而驰。
.get 成语最适合映射到 OP 代码中的“else”子句,这是我使用它的理由。
defaultdict。因为在这种情况下,它被明确选择为“默认”值。