【问题标题】:Separate number from unit in a string in Python在Python中的字符串中将数字与单位分开
【发布时间】:2010-02-10 20:59:55
【问题描述】:

我有包含数字及其单位的字符串,例如2GB、17 英尺等 我想将数字与单元分开并创建 2 个不同的字符串。有时,它们之间有一个空格(例如 2 GB),使用 split(' ') 很容易做到这一点。

当它们在一起时(例如 2GB),我会测试每个字符,直到找到一个字母,而不是一个数字。

s='17GB'
number=''
unit=''
for c in s:
    if c.isdigit():
        number+=c
    else:
        unit+=c

有没有更好的方法?

谢谢

【问题讨论】:

  • 您可能会发现您的方法比正则表达式方法更快,尤其是对于您正在使用的短字符串。

标签: python string units-of-measurement


【解决方案1】:

找到第一个非数字字符就可以跳出循环

for i,c in enumerate(s):
    if not c.isdigit():
        break
number = s[:i]
unit = s[i:].lstrip()

如果你有负数和小数:

numeric = '0123456789-.'
for i,c in enumerate(s):
    if c not in numeric:
        break
number = s[:i]
unit = s[i:].lstrip()

【讨论】:

  • 是的,它适用于正整数。 '-'.isdigit() 也是 False。
  • 缺少单元时代码不会给出错误结果。 '10' -> 数字='1',单位='0'。要解决这个问题,修改为for i,c in enumerate(s+' '):
【解决方案2】:

您可以使用正则表达式将字符串分组:

>>> import re
>>> p = re.compile('(\d+)\s*(\w+)')
>>> p.match('2GB').groups()
('2', 'GB')
>>> p.match('17 ft').groups()
('17', 'ft')

【讨论】:

  • 要匹配更通用的数字集,包括“6.2”和“3.4e-27”,需要更复杂的正则表达式。太糟糕了 python 没有内置的 scanf 模拟。
【解决方案3】:

tokenize 可以帮忙:

>>> import StringIO
>>> s = StringIO.StringIO('27GB')
>>> for token in tokenize.generate_tokens(s.readline):
...   print token
... 
(2, '27', (1, 0), (1, 2), '27GB')
(1, 'GB', (1, 2), (1, 4), '27GB')
(0, '', (2, 0), (2, 0), '')

