計算將港鐵車程拆開以減低車費的車程組合

注意:
本人並不確保本文的準確性,對任何損失或傷害概不負責,亦非鼓吹這樣做,只為作出簡單的計算供參考。本文提及的車費於本文刊登日有效。

眾所周知,如果在某些港鐵站前往羅湖或落馬洲,在上水出閘再原站入閘,總車費有可能會減少。我嘗試利用以下的小程式,計算出成人車票,包括八達通及單程車票,在港鐵系統內有可能因中途出閘再入閘而便宜了的組合。

先到政府資料一線通網站下載最新的港鐵車費表 mtr_lines_fares.csv(不包括輕鐵)。

再執行以下程式:
#!/usr/bin/env python

import csv
from math import ceil

octopusFareList = {}
ticketFareList = {}
stationList = {}
"""
	field available
	"SRC_STATION_NAME",
	"SRC_STATION_ID",
	"DEST_STATION_NAME",
	"DEST_STATION_ID",
	"OCT_ADT_FARE",
	"OCT_STD_FARE",
	"SINGLE_ADT_FARE",
	"OCT_CON_CHILD_FARE",
	"OCT_CON_ELDERLY_FARE",
	"OCT_CON_PWD_FARE",
	"SINGLE_CON_CHILD_FARE",
	"SINGLE_CON_ELDERLY_FARE"
"""
with open('mtr_lines_fares.csv', 'rb') as csvfile:
    reader = csv.DictReader(csvfile, delimiter=',')
    for row in reader:
		if (row['SRC_STATION_ID'] == row['DEST_STATION_ID']):
			continue
		srcId = int(row['SRC_STATION_ID'])
		destId = int(row['DEST_STATION_ID'])

		octopusFareList[srcId,destId] = float(row['OCT_ADT_FARE'])
		ticketFareList[srcId,destId] = float(row['SINGLE_ADT_FARE'])
		stationList[srcId] = row["SRC_STATION_NAME"]

# print octopusFareList[100,22]

# 1 = Central, 39 = Hong Kong , 3 = TST ,80 = ETST ,exclude these cases
centralStdId = 1
hongKongStdId = 39
tstStdId = 3
etstStdId = 80

