【问题标题】:Efficiently generate a list of all possible substrings of a string有效地生成字符串的所有可能子字符串的列表
【发布时间】:2019-07-18 04:21:34
【问题描述】:

我想编写一个函数,该函数根据子字符串的最小和最大长度有效地返回字符串的所有可能子字符串的列表。 (字符串只包含大写字母。)

例如,对于字符串'THISISASTRING',对于min_length=3max_length=4,它应该返回:

['THI', 'THIS', 'HIS', 'HISI', 'ISI', 'ISIS', 'SIS', 'SISA', 'ISA',
 'ISAS', 'SAS', 'SAST', 'AST', 'ASTR', 'STR', 'STRI', 'TRI', 'TRIN',
 'RIN', 'RING', 'ING']

我正在寻找一种比我目前的解决方案更快的解决方案:

import cProfile

random_english_text = \
    'AHOUSEISABUILDINGTHATISMADEFORPEOPLETOLIVEINITISAPERMANENTBUILDINGTHATISMEANTTOSTAYSTANDINGITISNOTEASILYPACKEDU' \
    'PANDCARRIEDAWAYLIKEATENTORMOVEDLIKEACARAVANIFPEOPLELIVEINTHESAMEHOUSEFORMORETHANASHORTSTAYTHENTHEYCALLITTHEIRHO' \
    'MEBEINGWITHOUTAHOMEISCALLEDHOMELESSNESSHOUSESCOMEINMANYDIFFERENTSHAPESANDSIZESTHEYMAYBEASSMALLASJUSTONEROOMORTH' \
    'EYMAYHAVEHUNDREDSOFROOMSTHEYALSOAREMADEMANYDIFFERENTSHAPESANDMAYHAVEJUSTONELEVELORSEVERALDIFFERENTLEVELSAHOUSEI' \
    'SSOMETIMESJOINEDTOOTHERHOUSESATTHESIDESTOMAKEATERRACEORROWHOUSEACONNECTEDROWOFHOUSES'

def assemble_substrings(textstring, length_min, length_max):
    str_len = len(textstring)
    subStringList = []
    idx = 0
    while idx <= str_len - length_min:
        max_depth = min(length_max, str_len - idx)
        for i in list(range(length_min, max_depth + 1)):
            subString = textstring[idx:idx + i]
            subStringList.append(subString)
        idx += 1
    return subStringList


pr = cProfile.Profile()
pr.enable()

for i in range(0, 1000):
    list_of_substrings = assemble_substrings(textstring=random_english_text, length_min=4, length_max=10)

pr.disable()
pr.print_stats(sort='cumtime')

这产生了我:

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1000    1.332    0.001    1.672    0.002 <input>:11(assemble_substrings)
  3654000    0.227    0.000    0.227    0.000 {method 'append' of 'list' objects}
   525000    0.112    0.000    0.112    0.000 {built-in method builtins.min}
     1000    0.000    0.000    0.000    0.000 {built-in method builtins.len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

现在,从分析器的输出来看,我对如何加速此功能没有太多了解。

使此功能尽可能快的最佳方法是什么? 我应该使用与列表不同的数据结构吗? 使用赛通?还是将这段代码写在外部 C/C++ 共享对象中?

非常感谢输入,通常还包括如何有效地处理类似于上述 Python 中的字符串和操作。

【问题讨论】:

  • 你想用这个做什么?可能有比包含所有选项的列表更好的数据结构。
  • 另外我认为你有一个边缘情况的错误,当循环位于字符串的最后一个 length_min 个字符时,它仍然为每个允许的长度添加字符串。我认为字符串的结尾需要被视为特殊情况。
  • 对于结果列表(或其他数据结构)中的所有项目,我希望能够: 1. 检查每个项目,如果它们包含在另一个列表(或其他数据结构)中) 2. 遍历所有元素并将它们用作访问字典的键
  • 你的问题让我想起了其他问题的一部分,虽然我不记得具体是哪一个。也许k-nucleotide
  • 你为什么使用list(range(...))?似乎没有必要打电话给list()

标签: python c++ c string list


【解决方案1】:

您可以将’’.join() 映射到压缩字符串:

def func(s, min_l, max_l):
    return [subl for i in range(min_l, max_l + 1)
                 for subl in map(''.join, zip(*[s[i:] for i in range(i)]))]

简介:

random_english_text = \
    'AHOUSEISABUILDINGTHATISMADEFORPEOPLETOLIVEINITISAPERMANENTBUILDINGTHATISMEANTTOSTAYSTANDINGITISNOTEASILYPACKEDU' \
    'PANDCARRIEDAWAYLIKEATENTORMOVEDLIKEACARAVANIFPEOPLELIVEINTHESAMEHOUSEFORMORETHANASHORTSTAYTHENTHEYCALLITTHEIRHO' \
    'MEBEINGWITHOUTAHOMEISCALLEDHOMELESSNESSHOUSESCOMEINMANYDIFFERENTSHAPESANDSIZESTHEYMAYBEASSMALLASJUSTONEROOMORTH' \
    'EYMAYHAVEHUNDREDSOFROOMSTHEYALSOAREMADEMANYDIFFERENTSHAPESANDMAYHAVEJUSTONELEVELORSEVERALDIFFERENTLEVELSAHOUSEI' \
    'SSOMETIMESJOINEDTOOTHERHOUSESATTHESIDESTOMAKEATERRACEORROWHOUSEACONNECTEDROWOFHOUSES'