【讨论】:

    【解决方案4】:
    s='17GB'
    for i,c in enumerate(s):
        if not c.isdigit():
            break
    number=int(s[:i])
    unit=s[i:]
    

    【讨论】:

    • -1 s='17GB' 给出 unit='GB',即单位前面有一个空格。 unit 需要一个 lstrip,然后你就会得到和我一样的答案。
    • 现在我重新阅读了这个问题,空格的情况是用 split() 处理的,而不是这个代码。我试图把 -1 拿回来,但它不让我。
    【解决方案5】:

    你应该使用正则表达式,将你想要找出的内容组合在一起:

    import re
    s = "17GB"
    match = re.match(r"^([1-9][0-9]*)\s*(GB|MB|KB|B)$", s)
    if match:
      print "Number: %d, unit: %s" % (int(match.group(1)), match.group(2))
    

    根据您要解析的内容更改正则表达式。如果您不熟悉正则表达式,here's 是一个很棒的教程网站。

    【讨论】:

      【解决方案6】:
      >>> s="17GB"
      >>> ind=map(str.isalpha,s).index(True)
      >>> num,suffix=s[:ind],s[ind:]
      >>> print num+":"+suffix
      17:GB
      

      【讨论】:

        【解决方案7】:

        这使用了一种比正则表达式更宽容的方法。注意:这不如发布的其他解决方案性能好。

        def split_units(value):
            """
            >>> split_units("2GB")
            (2.0, 'GB')
            >>> split_units("17 ft")
            (17.0, 'ft')
            >>> split_units("   3.4e-27 frobnitzem ")
            (3.4e-27, 'frobnitzem')
            >>> split_units("9001")
            (9001.0, '')
            >>> split_units("spam sandwhiches")
            (0, 'spam sandwhiches')
            >>> split_units("")
            (0, '')
            """
            units = ""
            number = 0
            while value:
                try:
                    number = float(value)
                    break
                except ValueError:
                    units = value[-1:] + units
                    value = value[:-1]
            return number, units.strip()
        

        【讨论】:

          【解决方案8】:

          这种解析器已经集成到Pint

          Pint 是一个 Python 包,用于定义、操作和操作物理 数量:一个数值和一个单位的乘积 测量。它允许它们之间的算术运算和 不同单位之间的转换。

          您可以使用pip install pint 安装它。

          然后,你可以解析一个字符串,得到想要的值('magnitude')和它的单位:

          >>> from pint import UnitRegistry
          >>> ureg = UnitRegistry()
          >>> size = ureg('2GB')
          >>> size.m
          2
          >>> size.u
          <Unit('gigabyte')>
          >>> size.to('GiB')
          <Quantity(1.86264515, 'gibibyte')>
          >>> length = ureg('17ft')
          >>> length.m
          17
          >>> length.u
          <Unit('foot')>
          >>> length.to('cm')
          <Quantity(518.16, 'centimeter')>
          

          【讨论】:

            【解决方案9】:

            【讨论】:

              【解决方案10】:

              对于这个任务,我肯定会使用正则表达式:

              import re
              there = re.compile(r'\s*(\d+)\s*(\S+)')
              thematch = there.match(s)
              if thematch:
                number, unit = thematch.groups()
              else:
                raise ValueError('String %r not in the expected format' % s)
              

              在 RE 模式中,\s 表示“空白”,\d 表示“数字”,\S 表示非空白; * 表示“0 个或多个前面”,+ 表示“1 个或多个前面”,括号括起来“捕获组”,然后由匹配对象上的 groups() 调用返回。( thematch 是 None 如果给定的字符串不对应于模式:可选的空白,然后是一个或多个数字,然后是可选的空白,然后是一个或多个非空白字符)。

              【讨论】:

                【解决方案11】:

                一个正则表达式。

                import re
                
                m = re.match(r'\s*(?P<n>[-+]?[.0-9])\s*(?P<u>.*)', s)
                if m is None:
                  raise ValueError("not a number with units")
                number = m.group("n")
                unit = m.group("u")
                

                这将为您提供一个带有可选符号的数字(整数或定点;很难区分科学记数法的“e”与单位前缀),然后是单位,以及可选的空格。

                如果您要进行大量比赛,可以使用re.compile()

                【讨论】:

                • 你需要 re.match(r'\s*(?P[-+]?[.0-9]*)\s*(?P.*) ', s)。它在 [.0-9] 之后缺少 *
                【解决方案12】:

                科学记数法 这个正则表达式非常适合我解析可能是科学记数法的数字,并且基于最近关于 scanf 的 python 文档: https://docs.python.org/3/library/re.html#simulating-scanf

                units_pattern = re.compile("([-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?|\s*[a-zA-Z]+\s*$)")
                number_with_units = list(match.group(0) for match in units_pattern.finditer("+2.0e-1 mm"))
                print(number_with_units)
                >>>['+2.0e-1', ' mm']
                
                n, u = number_with_units
                print(float(n), u.strip())
                >>>0.2 mm
                

                【讨论】:

                  【解决方案13】:

                  试试下面的正则表达式模式。第一组(任何方式的数字的 scanf() 标记)直接从 re 模块的 python 文档中提取。

                  import re
                  SCANF_MEASUREMENT = re.compile(
                      r'''(                      # group match like scanf() token %e, %E, %f, %g
                      [-+]?                      # +/- or nothing for positive
                      (\d+(\.\d*)?|\.\d+)        # match numbers: 1, 1., 1.1, .1
                      ([eE][-+]?\d+)?            # scientific notation: e(+/-)2 (*10^2)
                      )
                      (\s*)                      # separator: white space or nothing
                      (                          # unit of measure: like GB. also works for no units
                      \S*)''',    re.VERBOSE)
                  '''
                  :var SCANF_MEASUREMENT:
                      regular expression object that will match a measurement
                  
                      **measurement** is the value of a quantity of something. most complicated example::
                  
                          -666.6e-100 units
                  '''
                  
                  def parse_measurement(value_sep_units):
                      measurement = re.match(SCANF_MEASUREMENT, value_sep_units)
                      try:
                          value = float(measurement[0])
                      except ValueError:
                          print 'doesn't start with a number', value_sep_units
                      units = measurement[5]
                  
                      return value, units
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2015-04-21
                    • 1970-01-01
                    • 1970-01-01
                    • 2017-04-26
                    • 1970-01-01
                    • 2018-08-24
                    • 2019-10-03
                    相关资源
                    最近更新 更多