根据日期字符串匹配,将两个列表合并为一个Zip列表

4

我有两个文件列表,我正在使用以下方式从FTP文件夹中提取:

sFiles = ftp.nlst(date+'sales.csv')
oFiles = ftp.nlst(date+'orders.csv')

这将会得到两个列表,看起来像这样:
sFiles = ['20170822_sales.csv','20170824_sales.csv','20170825_sales.csv','20170826_sales.csv','20170827_sales.csv','20170828_sales.csv']

oFiles = ['20170822_orders.csv','20170823_orders.csv','20170824_orders.csv','20170825_orders.csv','20170826_orders.csv','20170827_orders.csv']

使用我的真实数据集,类似以下这种情况...

for sales, orders in zip(sorted(sFiles),sorted(oFiles)): 
     df = pd.concat(...)

能够得到我想要的结果,但有时会出现问题,两个文件都没有进入正确的FTP文件夹,所以我想要一些代码来创建一个可迭代对象,在其中可以根据日期提取匹配的订单和销售文件名。

以下代码有效...我不确定它的"pythonic"分数是多少。可读性差,但它是一个推导式,所以我想应该有性能上的优势?

[(sales, orders) for sales in sFiles for orders in oFiles if re.search(r'\d+',sales).group(0) == re.search(r'\d+',orders).group(0)]

你的例子是O(n²),所以对于大数据集不够高效。正则表达式有些过度:如果命名一致,sales[:8]==orders[:8]就可以工作了。 - Mark Tolonen
是的,我明白嵌套循环并不理想。希望在zip函数中有一种方法可以使用lambda函数。而且文件命名规范将始终保持一致。 - Yale Newman
1
有趣的是,以下是你原始数据和1000个日期文件对的一些时间记录:使用re的列表推导式:117微秒(仅原始数据),使用[:8]切片的列表推导式:10.2微秒/249毫秒,我的解决方案:13.5微秒/1.63毫秒,Pandas的解决方案:2.41毫秒/50.2毫秒。所以,在小数据的情况下,使用[:8]切片的列表推导式是最快的,但随着数据规模的增大,性能变得很差。Pandas实际上是最差的,但是在处理大数据时只比最快的方法慢20倍,而我的Python解决方案在处理大数据时慢了120倍,因此在非常大的数据集上,Pandas可能会更快。故事的寓意是,要进行测试! - Mark Tolonen
4个回答

3
利用 Pandas DataFrame 的索引:
import pandas as pd
sFiles = ['20170822_sales.csv','20170824_sales.csv','20170825_sales.csv','20170826_sales.csv','20170827_sales.csv','20170828_sales.csv']
oFiles = ['20170822_orders.csv','20170823_orders.csv','20170824_orders.csv','20170825_orders.csv','20170826_orders.csv','20170827_orders.csv']

s_dates = [pd.Timestamp.strptime(file[:8], '%Y%m%d') for file in sFiles]
s_df = pd.DataFrame({'sFiles': sFiles}, index=s_dates)

o_dates = [pd.Timestamp.strptime(file[:8], '%Y%m%d') for file in oFiles]
o_df = pd.DataFrame({'oFiles': oFiles}, index=o_dates)

df = s_df.join(o_df, how='outer')

所以:
>>> print(df)
                        sFiles               oFiles
2017-08-22  20170822_sales.csv  20170822_orders.csv
2017-08-23                 NaN  20170823_orders.csv
2017-08-24  20170824_sales.csv  20170824_orders.csv
2017-08-25  20170825_sales.csv  20170825_orders.csv
2017-08-26  20170826_sales.csv  20170826_orders.csv
2017-08-27  20170827_sales.csv  20170827_orders.csv
2017-08-28  20170828_sales.csv                  NaN

“20170823_orders.csv” 文件丢失。 - Mark Tolonen
1
所以现在已经修复了! - Hazzles
@Hazzles 我想象中这个解决方案可能是最快的,因为pandas利用了C或其他通常比Python更快的东西。 - Yale Newman
对于大量项目,肯定比嵌套的for循环方法更快。我猜测大部分加速是因为我们将问题转换为类似集合的操作,即取两个索引的并集,而C底层的加速是二阶效应。 - Hazzles
@Hazzles有没有关于Big O/如何思考使程序更高效的实用读物? - Yale Newman
很抱歉,我不熟悉任何好的资源。就我个人而言,尝试向量化而不是使用循环(例如,np.arange(1e6) * 2[i * 2 for i in range(1e6)]快得多);在实际应用中,不要花太多时间过早优化,因为程序的减速可能并不明显。cProfile非常有用 https://docs.python.org/3.6/library/profile.html - Hazzles

2
您可以使用字典:
import collections
d = collections.defaultdict(dict)

