问题 给定切片列表,如何按顺序拆分序列?


给定切片列表,如何基于它们分离序列?

我有很长的氨基酸字符串,我想根据列表中的起止值进行拆分。一个例子可能是解释它的最明确的方式:

str = "MSEPAGDVRQNPCGSKAC"
split_points = [[1,3], [7,10], [12,13]]

output >> ['M', '(SEP)', 'AGD', '(VRQN)', 'P', '(CG)', 'SKAC']

额外的括号是显示从split_points列表中选择的元素。我不认为开始 - 停止点会重叠。

我有一堆可行的想法,但看起来非常低效(代码长度明智),似乎必须有一个很好的pythonic方式来做到这一点。


8512
2017-11-12 19:19


起源

这是一个非常好的问题,并不仅限于字符串。 - Jed Smith
有关示例代码的一点需要注意,不要在python中使用str作为变量。这就是内置类的名称。阴影内置插件几乎总会在以后咬你。 - Bryan McLemore
好的,谢谢。 - latentflip
str 是一个 真 这里的名字不好,因为我对似乎调用str内置的解决方案感到困惑,而不是切割str变量。不幸的是,你现在无法编辑,因为发布的答案是均匀的 更多 混乱。 - PaulMcG


答案:


你有分裂字符串的奇怪方法:

def splitter( s, points ):
    c = 0
    for x,y in points:
        yield s[c:x]
        yield "(%s)" % s[x:y+1]
        c=y+1
    yield s[c:]

print list(splitter(str, split_points))
# => ['M', '(SEP)', 'AGD', '(VRQN)', 'P', '(CG)', 'SKAC']

# if some start and endpoints are the same remove empty strings.
print list(x for x in splitter(str, split_points) if x != '')

9
2017-11-12 19:49



这个赢得速度,但我超过投票限制。老实说,我认为C中的这个版本应该折叠成itertools。你应该提交补丁。 - Jed Smith
@Jed,我实际上比他们的时间慢了大约100毫秒。虽然他使用的发电机很优雅。 - Bryan McLemore
确认!我意外地投了这票,现在不让我改变它。如果有人能够解决这个问题,请做。 - Bryan McLemore
+1,非常干净的解决方案 - RedGlyph
@Bryan: THC4k [5.5428318977355957, 5.4677660465240479, 5.4681069850921631] Bryan [6.1738638877868652, 6.0337700843811035, 5.9942479133605957],你的速度稍慢......不确定你正在使用什么构造(那是1,000,000次运行) - Jed Smith


这是一个简单的解决方案。抓取该点指定的每个集合。

In[4]:  str[p[0]:p[1]+1] for p in split_points]
Out[4]: ['SEP', 'VRQN', 'CG']

要获得括号:

In[5]:  ['(' + str[p[0]:p[1]+1] + ')' for p in split_points]
Out[5]: ['(SEP)', '(VRQN)', '(CG)']

这是完成交易的更干净的方式:

results = []

for i in range(len(split_points)):
    start, stop = split_points[i]
    stop += 1

    last_stop = split_points[i-1][1] + 1 if i > 0 else 0

    results.append(string[last_stop:start])        
    results.append('(' + string[start:stop] + ')')

results.append(string[split_points[-1][1]+1:])

以下所有解决方案都很糟糕,而且比其他任何方式更有趣,请不要使用它们!

这更像是一个WTF解决方案,但我想我会发布它,因为它在评论中被要求:

split_points = [(x, y+1) for x, y in split_points]
split_points = [((split_points[i-1][1] if i > 0 else 0, p[0]), p) for i, p in zip(range(len(split_points)), split_points)]
results = [string[n[0]:n[1]] + '\n(' + string[m[0]:m[1]] + ')' for n, m in split_points] + [string[split_points[-1][1][1]:]]
results = '\n'.join(results).split()

还在试图找出一个班轮,这里有两个:

split_points = [((split_points[i-1][1]+1 if i > 0 else 0, p[0]), (p[0], p[1]+1)) for i, p in zip(range(len(split_points)), split_points)]
print '\n'.join([string[n[0]:n[1]] + '\n(' + string[m[0]:m[1]] + ')' for n, m in split_points] + [string[split_points[-1][1][1]:]]).split()

并且永远不应该使用的一个班轮:

print '\n'.join([string[n[0]:n[1]] + '\n(' + string[m[0]:m[1]] + ')' for n, m in (((split_points[i-1][1]+1 if i > 0 else 0, p[0]), (p[0], p[1]+1)) for i, p in zip(range(len(split_points)), split_points))] + [string[split_points[-1][1]:]]).split()

2
2017-11-12 19:36



