【问题标题】:Is there a better way of splitting this JSON string other than regex?除了正则表达式之外,还有更好的方法来拆分这个 JSON 字符串吗?
【发布时间】:2016-02-17 17:57:37
【问题描述】:

我正在研究车辆路线问题,到目前为止,最耗时的部分是使用真实的道路网络生成距离/时间矩阵。对于这个项目,我使用 localhost graphhopper 服务器,并且我曾经使用它来获取位置之间的成对距离和时间;一切都很容易解析。它返回个人驾驶指令以及总行程距离和时间。但是,对于 2000 个地点,这将是近 400 万次呼叫。

我有一种预感,想知道如果我要求它改为规划一条一次经过 100 个地点的路线,会不会更快。然后的目标是将 json 响应分解为 100 个块,就好像我进行了 100 次成对调用一样。事实证明,100 个位置的速度快了近 3 倍。除了试图跟踪我的矩阵的哪些部分是构建的有点头痛之外,响应没有为每个子路线提供明显的结构差异,而只是给出了您处于“中途停留”的指示,例如

{
    "hints": {
        "visited_nodes.average": "243.0",
        "visited_nodes.sum": "3402"
    },
    "paths": [{
        "instructions": [{
            "distance": 315.637,
            "sign": 0,
            "interval": [0, 7],
            "text": "Continue",
            "time": 29848
        }, {
            "exit_number": 3,
            "distance": 1460.234,
            "sign": 6,
            "turn_angle": -0.01,
            "interval": [7, 27],
            "text": "At roundabout, take exit 3",
            "time": 96167
        }, {
            "distance": 1258.763,
            "sign": 0,
            "interval": [27, 30],
            "text": "Continue onto Sheikh Zayed bin Sultan Street",
            "time": 50350
        }, {
            "distance": 116.7,
            "sign": 0,
            "interval": [30, 31],
            "text": "Continue",
            "time": 4668
        }, {
            "distance": 3543.556,
            "sign": 0,
            "interval": [31, 57],
            "text": "Continue onto Sheikh Zayed bin Sultan Street",
            "time": 144812
        }, {
            "distance": 0,
            "sign": 5,
            "interval": [87, 88],
            "text": "Stopover 1",
            "time": 0
        },

我目前的工作原型是不断总结每条指令的各个距离(米),但在重新开始记录距离之前使用正则表达式查找“​​中途停留”。

distance_matrix = []
sub_distance = []
for unit in response['paths'][0]['instructions']:
    if bool(re.search('Stopover', unit['text'])) == False:
        sub_distance.append(float(unit['distance'])/1000)
    else:
        distance_matrix.append(sum(sub_distance))
        sub_distance = []

我的背景根本不是编程,而且我在 Python 中自学了不到一年。虽然这可行,但我想知道我是否错过了一种更自然(更快)的方式来做到这一点,但我无法通过谷歌找到任何相关的东西,因为我不太确定我在寻找什么。在我们目前的规模下,距离/时间矩阵可能已经需要大约 8 分钟的时间来计算,所以我愿意接受任何缩短时间的建议。一条路线中的点数似乎是有限制的,所以这个函数仍然会被调用数千次,所以它会加起来。提前致谢。

【问题讨论】:

    标签: python json


    【解决方案1】:

    您存储子距离列表是否有原因?如果您所做的只是计算总和,那么在我看来,这样做会更快:

    distance_matrix = []
    sub_distance = 0.0
    for unit in response['paths'][0]['instructions']:
        if not unit['text'].startswith('Stopover'):
            sub_distance += unit['distance']
        else:
            distance_matrix.append(sub_distance / 1000.0)
            sub_distance = 0.0
    

    一个小的性能测试:

    import re
    import timeit
    import random
    
    # Creates a list of 1000000 items with random distances from [0, 1). On average every 10th element will be a stopover.
    data = [{"distance": random.random(), "text": "Stopover" if random.randint(0, 10) == 0 else "ASDF"} for i in range(1000000)]
    
    def test1():
      distance_matrix = []
      sub_distance = 0.0
      for unit in data:
        if not unit['text'].startswith('Stopover'):
          sub_distance += unit['distance']
        else:
          distance_matrix.append(sub_distance / 1000.0)
          sub_distance = 0.0
    
      return distance_matrix
    
    def test2():
      distance_matrix = []
      sub_distance = []
      for unit in data:
        if bool(re.search('Stopover', unit['text'])) == False:
          sub_distance.append(float(unit['distance'])/1000)
        else:
          distance_matrix.append(sum(sub_distance))
          sub_distance = []
    
      return distance_matrix
    
    print "Test 1: %.2f s for 10 runs" % timeit.timeit(test1, number=10)
    print "Test 2: %.2f s for 10 runs" % timeit.timeit(test2, number=10)
    

    在我的机器上产生以下结果:

    $ python test.py
    Test 1: 3.50 s for 10 runs
    Test 2: 16.67 s for 10 runs
    

    顺便提一下,预编译的正则表达式几乎与使用 startswith() 一样快,这在其他时候可能会派上用场。

    在循环之外预编译 像这样:

    stop = re.compile("Stopover")
    

    并像这样在循环中使用:

    if not stop.search(unit['text']):
    

    【讨论】:

    • 这是一个很好的观察,谢谢。事实上,我想我添加了sub_distance 以专注于我将要拆分列表的方式。在我测试之前,我想知道您是否知道这会出于某种原因提高性能?
    • 相对于更新值而言,创建列表的成本很高,并且您将为每个中途停留创建一个新列表。此外,求和时对列表的额外迭代并非没有成本。最后,我将除法移到 else 语句中,以避免在每次迭代中进行不是中途停留的除法(除法不像创建列表那样昂贵,但比简单的加法要昂贵得多)
    • 非常好的反馈,非常感谢。我已对回答和评论表示赞成,但我觉得 Daniel 的回答回答了线程的标题,所以我应该接受这个以关闭问题,因为它涉及破坏 json 字符串。
    • 当然!我添加了一个性能测试,显示了累加方法相对于列表追加和求和的性能改进。
    • 没问题 :) 我很好奇在你的数据上运行时对性能的影响是什么,所以如果你选择走这条路并且你有时间,请在合并后分享更新的数字我的改变
    【解决方案2】:

    在这里使用正则表达式没有多大意义,因为您没有使用它的任何功能。在这里使用startswith 要简单得多,而且可能会更快。

    if unit['text'].startswith('Stopover'):
    

    注意,您不应该明确比较布尔值与 False,只需使用 if <condition>:

    【讨论】:

    • 非常感谢您的反馈。我只是在每个循环中测试了 5 次(以提供响应反馈)。我设置的平均时间是 97600 毫秒,你的设置是 92800 毫秒。我会做一些进一步的测试,并在接受之前将其保留一段时间以防万一:)
    【解决方案3】:

    我同意丹尼尔的观点,你不需要正则表达式来做你想做的事。

    如果您的指令以您正在搜索的命令开头(例如 Stopover),请使用 .startswith 方法。

    如果您要查看的说明不在字符串的开头,您可以使用unit['text'].find('stopover'),它的作用类似于正则表达式,或者进一步简化搜索使用:

    distance_matrix = []
    sub_distance = []
    for unit in response['paths'][0]['instructions']:
        if 'Stopover' in unit['text']:
            distance_matrix.append(sum(sub_distance))
            sub_distance = []
        else:
            sub_distance.append(float(unit['distance'])/1000)
    

    不过,字符串搜索不会快多少,在这部分过程中您不会看到任何非常重要的加速。

    编辑: 刚刚找到this 问题,它解释了考虑字符串搜索的一些性能问题。看看吧。

    【讨论】:

    • 感谢您的详细说明。我不确定发布的答案的相关性,因为我希望以完整的方式多次拆分 json 字符串,所以我已经知道它包含我想要拆分的内容......但是在 @ 上拆分987654325@ 会给我留下无效的 json 吗?
    • 一点也不,我只是提出一种在 json 结构中搜索指令的替代方法。我将更新我的代码以使其更清晰。 json 根本没有被操纵。我提供的问题仅包含有关在字符串搜索中获得一些性能的更多信息。
    • 抱歉,我误解了链接到另一个答案的预期应用。那里的基准非常有趣。赞成。我以为re 会更慢,但不会到那种程度!
    猜你喜欢
    • 1970-01-01
    • 2011-08-28
    • 2020-10-21
    • 2023-03-27
    • 2023-03-05
    • 2013-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多