Python数据解析中的正则表达式

1 投票
2 回答
517 浏览
提问于 2025-04-17 22:45

我对正则表达式还不是很熟悉,但我对它们的强大功能感到惊讶。我有一个项目,想知道正则表达式是否合适,以及如何使用它们。

在这个项目中,我得到一个包含大量数据的文件。这里有一部分内容:

* File "miles.dat" from the Stanford GraphBase (C) 1993 Stanford University
* Revised mileage data for highways in the United States and Canada, 1949
* This file may be freely copied but please do not change it in any way!
* (Checksum parameters 696,295999341)

Youngstown, OH[4110,8065]115436
Yankton, SD[4288,9739]12011
966
Yakima, WA[4660,12051]49826
1513 2410

文件里有城市名称和州名,然后在括号里是纬度和经度,接着是人口。下一行是该城市到之前列出的每个城市的距离。数据总共有180个城市。

我的任务是创建四个列表。一个是城市列表,一个是坐标列表,一个是人口列表,还有一个是城市之间的距离列表。我知道不使用正则表达式也可以做到这一点(我已经写过),但代码有点笨重,效率也不是最高的。你觉得我该怎么做比较好呢?

2 个回答

0

在编程中,有时候我们需要让程序做一些事情,比如在特定的条件下执行某段代码。这个过程就像给程序下达命令,让它根据我们设定的规则来行动。

例如,如果你想让程序在用户输入的数字大于10时显示一条消息,你就需要用到条件语句。条件语句就像是一个判断器,它会检查你设定的条件是否成立,然后决定接下来要做什么。

在这个例子中,程序会先查看用户输入的数字,如果这个数字确实大于10,那么它就会显示你想要的消息。如果不是,程序就会跳过这条消息,继续执行其他的代码。

这样一来,程序就能根据不同的情况做出不同的反应,变得更加智能和灵活。

S = """Youngstown, OH[4110,8065]115436
    Yankton, SD[4288,9739]12011
    966
    Yakima, WA[4660,12051]49826
    1513 2410"""

import re

def get_4_list():
    city_list = []
    coordinate_list = []
    population_list = []
    distance_list = []

    line_list = S.split('\n')
    line_pattern = re.compile(r'(\w+).+(\[[\d,]+\])(\d+)')
    for each_line in line_list:
        match_list = line_pattern.findall(each_line)
        if match_list:
            print match_list
            city_list.append(match_list[0][0])
            coordinate_list.append(match_list[0][1])
            population_list.append(match_list[0][2])
        else:
            distance_list.extend(each_line.split())

    return city_list, coordinate_list, population_list, distance_list
2
CITY_TYPES = (str, float, float, int)

我建议使用正则表达式来处理城市名称的行,而用列表推导式来处理距离(因为用正则表达式会显得过于复杂,而且速度也慢)。

可以像这样:

import re

CITY_REG = re.compile(r"([^[]+)\[([0-9.]+),([0-9.]+)\](\d+)")
CITY_TYPES = (str, float, float, int)

def get_city(line):
    match = CITY_REG.match(line)
    if match:
        return [type(dat) for dat,type in zip(match.groups(), CITY_TYPES)]
    else:
        raise ValueError("Failed to parse {} as a city".format(line))

def get_distances(line):
    return [int(i) for i in line.split()]

然后:

>>> get_city("Youngstown, OH[4110.83,8065.14]115436")
['Youngstown, OH', 4110.83, 8065.14, 115436]

>>> get_distances("1513 2410")
[1513, 2410]

你可以这样使用它:

# This code assumes Python 3.x
from itertools import count, zip_longest

def file_data_lines(fname, comment_start="* "):
    """
    Return lines of data from file
     (strip out blank lines and comment lines)
    """
    with open(fname) as inf:
        for line in inf:
            line = line.rstrip()
            if line and not line.startswith(comment_start):
                yield line

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return zip_longest(fillvalue=fillvalue, *args)

def city_data(fname):
    data = file_data_lines(fname)

    # city 0 has no distances line
    city_line = next(data)
    city, lat, lon, pop = get_city(city_line)
    yield city, (lat, lon), pop, []

    # all remaining cities
    for city_line, dist_line in grouper(data, 2, ''):
        city, lat, lon, pop = get_city(city_line)
        dists = get_distances(dist_line)
        yield city, (lat, lon), pop, dists

最后:

def main():
    # load per-city data
    city_info = list(city_data("miles.dat"))
    # transpose into separate lists
    cities, coords, pops, dists = list(zip(*city_info))

if __name__=="__main__":
    main()

编辑:

它是如何工作的:

CITY_REG = re.compile(r"([^[]+)\[([0-9.]+),([0-9.]+)\](\d+)")

[^[]表示匹配除了[以外的任何字符;所以([^[]+)会匹配一个或多个字符,直到(但不包括)第一个[为止;这会得到“城市名称, 州”,并将其作为第一个组返回。

\[表示匹配一个字面上的[字符;我们需要用斜杠来转义它,以表明我们不是在开始另一个字符组。

[0-9.]表示匹配数字0到9,或者一个小数点。所以([0-9.]+)会匹配一个或多个数字或小数点,也就是任何整数或浮点数,但不包括尾数;这有点宽松,它会接受像0.1.2.3这样的输入,这不是一个有效的浮点数,但如果只匹配有效的浮点数,表达式会复杂得多,而在这里假设我们不会遇到异常输入,这样就足够了。

我们得到逗号,再匹配一个数字作为第三组,接着得到闭合的方括号;然后\d匹配任何数字(和[0-9]是一样的),所以(\d+)会匹配一个或多个数字,也就是一个整数,并将其作为第四组返回。

match = CITY_REG.match(line)

我们将正则表达式应用于输入的一行;如果匹配成功,我们会得到一个Match对象,里面包含匹配的数据,否则我们会得到None

if match:

... 这是一种简短的说法,表示if bool(match) == Truebool(MyClass)总是True(除非特别重写,比如空列表或字典),bool(None)总是False,所以实际上是“如果正则表达式成功匹配了字符串:”。

正则表达式只返回字符串;你想要不同的数据类型,所以我们需要转换,这就是

[type(dat) for dat,type in zip(match.groups(), CITY_TYPES)]

所做的;match.groups()是四个匹配到的数据,而CITY_TYPES是每个数据的期望类型,所以zip(data, types)返回类似于[("Youngstown, OH", str), ("4110.83", float), ("8065.14", float), ("115436", int)]的内容。然后我们将数据类型应用到每一部分,最终得到["Youngstown, OH", 4110.83, 8065.14, 115436]

希望这对你有帮助!

撰写回答