【问题标题】:Merging consecutive items in a list if they occur more than once python如果它们多次出现python,则合并列表中的连续项目
【发布时间】:2021-10-01 10:42:36
【问题描述】:

我正在寻找一种算法,如果它们在列表中多次出现,则可以合并列表中的连续项目。希望看到任何解决方法!

输入是一个列表,每个项目都有自己的字符,输出也是一个列表。

这里有一个例子来说明:

假设我的字符串是“hello yellow”。

我们会将其转换为列表。

['h', 'e', 'l', 'l', 'o', ' ', 'y', 'e', 'l', 'l', 'o', 'w']

然后,我们想查看哪些连续项目出现了多次。从左边开始,['e', 'l'] 出现多次。

我们将它们合并为 1 项,而不是列表中的 2 项。

['h', 'el', 'l', 'o', ' ', 'y', 'el', 'l', 'o', 'w']

现在,我们看到“el”、“l”不止一次出现。我们将它们合并在一起。

['h', 'ell', 'o', ' ', 'y', 'ell', 'o', 'w']

现在,我们将 'ell'、'o' 合并在一起,因为它们不止一次出现。

['h', 'ello', ' ', 'y', 'ello', 'w']

这是最终输出:['h', 'ello', ' ', 'y', 'ello', 'w']

我希望能够对任何输入执行此操作。

例如,另一个输入示例是列表:

