【问题标题】:Maximum flights out of a single city for a day一天最多出境单个城市的航班
【发布时间】:2023-01-17 08:10:59
【问题描述】:

我是一名航空极客,并试图计算出我可以从一个城市起飞的最大航班数量。我能够获得一个数据框,其中包含到达或离开城市的所有航班时刻表,并试图找出一种运行代码的有效方法。算法细节如下:

  1. Dataframe包含航班起飞时间、到达时间、出发城市、到达城市列
    • 出发时间:DPTR_TIME
    • 到达时间:ARRV_TIME
    • 出发城市:ORIG
    • 到达城市:DEST
    1. 我目前所在的城市被识别为HUB。所有离开枢纽的航班将乘坐下一班航班返回枢纽。 (例如,如果我的枢纽是纽约,我正从纽约飞往匹兹堡。我的下一班航班将是匹兹堡飞往纽约)
    2. 您可以在枢纽外开始新的一天,即飞入枢纽然后飞出
    3. 下一个航班的起飞时间必须大于上一个航班的到达时间
    4. 数据框已按航班起飞时间排序

    这是我解决问题的第一种方法:

    def iter_func(df,sch,conex):
        flt = df.iloc[0]
        df = sch[(sch['ORIG']==flt.DEST) & (sch['DPTR_TIME']>flt.ARRV_TIME+timedelta(hours=conex))]
        if df.shape[0]==0:
            return 1
        else:
            return 1 + iter_func(df,test,conex)
    

    该函数读取城市的初始航班时刻表并选择第一班航班。然后它将创建另一个数据框,该数据框从我飞往的城市开始,并确保我能够连接到下一个航班。如您所见,此代码仅输出原始计划中第一个航班的可能航班。

    我的目标是返回一个航班列表,其中包含一天内最有可能离开城市的航班。

【问题讨论】:

  • 添加一个minimal reproducible example怎么样,即小的输入数据的例子和相应的预期结果? IIUC,解决这个问题的方法很简单,贪心算法就可以了,但与你提出的不同。

标签: python pandas optimization


【解决方案1】:

为了扩展我的评论,这里有一个简单的方法来解决这个问题。

首先,我们生成“往返”,即从枢纽到另一个机场再返回的可行行程(“可行”行程是指在第一段行程完成后离开远程机场的行程)。对于给定的出境航班,我们选择了最快返回的航班。请注意,它不一定是“第一班返程航班”,因为给定路线的持续时间可能会有所不同:例如,您可能有一个去肯尼迪国际机场的去程航班在上午 10 点降落,然后有 2 个返程航班,其中一个在 10:30 起飞下午 1 点回到家,第二个在 10:35 出发,但在中午 12:50 提前到达。

其次,我们选择最长可行的往返序列,即必须在下一次开始之前完成一次往返。该序列是根据返回时间贪婪地选择第一个往返行程的序列,然后是那个时间之后离开的第二个往返行程,然后再次最快返回,依此类推。

