我有 3 个列表,a
、b
和 c
每个列表都包含带有 3 个数字的元组。
下面是一个示例输入:
a = [(1,2,4),(1,7,8),(1,5,4),(3,6,7)]
b = [(1,2,5),(1,9,3),(1,0,3),(3,6,8)]
c = [(2,6,3),(2,4,9),(2,8,5),(1,2,7)]
我正在寻找一种方法来生成一个列表,如果每个元组的两个前项相等,则该列表采用这 3 个列表中的元素,并添加第三个元素。
在我给出的数据中,只有 1 组元组,前 2 个值等于:(1,2,4)
、(1,2,5)
和 (1,2,7)
。
如果我添加他们的第三个值,我有4+5+7 = 16
,所以有了这些数据,我最终应该有[(1,2,16)]
。
前两个值在每个列表中都是唯一的,[(1,2,7),(1,2,15)]
不存在。
问题不在于找到只有前两个值相等的元组,而是通过列表理解很容易完成。但是我坚持寻找一种同时添加第三个值的python方法。
我可以做到这个:
elem_list = []
for elem in a:
b_elem = [i for i in b if i[:-1] == elem[:-1]]
c_elem = [i for i in c if i[:-1] == elem[:-1]]
if len(b_elem) != 0 and len(c_elem) != 0:
elem_list.append((elem[0],elem[1], elem[2]+b_elem[0][2]+c_elem[0][2]))
这给了我所需的输出,但它真的很长,这就是为什么我确信这是一种 pythonistic 的方式来做到这一点而不会遇到麻烦,我只是无法弄清楚。
这里有一种方法可以做到这一点:
from itertools import product, starmap
def solve(*tups):
key = tups[0][:2]
if all(x[:2] == key for x in tups):
return key + (sum(x[2] for x in tups), )
for p in product(a, b, c):
out = solve(*p)
if out:
print out
#(1, 2, 16)
或者使用上述函数的单行
:print filter(None, starmap(solve, product(a, b, c)))
#[(1, 2, 16)]
效率不是很高,但会做你想做的事:
a = [(1,2,4),(1,7,8),(1,5,4),(3,6,7)]
b = [(1,2,5),(1,9,3),(1,0,3),(3,6,8)]
c = [(2,6,3),(2,4,9),(2,8,5),(1,2,7)]
from itertools import product
print(filter(lambda x: x[0][:2] == x[1][:2] == x[2][:2] ,product(a,b,c)))
[((1, 2, 4), (1, 2, 5), (1, 2, 7))]
这是一种不考虑任何效率的方法(假设 i,j 和 k 是列表 a、b、c 的长度,它循环 i * j * k 次)。
from operator import itemgetter
f = itemgetter(0,1)
print [(x[0],x[1],x[2]+y[2]+z[2]) for x in a for y in b for z in c if f(x)==f(y)==f(z)]
输出:
[(1, 2, 16)]
使用三元组的前两个元素作为字典的键。添加三元组的第三个元素,并将其用作字典条目的值。
d = {}
# group the tuples and sum
for x,y,z in a+b+c:
d[(x,y)] = d.get((x,y), 0) + z
results = []
# sort the keys and convert to a list of tuples
for k in sorted(d.keys()):
x,y = k
results.append((x,y,d[(x,y)]))
print results
为了更好地衡量,这里有一个很好的无聊方法,它将位置与自定义匹配逻辑(例如前两个元组组件)、转换逻辑(例如对第三个组件求和)分隔成普通的老式无聊的辅助函数,然后使用无聊的 for 循环进行简单的递归调用(每次通过过滤掉不匹配项来缩小)——这是避免浪费调用 itertools.product
或starmap
的一种方法。
from functools import partial
from operator import eq, is_not, itemgetter
a = [(1,2,4),(1,7,8),(1,5,4),(3,6,7)]
b = [(1,2,5),(1,9,3),(1,0,3),(3,6,8)]
c = [(2,6,3),(2,4,9),(2,8,5),(1,2,7)]
is_not_none = partial(is_not, None)
def my_match_criterion(t1, t2):
return eq(*map(itemgetter(0,1), (t1, t2)))
def my_transformation(t1, t2):
return t1[0:2] + (t1[2] + t2[2],)
def collapse_matches_with_transformation(tups, *args):
if args == ():
return tups
else:
collapsed = collapse_matches_with_transformation(*args)
for i,c in enumerate(collapsed):
include = False
for t in tups:
if my_match_criterion(t, c):
collapsed[i], include = my_transformation(t, c), True
if not include:
collapsed[i] = None
return filter(is_not_none, collapsed)
print collapse_matches_with_transformation(a, b, c)
我可能代表了脾气暴躁的逆向者——我的观点是,这至少和任何理解业务一样是 Pythonic 的。将术语"Pythonic"用于表示"不惜一切代价简洁的语法"已经变得有点太时髦了。许多人习惯于将单行理解或内联lambda
函数作为关键参数。他们能人为地轻松阅读这些东西,仅仅是熟悉的人工制品,云层思考这种方式是否真的更"可读",当然从封装的角度来看它是否好。
当然,如果你的问题只需要在一个小实例上解决一次,比如在解释器中玩耍时,那么无论什么工作......
但是,如果您可以重新访问此代码,如果发生这种情况的可能性很小,为什么不将需求的不同部分写入不同的、分离的函数呢?
在这个问题中,有几件事在起作用:(1)可能需要处理多少元组列表?总是只有3个吗?(2)匹配条件改变的可能性有多大?如果你突然需要包含一条新数据,使你的元组成为与前 3 个元素匹配的 4 元组,需要更改多少代码以及在多少地方更改?(3)如果需要更改转换怎么办?如果您需要乘法或对其他元素求和,而不是对第三个元素求和怎么办?
在几乎任何实际问题中都必须对这些考虑进行预算(阅读:您多次使用此代码的任何位置)。
在任何这些情况下,所有涉及抛出大量东西(如lambda x: x[0:2] ... blah
或只是将逻辑x[2] + y[2] + z[2]
直接放入返回结果的理解等)的垃圾代码,只会给出错误的简洁性,因为系统非常脆弱w.r.t.假设它只是 3 个 3 元组列表,其第 3 个组件只需要在前两个的唯一匹配条件下求和组件匹配。
最后,即使您知道所有这些事情都将得到修复,提供简洁性的更好方法是更改数据结构。例如,如果您首先将元组列表转换为计数器列表,将前两个元素的子元组作为键,则执行此操作非常快:
from collections import Counter
def countify(tups):
return Counter({t[0:2]:t[2] for t in tups})
a, b, c = map(countify, (a,b,c))
common_keys = set(a.keys()).intersection(b, c)
totals = a + b + c
print [k + (itemgetter(k)(totals),) for k in common_keys]
大多数人肯定会说第二种方法更"Pythonic"——但实际上,只有当你不介意严格承诺你正在对原始元组的前两个组件进行值求和时,这才是真的。该代码不会完全概括为 3 个以上的元组列表,它对转换或数据表示中的细微更改并不可靠。
这就是为什么我认为这种代码简洁不应该是"Pythonic"的同义词——它应该更多地是关于什么是实用和直接的。"简单"并不一定意味着"简短","复杂"与"更多行代码"并不完全相关。"可读性"是非常主观的,并且因经验而异。
做无聊的事情!编写额外的辅助函数!写那个for循环!你会比以后更兴奋!