【问题标题】:Parse all substrings before a pattern without a loop?在没有循环的模式之前解析所有子字符串?
【发布时间】:2018-10-23 03:53:58
【问题描述】:

我有一个长字符串,其中包含许多用空格分隔的数字(有时甚至会在其中抛出一个新行)。我想遍历字符串并将所有数字附加到 0.000000000000000000e+00 数字开头之前的新列表中。所以这是我的字符串示例:

my_string = '1.249132165057832031e+13 1.638194600635518555e+13 2.127995187558799219e+13 2.744617593148214062e+13 -2.558800658636701519e+28 5.918883595148564680e+30 3.603563681248702509e+31 4.325917213186498068e+31 4.911908042151239481e+31 4.463331378152286632e+31 3.684371076399113503e+31 2.500614504012405068e+31 9.997365425073173512e+30 -7.046725649106466938e+30 -2.192076417151744811e+31 -2.531287564917444482e+31 -6.962936418905874724e+30 3.281685507310205847e+31 9.241630178064907840e+31 1.730544785932614751e+32 2.619210949875333106e+32 2.984440142196566918e+32 8.964375812060072923e+31 -8.515727465135046667e+32 -3.425309034394939997e+33 -8.145884847188906515e+33 -9.922370830834364410e+33 -2.119464668318252366e+28 -1.689726703118075140e+27 1.440101653069986610e+26 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 6.186324149659251562e+13 8.113154959294240625e+13 1.053889122977165625e+14 1.359271226298647969e+14 -2.097046363337115528e+28 4.850777756495711585e+30 2.953274256558218597e+31 3.545273642763729060e+31 4.025456872055449111e+31 3.657581460085835446e+31 3.018816679659856350e+31 2.048223110003727437e+31 8.176806147340775115e+30 -5.796250740354887641e+30 -1.798839398031696094e+31 -2.076444435341100150e+31 -5.711669151245612857e+30 2.691583747083509247e+31 7.579958708961477309e+31 1.419395486743453834e+32 2.148287875274468622e+32 2.447859658750551118e+32 7.352862842410293685e+31 -6.984595303325589259e+32 -2.809449882735912952e+33 -6.681296633318354125e+33 -8.138406580426555140e+33 -1.740744048703962454e+28 -1.411749034480591280e+27 8.079362883576220633e+25 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00'

从这个字符串中,我最终想要的是:

new_list = ['1.440101653069986610e+26', '8.079362883576220633e+25']

我在想我会使用正则表达式,但这似乎有点棘手,因为我有一堆 0.000000000000000000e+00 出现分组在一起,我只想要第一个零出现之前的非零数字。我也不能假设总是有相同数量的零组合在一起。

我也想过分割空格并迭代,但我的完整字符串实际上太长了,无法有效地做到这一点。我该怎么做?

【问题讨论】:

  • 你有没有试过向我们展示什么?
  • @Jean-FrançoisFabre 0.000000000000000000e+00 数字开始之前
  • 啊,现在更清楚了。
  • “我也不能假设总是有相同数量的零组合在一起”。我们如何区分 2 个零值和“一组零”?

标签: python regex python-2.7


【解决方案1】:

你可以使用否定的lookbehind断言:

In [55]: re.findall(r'(\S+)(?<!0\.000000000000000000e\+00)\s+0\.000000000000000000e\+00', my_string)
Out[55]: ['1.440101653069986610e+26', '8.079362883576220633e+25']

正如@revo 在评论中提到的那样,可以使用负前瞻断言来改进正则表达式以提高性能:

([-+]?\d\.(?!0+e\+0+)\S+)\s+(?:0\.0+e\+00\s*)+

Live demo

【讨论】:

  • 现在可以了。您甚至可以将其修改为性能更好的东西。我稍微改了一下([-+]?\d\.(?!0+e\+0+)\S+)\s+0\.0+e\+00
【解决方案2】:

我也不能假设总是有相同数量的零组合在一起。

我们如何区分例如 2 个连续的零值和“一组零”?

好吧,鉴于您正在寻找至少 5 个 0.000 模式,您可以在这个多 0 模式上使用非捕获组(以避免匹配它),遵循非空白模式(用于数字)

re.findall("(\S+)\s+(?:0\.0+e\+00\s+){5,}",my_string)

如果除了模式本身不能有任何零,则可以概括为:

re.findall("(\S+)\s+(?:0\.0+e\+00\s+)+",my_string)

(您需要在非捕获组末尾的+ 来捕获并丢弃所有个零)