print '"TYPE","START","OUT_AND_IN","END","OLD","NEW","SAVE"'
for i in range(1,121):
	for j in range(1,121):
		if i == j or (i == centralStdId and j == hongKongStdId) or (i == hongKongStdId and j == centralStdId) or (i == etstStdId and j == tstStdId)or (i == tstStdId and j == etstStdId):
			continue
		if (i,j) not in octopusFareList.keys():
			continue
		for x in range(1,121):
			if x == i or x == j or (i == centralStdId and x == hongKongStdId) or (i == hongKongStdId and x == centralStdId) or (i == etstStdId and x == tstStdId)or (i == tstStdId and x == etstStdId) or (j == centralStdId and x == hongKongStdId) or (j == hongKongStdId and x == centralStdId) or (j == etstStdId and x == tstStdId)or (j == tstStdId and x == etstStdId):
				continue
			if (i,x) not in octopusFareList.keys():
				continue
			if (x,j) not in octopusFareList.keys():
				continue
			# normal octopus case
			newOctupusFare = octopusFareList[i,x] + octopusFareList[x,j]
			if newOctupusFare <= octopusFareList[i,j] - 0.01:
				print '"OCTOPUS","' + stationList[i] + '","' + stationList[x] + '","' + stationList[j] + '",' + str(octopusFareList[i,j]) + "," + str(newOctupusFare) + "," + str(octopusFareList[i,j] - newOctupusFare)
			# 2nd trip discount
			newOctupusFare = octopusFareList[i,x] + ceil(octopusFareList[x,j] * 0.9*10.0)/10.0
			if newOctupusFare <= octopusFareList[i,j] - 0.01:
				print '"OCTOPUS(2nd trip)","' + stationList[i] + '","' + stationList[x] + '","' + stationList[j] + '",' + str(octopusFareList[i,j]) + "," + str(newOctupusFare) + "," + str(octopusFareList[i,j] - newOctupusFare)
			# single trip ticket
			newTicketFare = ticketFareList[i,x] + ticketFareList[x,j]
			if newTicketFare <= ticketFareList[i,j] - 0.01:
				print '"TICKET","' + stationList[i] + '","' + stationList[x] + '","' + stationList[j] + '",'+ str(ticketFareList[i,j]) + "," + str(newTicketFare) + ","  + str(ticketFareList[i,j] - newTicketFare)
		# Handle TST and ETST
		if (i != tstStdId) and (j != etstStdId) and (i != etstStdId) and (j != tstStdId):
			# normal octopus case
			newOctupusFare = octopusFareList[i,tstStdId] + octopusFareList[etstStdId,j]
			if newOctupusFare <= octopusFareList[i,j] - 0.01:
				print '"OCTOPUS","' + stationList[i] + '","' + stationList[tstStdId] + "->" + stationList[etstStdId] + '","' + stationList[j] + '",' + str(octopusFareList[i,j]) + "," + str(newOctupusFare) + "," + str(octopusFareList[i,j] - newOctupusFare)
			# 2nd trip discount
			newOctupusFare = octopusFareList[i,tstStdId] + ceil(octopusFareList[etstStdId,j] * 0.9*10.0)/10.0
			if newOctupusFare <= octopusFareList[i,j] - 0.01:
				print '"OCTOPUS(2nd trip)","' + stationList[i] + '","' + stationList[tstStdId] + "->" + stationList[etstStdId] + '","' + stationList[j] + '",' + str(octopusFareList[i,j]) + "," + str(newOctupusFare) + "," + str(octopusFareList[i,j] - newOctupusFare)
			# single trip ticket
			newTicketFare = ticketFareList[i,tstStdId] + ticketFareList[etstStdId,j]
			if newTicketFare <= ticketFareList[i,j] - 0.01:
				print '"TICKET","' + stationList[i] + '","' + stationList[tstStdId] + "->" + stationList[etstStdId] + '","' + stationList[j] + '",'+ str(ticketFareList[i,j]) + "," + str(newTicketFare) + ","  + str(ticketFareList[i,j] - newTicketFare)
		

			# normal octopus case
			newOctupusFare = octopusFareList[i,etstStdId] + octopusFareList[tstStdId,j]
			if newOctupusFare <= octopusFareList[i,j] - 0.01:
				print '"OCTOPUS","' + stationList[i] + '","' + stationList[etstStdId] + "->" + stationList[tstStdId] + '","' + stationList[j] + '",' + str(octopusFareList[i,j]) + "," + str(newOctupusFare) + "," + str(octopusFareList[i,j] - newOctupusFare)
			# 2nd trip discount
			newOctupusFare = octopusFareList[i,etstStdId] + ceil(octopusFareList[tstStdId,j] * 0.9*10.0)/10.0
			if newOctupusFare <= octopusFareList[i,j] - 0.01:
				print '"OCTOPUS(2nd trip)","' + stationList[i] + '","' + stationList[etstStdId] + "->" + stationList[tstStdId] + '","' + stationList[j] + '",' + str(octopusFareList[i,j]) + "," + str(newOctupusFare) + "," + str(octopusFareList[i,j] - newOctupusFare)
			# single trip ticket
			newTicketFare = ticketFareList[i,etstStdId] + ticketFareList[tstStdId,j]
			if newTicketFare <= ticketFareList[i,j] - 0.01:
				print '"TICKET","' + stationList[i] + '","' + stationList[etstStdId] + "->" + stationList[tstStdId] + '","' + stationList[j] + '",'+ str(ticketFareList[i,j]) + "," + str(newTicketFare) + ","  + str(ticketFareList[i,j] - newTicketFare)

執行後,可得出一萬多項結果。我將結果輸出到一個 csv file,可到下載。

以下為部分結果:
mtr-1

第一列為車票種類,有八達通、第二程八達通可享九折的車程(詳情)和單程車票。
第二列為起點車站,本例選取了大埔墟作為起點。
第三列為中途出閘再入閘(單程車票需重新購票)的車站。
第四列為終點車站。
第五及第六列分別為原來車費及新車費。
第七列為可節省的車費。

本文連結