合并列表,使唯一值覆盖非唯一值?
我花了很长时间搜索和尝试用列表推导式自己解决一个问题,但现在完全被难住了。这个问题是:
给定一个列表:
crazy_list = [a, b]
其中:
a = [['*'], ['*', '34', '*', '*', '*', '*', '*', '*', '*'], ['*', '*', '*', '102', '*', '*', '*', '*', '*'], ['*', '*', '*', '*', '*', '170', '*', '*', '*'], ['*', '*', '*', '*', '*', '*', '*', '238', '*'], ['*']]
b = [['*'], ['*', '*', '*', '102', '*', '*', '*', '*', '*'], ['*', '*', '*', '*', '*', '170', '*', '*', '*'], ['*', '*', '*', '*', '*', '*', '*', '238', '*'], ['*', '34', '*', '*', '*', '*', '*', '*', '*'], ['*']]
我该如何优雅地得到一个新的列表,里面包含多个列表,这样:
answer = [['*'], ['*', '34', '*', '102', '*', '*', '*', '*', '*'], ['*', '*', '*', '102', '*', '170', '*', '*', '*'], ['*', '*', '*', '*', '*', '170', '*', '238', '*'], ['*', '34', '*', '*', '*', '*', '*', '238', '*'], ['*']]
另外,我该如何将这种合并方式推广到:
[a, b, c, etc...]
(c及后面的部分会有和'a'、'b'一样数量的元素和子元素,但可能在不同的位置有不同的独特值)
====================v 到目前为止我能做的事情 v=====================
我能想到一个列表推导式,可以处理单个列表,像这样:
c = ['*', '34', '*', '*', '*', '*', '*', '*', '*']
d = ['*', '*', '*', '102', '*', '*', '*', '*', '*']
通过这样做:
[c[item_indx] if item != '*' else d[item_indx] for item_indx, item in enumerate(c)]
但即使是尝试将我目前的想法推广开来,也让我头疼不已……我可能是错误地接近了这个问题。如果能得到一些帮助或者对如何解决/更好地处理这个问题的想法,我将非常感激。另外,我不能简单地去掉'*',因为它们编码了重要的时间信息。再次感谢你的时间!
4 个回答
你可以试试这个。还有另一个版本
reduce(lambda a,b:[[x[0] if x[1] == '*' else x[1] for x in p]
for p in (zip(x,y) for x,y in zip(a,b)) ],(a,b))
适用于多个列表
reduce(lambda a,b:[[x[0] if x[1] == '*' else x[1] for x in p]
for p in (zip(x,y) for x,y in zip(a,b)) ],(a,b,c.....))
或者可以试试这个
reduce(lambda a,b:[[x[x[1] != '*'] for x in p]
for p in (zip(x,y) for x,y in zip(a,b)) ],(a,b,c,....))
注意:reduce 函数会对迭代器应用两个 lambda 函数,把结果作为下一个调用的第一个参数。
这个lambda 函数会把两个列表“压缩”在一起,然后对列表中的每个项目选择非 '*' 的项目,如果有冲突的话,会优先选择第一个列表中的项目。
假设你在处理两个列表的进展还不错,你可以试试这个方法。
def merge(a, b):
return [a[item_indx] if item != '*' else b[item_indx] for item_indx, item in enumerate(a)]
reduce(merge, crazy_list)
这里的 reduce
的作用是把上一次合并得到的列表和下一个元素再合并一次。比如说,如果有一个列表是 [a, b, c, d],那么首先 a 和 b 会合并,得到一个新的列表,然后这个新列表会和 c 合并,再得到一个新的结果,最后这个结果会和 d 合并。就这样一直进行下去。总的来说,reduce
会按照你提供的函数来逐步处理这些元素。
补充:好的,我明白你刚才说的意思了。所以我定义了另一个方法来处理列表中的列表。我知道你已经接受了一个答案,但我就把这个作为一个替代方案留在这里。
# a and b are lists of lists.
def merge_lists(a, b):
for i, v in enumerate(a):
a[i] = merge(a[i], b[i])
return a
reduce(merge_lists, (c, d))
首先,我们来看看怎么把列表中的一部分和列表中的一部分合并在一起:
[x if x != '*' else y for (x, y) in zip(a, b)]
我们从这两个列表中取出一对一对的元素,然后对于每一对,选择其中一个元素,最后把这些选择的结果放到一个新列表里。
如果我们想把这个方法扩展到多个列表上:其实没问题,zip
可以接受多个参数,所以我们可以从任意数量的列表中组合出元素对。不过,这样一来,我们就需要从每一对中取出“如果有的话就取非‘*’的元素,否则就取‘*’”。
一种实现方法是先把这些元素放到一个set
集合里,去掉'*'
,然后从结果中取出任意一个元素(如果集合不为空),如果集合为空就用'*'
。我们可以用set
的.pop()
方法来获取“任意元素”,不过如果集合是空的,它会报错。所以我们可以先写一个包装函数,然后再使用它:
def non_star_if_possible(items):
try: return set(items).difference('*').pop()
except KeyError: return '*'
def merge(lists):
return [non_star_if_possible(items) for items in zip(*lists)]
最后,我们实际上得到了一个列表的列表的列表,我们想要从这些嵌套的列表中逐个取出元素。所以我们可以再次使用之前的zip
技巧:
def merge_all(data):
return [merge(lists) for lists in zip(*data)]
如果你愿意的话,我们也可以把最后这两步合并成一个嵌套的列表推导式,但这样写可能会更清晰一些。:)