在这两个步骤中,贪婪的方法保证我们找到全局最优:

  1. 在第一步中,为给定的第一条腿“X”选择往返没有优势,因为它会比我们选择的(第一个回来的)晚回来。
  2. 在第二步中,选择比我们选择的那个(再次,第一个回来)晚回来的下一个往返没有优势。

    假飞行数据生成器

    为了对此进行试验,我们编写了一个随机生成的虚假飞行数据。唯一的要求是ARRV_TIME > DPTR_TIME。我们不会关心对城市进行合理的安排,使距离满足三角不等式或飞行时间与距离大致一致。

    def gen_random_flights(n, m, t0='2020-01-01', hub='hub'):
        # random airport flight data:
        #   random made-up destinations, random durations
        #   (one-way duration can be totally diff. than return flight)
        # n: number of "other" airports
        # m: number of flights
        airports = pd.unique(
            np.random.randint(65, 65+26, 2*n * 3, dtype=np.uint32).view(f'U3')
        )[:n]  # other airports
        t0 = pd.Timestamp(t0)
        starts = np.random.uniform(size=m + 1).cumsum()
        starts = (pd.to_timedelta(starts / starts[-1], 'day')[:-1] + t0).round('min')
    
        dur = pd.to_timedelta(pd.Series(np.random.randint(30, 4*60, size=m)), 'min')
        is_dept = np.random.choice([False, True], size=m)
        other_airport = np.random.choice(airports, m)
    
        flight_num = pd.unique(np.random.randint(0, 10000, 2*m))[:m]
        flight_airline = np.random.choice(['UA', 'AS', 'NZ', 'AC', 'AA', 'VA', 'LH'], m)
        flight_num = [f'{name}{i}' for name, i in zip(flight_airline, flight_num)]
    
        df = pd.DataFrame({
            'flight': flight_num,
            'DPTR_TIME': starts,
            'ARRV_TIME': starts + dur,
            'ORIG': np.where(is_dept, hub, other_airport),
            'DEST': np.where(is_dept, other_airport, hub),
        })
        return df
    

    例如:

    np.random.seed(163)  # chosen for more interesting data
    df = gen_random_flights(2, 10)
    >>> df
       flight           DPTR_TIME           ARRV_TIME ORIG DEST
    0  NZ1149 2020-01-01 02:48:00 2020-01-01 03:37:00  hub  BOH
    1    UA70 2020-01-01 04:51:00 2020-01-01 08:04:00  BOH  hub
    2  LH3995 2020-01-01 05:27:00 2020-01-01 08:05:00  hub  BOH
    3  AS7420 2020-01-01 07:04:00 2020-01-01 10:29:00  hub  BOH
    4  UA2777 2020-01-01 08:18:00 2020-01-01 08:50:00  hub  PCH
    5  VA3028 2020-01-01 09:09:00 2020-01-01 10:22:00  PCH  hub
    6  AA1217 2020-01-01 12:12:00 2020-01-01 13:00:00  hub  BOH
    7  AA8825 2020-01-01 15:02:00 2020-01-01 16:39:00  BOH  hub
    8  LH9857 2020-01-01 17:36:00 2020-01-01 20:01:00  PCH  hub
    9  LH5359 2020-01-01 20:53:00 2020-01-01 23:40:00  hub  PCH
    

    机场代码和航班号当然是随机的。

    往返

    上面说了,这里的算法很简单,对于任意一个出港航班,选择第一个可行的返程航班按到达时间.

    def make_roundtrips(df, hub='hub'):
        is_outb = df['ORIG'] == hub
        is_back = df['DEST'] == hub
    
        a = df.loc[is_outb]
        b = df.loc[is_back]
    
        z = a.merge(
            b, left_on='DEST', right_on='ORIG',
            suffixes=['', '_ret']
        ).query('ARRV_TIME < DPTR_TIME_ret').sort_values('ARRV_TIME_ret')
        z = z.groupby('DPTR_TIME', sort=False).first().reset_index()
        
        return z[[
            'flight', 'DPTR_TIME', 'ARRV_TIME', 'DEST',
            'flight_ret', 'DPTR_TIME_ret', 'ARRV_TIME_ret',
        ]]
    

    上面我们的假数据示例:

    z = make_roundtrips(df)
    >>> z
       flight           DPTR_TIME           ARRV_TIME DEST flight_ret       DPTR_TIME_ret       ARRV_TIME_ret
    0  NZ1149 2020-01-01 02:48:00 2020-01-01 03:37:00  BOH       UA70 2020-01-01 04:51:00 2020-01-01 08:04:00
    1  UA2777 2020-01-01 08:18:00 2020-01-01 08:50:00  PCH     VA3028 2020-01-01 09:09:00 2020-01-01 10:22:00
    2  LH3995 2020-01-01 05:27:00 2020-01-01 08:05:00  BOH     AA8825 2020-01-01 15:02:00 2020-01-01 16:39:00
    3  AS7420 2020-01-01 07:04:00 2020-01-01 10:29:00  BOH     AA8825 2020-01-01 15:02:00 2020-01-01 16:39:00
    4  AA1217 2020-01-01 12:12:00 2020-01-01 13:00:00  BOH     AA8825 2020-01-01 15:02:00 2020-01-01 16:39:00
    

    选择最长的往返序列

    现在我们已经缩小了有趣的往返行程,我们可以通过返回(到达)时间贪婪地选择第一个,然后是下一个,等等。

    def select_roundtrips(z):
        t = z['DPTR_TIME'].min() - pd.Timedelta(1)
        z = z.sort_values('ARRV_TIME_ret')  # just to make sure
        ix = []
        while True:
            cond = z['DPTR_TIME'] > t
            if not cond.any():
                break
            i = z.loc[cond].index[0]
            ix.append(i)
            t = z.loc[i, 'ARRV_TIME_ret']
        return z.loc[ix]
    

    继续我们上面的假例子:

    >>> select_roundtrips(z)
       flight           DPTR_TIME           ARRV_TIME DEST flight_ret       DPTR_TIME_ret       ARRV_TIME_ret
    0  NZ1149 2020-01-01 02:48:00 2020-01-01 03:37:00  BOH       UA70 2020-01-01 04:51:00 2020-01-01 08:04:00
    1  UA2777 2020-01-01 08:18:00 2020-01-01 08:50:00  PCH     VA3028 2020-01-01 09:09:00 2020-01-01 10:22:00
    4  AA1217 2020-01-01 12:12:00 2020-01-01 13:00:00  BOH     AA8825 2020-01-01 15:02:00 2020-01-01 16:39:00
    

    速度

    更实际的数据大小的性能如何?交叉连接不会非常昂贵吗?

    连接的大小确实是 O(p^2),其中 p 是我们的枢纽和给定机场之间的最大航班数量。实际上,即使在繁忙的机场,这实际上也很小。

    在假数据上,该方法表现得很好。例子:

    df = gen_random_flights(50, 2500)
    %timeit select_roundtrips(make_roundtrips(df))
    26.1 ms ± 197 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多