【问题标题】:Duplicate strings in a list and add integer suffixes to newly added ones复制列表中的字符串并将整数后缀添加到新添加的字符串中
【发布时间】:2018-10-23 01:12:43
【问题描述】:

假设我有一个列表:

l = ['a', 'b', 'c']

及其后缀列表:

l2 = ['a_1', 'b_1', 'c_1']

我想要的输出是:

out_l = ['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

结果是上面两个列表的交错版本。

我可以编写常规的 for 循环来完成这项工作,但我想知道是否有更 Python 的方式(例如,使用列表理解或 lambda)来完成它。

我尝试过这样的事情:

list(map(lambda x: x[1]+'_'+str(x[0]+1), enumerate(a)))
# this only returns ['a_1', 'b_2', 'c_3']

此外,对于一般情况,即 l2 不一定是 l 的派生的 2 个或更多列表,需要进行哪些更改?

【问题讨论】:

标签: python string list performance duplicates


【解决方案1】:

yield

您可以使用生成器来获得优雅的解决方案。在每次迭代中,产生两次——一次使用原始元素,一次使用添加后缀的元素。

发电机需要耗尽;这可以通过在最后添加list 来完成。

def transform(l):
    for i, x in enumerate(l, 1):
        yield x
        yield f'{x}_{i}'  # {}_{}'.format(x, i)

您也可以使用 yield from 语法重写此代码以进行生成器委托:

def transform(l):
    for i, x in enumerate(l, 1):
        yield from (x, f'{x}_{i}') # (x, {}_{}'.format(x, i))

out_l = list(transform(l))
print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

如果您使用的版本早于 python-3.6,请将 f'{x}_{i}' 替换为 '{}_{}'.format(x, i)

概括
考虑一个一般场景,您有 N 个表单列表:

l1 = [v11, v12, ...]
l2 = [v21, v22, ...]
l3 = [v31, v32, ...]
...

你想交错的。这些列表不一定是相互派生的。

要处理这 N 个列表的交错操作,您需要迭代对:

def transformN(*args):
    for vals in zip(*args):
        yield from vals

out_l = transformN(l1, l2, l3, ...)

切片list.__setitem__

我会从性能的角度推荐这个。首先为一个空列表分配空间,然后使用切片列表分配将列表项分配到其适当的位置。 l 进入偶数索引,l'l 已修改)进入奇数索引。

out_l = [None] * (len(l) * 2)
out_l[::2] = l
out_l[1::2] = [f'{x}_{i}' for i, x in enumerate(l, 1)]  # [{}_{}'.format(x, i) ...]

print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

这始终是我计时中最快的(如下)。

概括
要处理 N 个列表,请迭代地分配给切片。

list_of_lists = [l1, l2, ...]

out_l = [None] * len(list_of_lists[0]) * len(list_of_lists)
for i, l in enumerate(list_of_lists):
    out_l[i::2] = l

zip + chain.from_iterable

一种功能性方法,类似于@chrisz 的解决方案。使用zip 构造对,然后使用itertools.chain 将其展平。

from itertools import chain
# [{}_{}'.format(x, i) ...]
out_l = list(chain.from_iterable(zip(l, [f'{x}_{i}' for i, x in enumerate(l, 1)]))) 

print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

iterools.chain被广泛认为是pythonic列表展平方法。

概括
这是最简单的泛化解决方案,我怀疑当 N 很大时对多个列表最有效。

list_of_lists = [l1, l2, ...]
out_l = list(chain.from_iterable(zip(*list_of_lists)))

性能

让我们看一下针对两个列表(一个带有后缀的列表)的简单情况的一些性能测试。一般情况下不会进行测试,因为结果因数据而异。

Benchmarking code, for reference.

功能

def cs1(l):
    def _cs1(l):
        for i, x in enumerate(l, 1):
            yield x
            yield f'{x}_{i}'

    return list(_cs1(l))

def cs2(l):
    out_l = [None] * (len(l) * 2)
    out_l[::2] = l
    out_l[1::2] = [f'{x}_{i}' for i, x in enumerate(l, 1)]

    return out_l

def cs3(l):
    return list(chain.from_iterable(
        zip(l, [f'{x}_{i}' for i, x in enumerate(l, 1)])))

def ajax(l):
    return [
        i for b in [[a, '{}_{}'.format(a, i)] 
        for i, a in enumerate(l, start=1)] 
        for i in b
    ]

def ajax_cs0(l):
    # suggested improvement to ajax solution
    return [j for i, a in enumerate(l, 1) for j in [a, '{}_{}'.format(a, i)]]

def chrisz(l):
    return [
        val 
        for pair in zip(l, [f'{k}_{j+1}' for j, k in enumerate(l)]) 
        for val in pair
    ]

【讨论】:

  • 从可读性、简单性和维护性的角度来看,我推荐yield,因为这不太可能成为主要瓶颈。 (可能数据量不够大,可能不是性能关键的应用程序。)生成器非常易于理解。如果出现问题,OP 可以返回并进行优化。 +1
  • @user1717828 很高兴您从中学到了一些东西!它们被称为 f-strings,是为 python-3.6+ 引入的。请查看this section of the docs 了解更多信息。学习愉快!
  • 我不明白为什么yield from。您能否为此添加更多解释?
  • yield from 提供了一种稍微紧凑的语法来执行两个 yield 语句所做的相同的事情 - 它委托 yield 过程,因此您不需要在一个可迭代对象上编写一个循环(或在本例中为两个 yield 语句)。
  • @cs95 性能比较存在偏差,因为ajax1234cs0 使用str.format,而其他函数使用更快的f-strings(sruthiV 甚至使用+)。因此,使用性能较低的格式化选项会有效地降低这些功能的性能。为了提供有意义的比较,需要更新函数以使用相同的格式选项。同样sruthiV 应该使用i//2 而不是int(i/2),因为它更有效(因此避免了额外的偏差)。
【解决方案2】:

您可以像这样使用列表推导:

l=['a','b','c']
new_l = [i for b in [[a, '{}_{}'.format(a, i)] for i, a in enumerate(l, start=1)] for i in b]

输出:

['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

可选的,更短的方法:

[j for i, a in enumerate(l, 1) for j in [a, '{}_{}'.format(a, i)]]

【讨论】:

    【解决方案3】:

    你可以使用zip:

    [val for pair in zip(l, [f'{k}_{j+1}' for j, k in enumerate(l)]) for val in pair]
    

    输出:

    ['a', 'a_1', 'b', 'b_2', 'c', 'c_3']
    

    【讨论】:

    • 您可以使用列表理解而不是 zip。不确定哪个更快...
    • 如果您查看时间,这比使用列表推导要快。更快。
    【解决方案4】:

    这是我的简单实现

    l=['a','b','c']
    # generate new list with the indices of the original list
    new_list=l + ['{0}_{1}'.format(i, (l.index(i) + 1)) for i in l]
    # sort the new list in ascending order
    new_list.sort()
    print new_list
    # Should display ['a', 'a_1', 'b', 'b_2', 'c', 'c_3']
    

    【讨论】:

      【解决方案5】:

      如果你想返回[["a","a_1"],["b","b_2"],["c","c_3"]],你可以写

      new_l=[[x,"{}_{}".format(x,i+1)] for i,x in enumerate(l)]
      

      这不是你想要的,而是你想要的["a","a_1"]+["b","b_2"]+["c","c_3"]。这可以使用sum() 从上述操作的结果中得出;由于您正在对列表求和,因此您需要将空列表添加为参数以避免错误。所以这给了

      new_l=sum(([x,"{}_{}".format(x,i+1)] for i,x in enumerate(l)),[])
      

      我不知道这在速度方面如何比较(可能不太好),但我发现比其他基于列表理解的答案更容易理解发生了什么。

      【讨论】:

      • @cᴏʟᴅsᴘᴇᴇᴅ 怎么不是被问到的?如果l==['a','b','c'],则结果为['a', 'a_1', 'b', 'b_2', 'c', 'c_3'],它避免了使用for 循环。
      • 呃抱歉,第一行没读完。但是,在列表上调用 sum() 通常是不受欢迎的,它比循环更糟糕。
      【解决方案6】:

      一个非常简单的解决方案:

      out_l=[]
      for i,x in enumerate(l,1):
          out_l.extend([x,f"{x}_{i}"])
      

      【讨论】:

        【解决方案7】:

        以下是对这个问题的更简单的列表理解:

        l = ['a', 'b', 'c']
        print([ele for index, val in enumerate(l) for ele in (val, val + f'_{index + 1}')])
        

        输出:

        ['a', 'a_1', 'b', 'b_2', 'c', 'c_3']
        

        请注意,这只是交错两个列表的更简单的解决方案。这不是多个列表的解决方案。我使用两个 for 循环的原因是,在撰写本文时,列表理解不支持元组解包。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-03-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-30
          • 1970-01-01
          相关资源
          最近更新 更多