如何从字符串中选取数字,直到出现第一个非数字字符



我有一个字符串集合,如下所示:

"0"
"90/100"
None
"1-5%/34B-1"
"-13/7"

我想将它们转换为整数(或None),以便我从头开始选择数字并在第一个非数字字符处停止。因此,上述数据将变为:

0
90
None
1
None

我尝试做类似下面的代码,但遇到了多个问题,例如当int(new_n)行只是空字符串时new_n导致ValueError。即使没有它,代码看起来也很糟糕:

def pick_right_numbers(old_n):
    new_n = ''
    numbers = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
    if old_n is None:
        return None
    else:
        for n in old_n:
            if n in numbers:
                new_n += n
            else:
                return int(new_n)
        if new_n:
            return int(new_n)
        else:
            return None

有人可以用这个把我推向正确的方向吗?

这是你要找的那种东西吗?

import re
data = ['0', '90/100', None, '1-5%/34B-1', '-13/7']
def pick_right_numbers(old_n):
    if old_n is None:
        return None
    else:
        digits = re.match("([0-9]*)",old_n).groups()[0]
        if digits.isdigit():
            return int(digits)
        else:
            return None
for string in data:
    result = pick_right_numbers(string)
    if result is not None:
        print("Matched section is : {0:d}".format(result))

它使用re(模式匹配)来检测字符串开头的数字块(匹配仅匹配字符串的开头,搜索将在字符串中的任何位置找到块)。它检查匹配项,确认匹配项是数字(否则最后一个数据元素匹配,但为空字符串),并将其转换为整数以返回。

执行此操作

的基本方法是:

input_list = ["0", "90/100", None,  "1-5%/34B-1", "-13/7"]
char_list = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
output_list = []
for input_str in input_list:
    if isinstance(input_str, str):
        i = 0
        for input_char in input_str:
            if input_char in char_list:
                i += 1
            else:
                break
    else:
        i = 0
    if i:
        output = int(input_str[0:i])
    else:
        output = None
    output_list.append(output)

但是有很多变体。 如果这是一个您每天重复 10.000+ 次的功能,那么考虑一些性能分析将是明智的。

编辑:考虑Python 2与3中的字符串是什么可能是明智的(请参阅isinstance('aaa',basestring)和isinstance('aaa',str)之间的区别是什么?

edit2:了解 Bakuriu 的解决方案如何简化这一点 ->

from itertools import takewhile
input_list = ["0", "90/100", None,  "1-5%/34B-1", "-13/7"]
output_list = []
for input_str in input_list:
    text = ''.join(takewhile(str.isdigit, input_str or ""))        
    output_list.append(int(text) if text else None)

(所以我认为他应该补充说,这是诚实的最佳答案;)

>>> import re
>>> s = ["0", "90/100", None, "1-5%/34B-1", "-13/7"]
>>> [int(c) if c else None for c in (re.sub('([0-9]*).*', r'1', str(x)) for x in s)]
[0, 90, None, 1, None]

工作原理

我们有两个列表推导。 内部从列表s的元素中删除除初始数字之外的所有内容:

>>> list(re.sub('([0-9]*).*', r'1', str(x)) for x in s)
['0', '90', '', '1', '']

外部列表推导会将这些字符串(如果为非空)转换为整数或以其他方式转换为None

>>> [int(c) if c else None for c in ('0', '90', '', '1', '')]
[0, 90, None, 1, None]

替代方法:使用takewhile

根据 Bakuriu 的评论,我们可以使用intertools.takewhile代替re.sub

>>> from itertools import takewhile
>>> [int(c) if len(c) else None for c in (''.join(takewhile(str.isdigit, x or "")) for x in s)]
[0, 90, None, 1, None]

对原始代码的修改

或者,我们可以修改原始代码:

def pick_right_numbers(old_n):
    if old_n is None:
        return None
    else:
        new_n = ''
        for n in old_n:
            if not n.isdigit():
                break
            new_n += n 
        return int(new_n) if len(new_n) else None

此代码生成输出:

>>> [pick_right_numbers(x) for x in s]
[0, 90, None, 1, None]

有多种方法可以检查对象是否为数字。例如,请参阅此答案。

但是,您一次只需要检查一个字符,因此您的方法实际上很好。阵列将永久位于缓存中,因此将快速扫描。

请注意,你可以用更好的方式写它:

if n in "0123456789":

另一种可能性,可能是最快的,是检查范围,通过ASCII表示将它们视为数值(使用数字在该表示中是连续的,并且按照您期望的顺序):

zero = ord('0')
nine = ord('9')
for n in old_n:
   nn = ord(n) 
   if (nn >= zero) and (nn <= nine):

当然,最优雅的方式是调用本地isdigit();您可以节省所有混乱并完全明确您的意图。请注意,它可能比您要求的要多 - 根据 Unicode 是一个数字。但是您不太可能遇到这种情况。另请注意,因此,它可能比您的实现

请注意,您还需要在循环内检查new_n == ''!不重复自己的最好方法是退出循环进入决赛,如果

def pick_right_numbers(old_n):
    new_n = ''
    if old_n is None:
        return None
    else:
        for n in old_n:
            if n.isdigit():
                new_n += n
            else:
                break
        if new_n:
            return int(new_n)
        else:
            return None

当然,如果你需要速度,你可能不得不改变方法,因为你正在循环中增长一个向量。但是,如果这是对您有意义的逻辑,那么只有在这是程序的瓶颈时才使其复杂化。

最新更新