合并列表项与前一项

7

我试图将列表项与前面的项合并,如果它们不包含特定的前缀,则在这样做时在这些列表项之间添加一个\n

prefix  = '!'
cmds    = ['!test','hello','world','!echo','!embed','oh god']

output  = ['!test\nhello\nworld','!echo','!embed\noh god']

我尝试了类似以下的操作

for i in list(range(0,len(cmds))):
    if not cmds[i+1].startswith(prefix):
        cmds[i] += cmds.pop(i+1)

但总是遇到list index out of range错误。

如果我的表述不太清楚或者看起来像是一个明显的修复方法,请原谅,我对Python/编程还比较新。

编辑:

我已经成功让它工作了。

prefix = '!'
cmds    = ['!test','hello','world','!echo','!embed','oh god']
print(list(range(0,len(cmds))))
for i in reversed(range(len(cmds))):
    if not cmds[i].startswith(prefix):
        cmds[i-1] += '\n'+cmds.pop(i)
print(cmds)

但是你的答案看起来更加整洁高效,非常感谢大家。

3
如果使用 pop 函数,列表大小会发生改变,因此不要增加索引。 - Mad Physicist
4个回答

10
我建议创建一个新列表,就像您在问题规范中所示的一样:
prefix  = '!'
cmds    = ['!test','hello','world','!echo','!embed','oh god']

output  = []
for cmd in cmds:
    if cmd.startswith(prefix) or not output:
        output.append(cmd)
    else:
        output[-1] += "\n" + cmd  # change the string in the last element of output

结果是:
>>> output
['!test\nhello\nworld', '!echo', '!embed\noh god']

3

使用 itertools.groupbyitertools.accumulate,这是一个简单的解决方案:

from itertools import accumulate, groupby
from operator import itemgetter

x = ['!test','hello','world','!echo','!embed','oh god']

cumsum = accumulate(map(lambda s: s.startswith('!'), x))
result = ['\n'.join(map(itemgetter(0), g)) for _, g in groupby(zip(x, cumsum), itemgetter(1))]

这似乎是两行,因为我想让它半可读,但这并不总是必要的:

这个句子看起来与IT技术无关。
result = ['\n'.join(map(itemgetter(0), g)) for _, g in groupby(zip(x, accumulate(map(lambda s: s.startswith('!'), x))), itemgetter(1))]
cumsum 提供了从开始到当前位置找到的 ! 元素数量。这为 groupby 提供了一个很好的键。它通过将 str.startswith 返回的布尔值累加到整数中来实现。
最终结果使用 cumsum 作为键,但是使用换行符将 x 的分组元素连接起来。
这里有一个可以试玩的IDEOne链接

1
你可以使用列表推导式"也"来完成它。
In [1]: cmds    = ['!test','hello','world','!echo','!embed','oh god']
In [2]: prefix  = '!'
In [3]: inds = [i for i, x in enumerate(cmds) if prefix in x]
In [4]: inds.append(len(cmds))
In [5]: lens = list(zip(inds, inds[1:]))
# [(0, 3), (3, 4), (4, 6)]

In [6]: ["\n".join(cmds[a:b]) for a, b in lens]
Out[6]: ['!test\nhello\nworld', '!echo', '!embed\noh god']

1

一种比较长,但可以轻松推广到其他情况的解决方案,使用itertools.groupby:

from itertools import groupby

class StartGroupOnPrefix:
    def __init__(self, prefix):
        self.output = False
        self.prefix = prefix

    def __call__(self, item):
        if item.startswith(self.prefix):
            self.output = not self.output
        return self.output


prefix  = '!'
cmds    = ['!test','hello','world','!echo','!embed','oh god']

condition = StartGroupOnPrefix(prefix)

out = ['\n'.join(group) for f, group in groupby(cmds, condition)]
print(out)

#  ['!test\nhello\nworld','!echo','!embed\noh god']

我们有了迭代器,就不必一次性创建整个输出列表,可以即时生成每个输出:

for grouped_item in ('\n'.join(group) for f, group in groupby(cmds, condition)):
    print('-----------\n', grouped_item)

# -----------
#  !test
# hello
# world
# -----------
#  !echo
# -----------
#  !embed
# oh god

一点解释: groupby(iterable) 每当从 iterable 中获取到一个不同的项时,就会开始一个新的组。 groupby(iterable, key) 在每次返回值由 key 函数更改时开始一个新的组。我们的 condition 函数在每次项以前缀开头时,其输出在 TrueFalse 之间交替变化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接