结果(在这两种情况下):

['1.440101653069986610e+26', '8.079362883576220633e+25']

这也处理换行符,并且可以容忍小数部分中可变数量的零

【讨论】:

    【解决方案3】:

    列表理解和压缩

    这比其他解决方案快 10-70 倍 倍。

    my_values = my_string.split()
    output = [x for x,y in zip(my_values,my_values[1:]) 
               if (y == '0.000000000000000000e+00' and x != '0.000000000000000000e+00')]
    print(output)
    

    或者,按照@Jean-François Fabre 的建议,使用 islice 来节省内存:

    import itertools
    my_values = my_string.split()
    output = [x for x,y in zip(my_values,itertools.islice(myvalues,1,None)) 
                   if (y == '0.000000000000000000e+00' and x != '0.000000000000000000e+00')]
    print(output)
    

    这是通过成对 (x,y) 对元素进行分组来实现的。 x 应该不同于 0.00.. 而 y 应该等于它。通过首先进行 y 检查,这将在大多数情况下快速评估为 False 并继续迭代。 返回:

    ['1.440101653069986610e+26', '8.079362883576220633e+25']
    

    熊猫和numpy

    但是,另一个想法(我认为这是最聪明的)是使用 pandas 和 pd.to_numeric()。当您使用数字时,您很可能希望使用 numpy 或 pandas 之类的库。这会更安全,因为您还可以顺利处理错误。另请注意,我在这两种情况下都将数字转换回字符串(您可以跳过)。

    import pandas as pd
    
    data = pd.Series(pd.to_numeric(my_string.split()))
    output = data[(data != 0) & (data.shift(-1) == 0)].astype(str).tolist()
    print(output)
    
    #['1.440101653069986610e+26', '8.079362883576220633e+25']
    

    还有 numpy:

    import numpy as np
    
    data = np.loadtxt(my_string.split())
    output = list(map(str,data[(data != 0) & (np.roll(ar, -1) == 0)]))
    print(output)
    
    #['1.440101653069986610e+26', '8.079362883576220633e+25']
    

    时间比较

    最快 --> 最慢

    100000 loops, best of 3: 9.28 µs per loop  <-- Anton vBR list comprehension
    10000 loops, best of 3: 98.4 µs per loop   <-- Revos Regex
    1000 loops, best of 3: 256 µs per loop     <-- Anton vBR numpy
    1000 loops, best of 3: 425 µs per loop     <-- Tzot Regex
    1000 loops, best of 3: 513 µs per loop     <-- Jean-François Fabre Regex 
    1000 loops, best of 3: 782 µs per loop     <-- liliscent 
    1000 loops, best of 3: 794 µs per loop     <-- Anton vBR pandas
    

    【讨论】:

    • 您可以通过删除my_values[1:] 以支持itertools.islice(myvalues,1,None) 来更快
    • 我想知道其他人在 x100k 迭代中的执行次数。顺便说一句,干得好。
    • 令人失望 :) 这主要是内存增益。但是,如果您有一个非常大的列表,它会有所不同。
    • @Jean-FrançoisFabre 非常感谢您的提示。会记得的。
    • @AntonvBR 你介意对我的一些修改过的正则表达式做一个基准测试吗:([-+]?\d\.(?!0+e\+0+)\S+)\s+(?:0\.0+e\+00\s*)+
    【解决方案4】:

    如果你想要 float 值而不是它们的字符串表示:

    import re
    
    list(
        filter(
            None,
            map(
                float,
                re.findall(r"\S+(?=\s0\.0+e)", my_string)
    )))
    
    • re.findall(r"\S+(?=\s0\.0+e)", my_string) :
      查找在空白和 0.00000...e 之前出现的所有非空白字符序列
    • map(float, ^ ) :
      假设上述所有匹配项都可以转换为浮点数
    • filter(None, ^ ) :
      过滤掉所有零浮点数
    • list( ^ ) :
      将上述内容变成一个列表(Python 2 中的无操作,Python 3 中将生成器转换为列表)

    结果:

    >>> list(filter(None, map(float, re.findall(r"\S+(?=\s0\.0+e)", my_string))))
    [1.4401016530699866e+26, 8.07936288357622e+25]
    

    但是,如果您仍然想要字符串值本身,请告诉我;在这种情况下,需要修改 mapfilter 子表达式。

    【讨论】:

      猜你喜欢
      • 2018-03-21
      • 2015-06-22
      • 2011-02-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多