输出缺少分割点之间的部分。如果你通过列表​​理解得到它,我的帽子就是你的。 - gahooa
嘿,也许5线的答案是pythonic的方式:) - latentflip
那么我的更长的解决方案需要大约660ms才能在本地进行100k迭代。我的单行解决方案需要1.17秒来完成100k所以展开的版本肯定更快 - Bryan McLemore
@latentflip:最确定的是,1-liners对我来说似乎更加珍贵。 - PaulMcG


这里有一些可行的代码。

result = []
last_end = 0
for sp in split_points:
  result.append(str[last_end:sp[0]])
  result.append('(' + str[sp[0]:sp[1]+1] + ')')
  last_end = sp[1]+1
result.append(str[last_end:])

print result

如果您只想要括号中的部分,它会变得更简单:

result = [str[sp[0]:sp[1]+1] for sp in split_points]

0
2017-11-12 19:29



我认为你需要last_end = sp [1] +1,然后在for循环之后需要result.append(str [last_end:])。但除此之外有效。我认为列表推导可能是可行的,但也许它会过于复杂。 - latentflip
哦,是的,我为非选定元素设置了边缘条件。谢谢你的解决方案。 - jblocksom
THC4k的答案获胜的原因是因为使用了发生器(即使你做了大致相似的事情)。对于基本迭代器,即 for slice in jblocksom(A, B):,产生每个部分而不是构建列表并返回它,它的内存效率更高。这是你的唯一原因,因为逻辑几乎完全一样。 - Jed Smith
@JedSmith很高兴了解内存效率,我不会想到这一点。 - latentflip


这是一个将split_points转换为常规字符串切片然后打印出适当切片的解决方案:

str = "MSEPAGDVRQNPCGSKAC"
split_points = [[1, 3], [7, 10], [12, 13]]

adjust = [s for sp in [[x, y + 1] for x, y in split_points] for s in sp]
zipped = zip([None] + adjust, adjust + [None])

out = [('(%s)' if i % 2 else '%s') % str[x:y] for i, (x, y) in
       enumerate(zipped)]

print out

>>> ['M', '(SEP)', 'AGD', '(VRQN)', 'P', '(CG)', 'SKAC']

0
2017-11-12 20:37





>>> str =“MSEPAGDVRQNPCGSKAC”
>>> split_points = [[1,3],[7,10],[12,13]]
>>>
>>> all_points = sum(split_points,[0])+ [len(str)-1]
>>> map(lambda i,j:str [i:j + 1],all_points [: -  1],all_points [1:])
['MS','SEP','PAGDV','VRQN','NPC','CG','GSKAC']
>>>
>>> str_out = map(lambda i,j:str [i:j + 1],all_points [: -  1:2],all_points [1 :: 2])
>>> str_in = map(lambda i,j:str [i:j + 1],all_points [1:-1:2],all_points [2 :: 2])
>>> sum(map(list,zip(['(%s)'%s for s in str_in],str_out [1:])),[str_out [0]])
['MS','(SEP)','PAGDV','(VRQN)','NPC','(CG)','GSKAC']

0
2017-11-12 20:56





可能不是为了优雅,而是因为我可以在一个oneliner :)

>>> reduce(lambda a,ij:a[:-1]+[str[a[-1]:ij[0]],'('+str[ij[0]:ij[1]+1]+')',
            ij[1]], split_points, [0])[:-1] + [str[split_points[-1][-1]+1:]]

['M', '(SEP)', 'PAGD', '(VRQN)', 'NP', '(CG)', 'SKAC']

也许你喜欢它。这里有一些解释:

在你的问题中,你传递了一组切片,并且隐含地你也希望得到补片组(以生成未加括号的[是英语?]切片)。所以基本上,每个切片[i,j]都缺少先前的j。例如[7,10]缺少3和[1,3]缺少0。

reduce 进程列表和每一步都通过 到目前为止的输出 (a)加上 下一个输入元素 (ij)。诀窍是,除了生成普通输出之外,我们每次都会添加一个额外的变量---一种内存 - 这是在下一步中检索的 a[-1]。在这个特定的例子中,我们存储了最后的j值,因此我们始终拥有完整的信息来提供未加密的子串和子括号的子串。

最后,使用[:-1]删除内存,并用原始str的其余部分替换 [str[split_points[-1][-1]+1:]]


0
2017-11-12 20:32



我不确定哪一个更野蛮,你的一个班轮或我的。 - Bryan McLemore
我喜欢它,而不是我特别遵循它(缺乏lambda函数和reduce()的实践)。 - latentflip
这很有趣,但是我的解开后的解决方案就更好了,呵呵。 - Bryan McLemore
@Bryan:你今天会成就伟大的。 - Paul