['h', 'e', 'l', 'l', 'o', ' ', 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']

输出将是:

['hello ', 'hello ', 'w', 'o', 'r', 'l', 'd']

我尝试了以下方法:

s = "hello there"

def merge_items(s):
  d = {}
  for i in range(0, len(s)):
      k = s[i:i+2]
      d[k] = d.setdefault(k, 0) + 1
  print(d)

  l = []
  for i in range(0, len(s)):
      k = s[i:i + 2]
      if d[k] > 1:
        l.append(k)
      else:
        l.extend(s[i])
  return l
  
print(merge_items(s))

'e' 在这里打印了两次,它对其他输入不起作用,例如“hello hello”。我无法将其扩展为重复超过 2 个字符的字符串。

不知道如何改进这一点,因为我是 Python 的初学者。

输出:

{'he': 2, 'el': 1, 'll': 1, 'lo': 1, 'o ': 1, ' t': 1, 'th': 1, 'er': 1, 're': 1, 'e': 1}
['he', 'e', 'l', 'l', 'o', ' ', 't', 'he', 'e', 'r', 'e']

如果我输入“hello hello world”作为字符串,输出是这样的:

{'he': 2, 'el': 2, 'll': 2, 'lo': 2, 'o ': 2, ' h': 1, ' w': 1, 'wo': 1, 'or': 1, 'rl': 1, 'ld': 1, 'd': 1}
['he', 'el', 'll', 'lo', 'o ', ' ', 'he', 'el', 'll', 'lo', 'o ', ' ', 'w', 'o', 'r', 'l', 'd']

现在,我正在数对,但不确定如何将“你好”合并到一个项目中。

【问题讨论】:

  • “非常感谢”:呃,等等……你忘了问一个与你在这个挑战中的尝试有关的问题。您尝试过什么,哪里出错了?
  • 刚刚添加。第一次使用 Stack Overflow,谢谢你告诉我。
  • 您能否添加示例输入和您的代码不起作用的预期输出?
  • 也添加了。示例输入是第一个字符串,并列出了输出。

标签: python list


【解决方案1】:

如果您尝试一次执行多个替换,则很难防止它们相互冲突。更容易每次只进行一次替换并迭代直到无事可做:

from collections import Counter
from typing import Sequence


def merge_items(s: Sequence[str]) -> Sequence[str]:
    def merge_once(s: Sequence[str]) -> Sequence[str]:
        if len(s) <= 1:
            return s
        ss = list(zip(s, s[1:])) + [(s[-1], '')]
        c = Counter(ss)
        try:
            a, b = next(p for p, count in c.items() if count > 1)
        except StopIteration:
            return s
        i = 0
        ret = []
        while i < len(ss):
            x, y = ss[i]
            if x + y == a + b:
                ret.append(x + y)
                i += 2
            else:
                ret.append(x)
                i += 1
        return ret
    t = merge_once(s)
    while t != s:
        s = t
        t = merge_once(s)
    return s


print(merge_items("hello there"))  
# ['he', 'l', 'l', 'o', ' ', 't', 'he', 'r', 'e']

print(merge_items("hello hello world"))  
# ['hello ', 'hello ', 'w', 'o', 'r', 'l', 'd']

【讨论】:

  • 你为什么选择递归处理它,而不是使用字典?我是 Python 新手,但认为递归方法对于较大的文本效率较低。谢谢!
  • 这不是递归,而是迭代——我在循环中调用merge_once 助手,它不是在调用自身。 Counter 是一个字典。
【解决方案2】:

这是一种动态编程的方法——想法是将字符串表示为一个矩阵来比较每个字符对。例如,“你好黄色”可以翻译成下面的矩阵。请注意,我们所追求的序列现在可以识别为1s 的最长对角序列(当然不包括对角线),这给了我们“hello”。

#  h  e  l  l  o     y  e  l  l  o  w
h [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
e [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]
l [0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0]
l [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0]
o [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
y [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
e [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
l [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]
l [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
o [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
w [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

事实上,我们甚至不需要完整的网格。将具有相同字符的坐标收集到一个集合中,然后得到最长的子序列就足够了,这或多或少是直截了当的。

def get_pairs(s):
    """
    Input: a string or list of characters
    Output: a set {(x, y), (x2, y2), ...} where s[x] == s[y]
    """
    n = len(s)

    pairs = set()

    # This iterates through the upper half of the imaginary matrix
    for y in range(n):
        for x in range(y+1, n):
            if s[x] == s[y]:
                pairs.add((x,y)) # collect matching pairs

    return pairs


# There's some potential for optimization here, but you get the idea.
def longest_subsequence(pairs):
    """
    Input: A sequence of coordinates [(x, y), (x2, y2), ...] 
    Output: The longest subsequence where [(x, y), (x+1, y+1), (x+2, y+2)] applies
    """
    longest = []

    for p in pairs:
        seq = [p]
        x, y = p

        # keep collecting items on the diagonal
        while True:
            x, y = x+1, y+1

            if (x,y) in pairs:
                seq.append((x,y))
            else:
                break
                
        if len(seq) > len(longest):
            longest = seq
    
    return longest

运行一些测试,这似乎按预期工作,但有一个问题:如果字符串仅包含一个字符,则结果似乎违反直觉。一般来说,重叠匹配似乎是个问题,但你对边界情况没有多说。

test = ['hello yellow',
        'hello hello world',
        'aaaaabaaaaa',
        'aaaaaaaaaa' # this is problematic
       ]

for s in test:
    
    pairs = get_pairs(s)
    result = longest_subsequence(pairs)
    
    print(f'{s=}')
    print(f'{result=}')
    print(repr(''.join(s[i] for i,_ in result)))
    
    print()

结果:

s='hello yellow'
result=[(7, 1), (8, 2), (9, 3), (10, 4)]
'ello'

s='hello hello world'
result=[(6, 0), (7, 1), (8, 2), (9, 3), (10, 4), (11, 5)]
'hello '

s='aaaaabaaaaa'
result=[(6, 0), (7, 1), (8, 2), (9, 3), (10, 4)]
'aaaaa'

s='aaaaaaaaaa'
result=[(1, 0), (2, 1), (3, 2), (4, 3), (5, 4), (6, 5), (7, 6), (8, 7), (9, 8)]
'aaaaaaaaa'

编辑:忘了概括。该矩阵还包含足够的信息来收集多个匹配项。为此,我们可以将第二个函数替​​换为以下收集所有子序列的函数。

from collections import defaultdict

def find_all_subsequences(pairs):
    """
    Input: A sequence of coordinates [(x, y), (x2, y2), ...] 
    Output: A dictionary mapping substrings to sets of subsequences {str: {((x, y),), ...}}
    """
    
    def to_string(result):
        return ''.join(s[i] for i,_ in result)
    
    output = defaultdict(set)
    seen = set() # to prevent double matches
    
    for p in pairs:
        
        if p in seen:
            continue
                
        seq = [p]
        x, y = p

        # collect diagonal sequences
        while True:
            x, y = x+1, y+1
            
            if (x,y) in pairs:
                seq.append((x,y))
                
                # keep track of visited elements
                seen.add((x, y))
                
                # add to the output
                output[to_string(seq)].add(tuple(seq))
            else:
                output[to_string(seq)].add(tuple(seq))
                break

    return output

这将类似于上面的函数,但获取所有组合,例如,

s = "hello yellow marshmellow, don't yell"

pairs = get_pairs(s)
result = find_all_subsequences(pairs)

for k in sorted(result, key=len, reverse=True):
    v = result[k]
    print(f"Sequence: {k!r}, length: {len(k)}, occurrences: {len(v)+1}")
    #print(v) # uncomment to see the raw data

返回所有子序列,如下所示。您可以根据需要对其进行过滤。

Sequence: ' yell', length: 5, occurrences: 2
Sequence: 'ellow', length: 5, occurrences: 2
Sequence: ' yel', length: 4, occurrences: 2
Sequence: 'llow', length: 4, occurrences: 2
Sequence: 'ello', length: 4, occurrences: 4
Sequence: ' ye', length: 3, occurrences: 2
Sequence: 'llo', length: 3, occurrences: 2
Sequence: 'ell', length: 3, occurrences: 6
Sequence: ' y', length: 2, occurrences: 2
Sequence: 'll', length: 2, occurrences: 2
Sequence: 'el', length: 2, occurrences: 6
Sequence: 'lo', length: 2, occurrences: 2
Sequence: 'h', length: 1, occurrences: 2
Sequence: 'o', length: 1, occurrences: 4
Sequence: 'l', length: 1, occurrences: 17
Sequence: 'm', length: 1, occurrences: 2
Sequence: ' ', length: 1, occurrences: 6

【讨论】:

    猜你喜欢
    • 2019-06-13
    • 1970-01-01
    • 2022-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多