pr = cProfile.Profile()
pr.enable()

for i in range(0, 1000):
    list_of_substrings = func(random_english_text, 4, 10)

pr.disable()
pr.print_stats(sort='cumtime')

输出:

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
  1000    0.002    0.000    0.772    0.001 Untitled.py:3(func)
  7000    0.014    0.000    0.014    0.000 Untitled.py:4(<listcomp>)
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

【讨论】:

    【解决方案2】:

    我不确定它实际上有多快(阅读:需要进一步调查),但对我来说,这听起来像是正则表达式的任务(re 模块),我会在下面做方式:

    import re
    minlen = 3
    maxlen = 4
    s = 'THISISASTRING'
    out = []
    for i in range(minlen,maxlen+1):
        p = re.compile('(?=(.{'+str(i)+'}))',re.DOTALL)
        out = out+p.findall(s)
    print(out)
    

    输出:

    ['THI', 'HIS', 'ISI', 'SIS', 'ISA', 'SAS', 'AST', 'STR', 'TRI', 'RIN', 'ING', 'THIS', 'HISI', 'ISIS', 'SISA', 'ISAS', 'SAST', 'ASTR', 'STRI', 'TRIN', 'RING']
    

    我使用来自 topicbernie 答案让 findall 以重叠的方式工作。我知道这个特殊的 零长度断言 可以利用可变长度模式,但是当我执行re.findall('(?=(.{3,4}))','THISISASTRING') 时,它产生了['THIS', 'HISI', 'ISIS', 'SISA', 'ISAS', 'SAST', 'ASTR', 'STRI', 'TRIN', 'RING', 'ING'],这不是我们想要的输出。因此,我提出了混合for-re 解决方案,每一圈循环用于特定长度的字符串。我必须承认,我在re 方面做得不够好,无法使其以单通道方式工作(仅re,没有for),但是也许其他一些用户能够完成它?

    【讨论】:

    • 谢谢你的回答,刚刚检查过,你的解决方案比我的快 3 倍。但是,我更希望能有更大幅度的加速。 :)
    【解决方案3】:

    为什么不简单地使用超过 2 个范围的列表理解和字符串切片?

    t = "SOMETEXT"
    
    print(t)
    
    minl = 3
    maxl = 8
    
    parts = [t[i:i+j] for i in range(len(t)-minl) for j in range(minl,maxl+1)]
    
    print(parts)
    

    输出:

    ['SOM', 'SOME', 'SOMET', 'SOMETE', 'SOMETEX', 'SOMETEXT', 'OME', 'OMET', 'OMETE', 'OMETEX', 
     'OMETEXT', 'OMETEXT', 'MET', 'METE', 'METEX', 'METEXT', 'METEXT', 'METEXT', 'ETE', 'ETEX', 
     'ETEXT', 'ETEXT', 'ETEXT', 'ETEXT', 'TEX', 'TEXT', 'TEXT', 'TEXT', 'TEXT', 'TEXT']
    

    如果顺序不重要,您可以使用集合来删除重复项 - 否则为按顺序存储创建一个唯一列表:

    nodupes = [] 
    k = set() 
    for l in parts:
        if l in k:
            pass
        else:
            nodupes.append(l)
            k.add(l)
    
    print(nodupes)   
    

    输出:

    ['SOM', 'SOME', 'SOMET', 'SOMETE', 'SOMETEX', 'SOMETEXT', 'OME', 'OMET', 'OMETE', 'OMETEX', 
     'OMETEXT', 'MET', 'METE', 'METEX', 'METEXT', 'ETE', 'ETEX', 'ETEXT', 'TEX', 'TEXT']
    

    有时间安排:

    def doit(t,minl,maxl):
        parts = [t[i:i+j] for i in range(len(t)-minl) for j in range(minl,maxl+1)]
        return parts
    
    pr = cProfile.Profile()
    pr.enable()
    
    for i in range(0, 1000):
        list_of_substrings = doit(random_english_text, 4, 10)
    
    pr.disable()
    pr.print_stats(sort='cumtime')
    

             3001 function calls in 0.597 seconds
    
       Ordered by: cumulative time
    
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
         1000    0.001    0.000    0.597    0.001 main.py:10(doit)
         1000    0.596    0.001    0.596    0.001 main.py:11(<listcomp>)
         1000    0.000    0.000    0.000    0.000 {built-in method builtins.len}
            1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
    

    你的给:4181001 function calls in 1.614 seconds

    【讨论】:

    • 非常感谢您的回答!不用说,你的答案比我的要优雅得多。 :) 关于速度,您的解决方案比我的解决方案快 3 倍左右,这已经很好了,但如果它可以快得多,那就太好了 :)
    猜你喜欢
    • 1970-01-01
    • 2019-02-17
    • 1970-01-01
    • 2021-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多