Python数据解析中的正则表达式
我对正则表达式还不是很熟悉,但我对它们的强大功能感到惊讶。我有一个项目,想知道正则表达式是否合适,以及如何使用它们。
在这个项目中,我得到一个包含大量数据的文件。这里有一部分内容:
* 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 个回答
在编程中,有时候我们需要让程序做一些事情,比如在特定的条件下执行某段代码。这个过程就像给程序下达命令,让它根据我们设定的规则来行动。
例如,如果你想让程序在用户输入的数字大于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
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) == True
。bool(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]
。
希望这对你有帮助!