sFiles = ftp.nlst(date+'sales.csv')
oFiles = ftp.nlst(date+'orders.csv')
for sale, order in zip(sFiles, oFiles):
    a, b = sale.split("_")
    a1, b2 = order.split("_")
    d[a]["sales"] = sale
    d[a1]["orders"] = order
print(dict(d))

现在,您的数据以以下格式结构化:{"date":{"销售":"销售文件名", "订单":"订单文件名"}}

输出结果:

{'20170828': {'sales': '20170828_sales.csv'}, '20170822': {'sales': '20170822_sales.csv', 'orders': '20170822_orders.csv'}, '20170823': {'orders': '20170823_orders.csv'}, '20170824': {'sales': '20170824_sales.csv', 'orders': '20170824_orders.csv'}, '20170825': {'sales': '20170825_sales.csv', 'orders': '20170825_orders.csv'}, '20170826': {'sales': '20170826_sales.csv', 'orders': '20170826_orders.csv'}, '20170827': {'sales': '20170827_sales.csv', 'orders': '20170827_orders.csv'}}

编辑:

通过字典推导和基于您提出的列表推导解决方案,您可以尝试以下方法:

import re
final_data = [{"sold":sold, "order":order} for sold in sFiles for order in oFiles if re.findall("\d+", sold)[0] == re.findall("\d+", order)[0]]

输出:

[{'sold': '20170822_sales.csv', 'order': '20170822_orders.csv'}, {'sold': '20170824_sales.csv', 'order': '20170824_orders.csv'}, {'sold': '20170825_sales.csv', 'order': '20170825_orders.csv'}, {'sold': '20170826_sales.csv', 'order': '20170826_orders.csv'}, {'sold': '20170827_sales.csv', 'order': '20170827_orders.csv'}]

这里的collections是做什么的?是否可以使用字典推导来实现这个?请在我编辑后的问题中检查我的期望解决方案。 - Yale Newman
@YaleNewman 请查看我最近的编辑,让我知道我的解决方案是否符合您的想法。 - Ajax1234
是的,这就是我最初想到的。然而,我相当有信心利用Pandas索引可以得出最有效的解决方案。我可能错了。 - Yale Newman
@Ajax,实际上,只有当数据变得非常大时,才需要使用pandas。我在问题下面的评论中放了一些时间记录。 - Mark Tolonen
@MarkTolonen 有趣的时间数据和观察。谢谢您的发布! - Ajax1234
显示剩余2条评论

1

仅仅因为理解存在并不意味着你应该在所有情况下都使用它们。这样做是可以的:

date = re.compile(r'\d+')
for sales in sFiles:
    salesDate = date.search(sales).group(0)
    for orders in oFiles:
        orderDate = date.search(orders).group(0)
        if salesDate == orderDate:
            print sales, orders

你是否可以让它更快?是的。但是你不需要仅仅因为可以而强制使用列表推导式。有时写 更多 的代码更好,因为它会将复杂性分散一些。

这里有一个渐进式的改进,使算法变成O(n):

date = re.compile(r'\d+')
orders_dict = dict((date.search(file).group(0), file) for file in oFiles)

for sales in sFiles:
    salesDate = date.search(sales).group(0)
    if salesDate in orders_dict:
        orders = orders_dict[salesDate]
        print sales, orders
    else:
        # what do you do if it doesn't exist? You can't put handling code
        # here if you try to write this as a comprehension.

1
这将创建一个生成器,按日期顺序返回匹配的一对。
from collections import defaultdict

def match(sales,orders):
    # When a key is referenced for the first time, the value
    # will default to the result of the lambda.
    d = collections.defaultdict(lambda:[None,None])

    # set sales files on the first entry in the value.
    for sale in sFiles:
        d[sale[:8]][0] = sale
    # set orders files on the second entry.
    for order in oFiles:
        d[order[:8]][1] = order

    for k in sorted(d):
        # Both values need to exist.
        # If you want the singles remove the if.
        if all(v for v in d[k]):
            yield d[k]

sFiles = ['20170822_sales.csv','20170824_sales.csv','20170825_sales.csv','20170826_sales.csv','20170827_sales.csv','20170828_sales.csv']
oFiles = ['20170822_orders.csv','20170823_orders.csv','20170824_orders.csv','20170825_orders.csv','20170826_orders.csv','20170827_orders.csv']

for s,o in match(sFiles,oFiles):
    print(s,o)

输出:

20170822_sales.csv 20170822_orders.csv
20170824_sales.csv 20170824_orders.csv
20170825_sales.csv 20170825_orders.csv
20170826_sales.csv 20170826_orders.csv
20170827_sales.csv 20170827_orders.csv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接