Skip to content

Commit 94007a6

Browse files
authored
Shared stop sort for non-ferry timetables (#2510)
* shared stop sort * use time and test * dialyzer * docs
1 parent c704135 commit 94007a6

File tree

3 files changed

+131
-43
lines changed

3 files changed

+131
-43
lines changed

lib/dotcom_web/controllers/schedule/timetable_controller.ex

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ defmodule DotcomWeb.ScheduleController.TimetableController do
9090
header_schedules =
9191
trip_schedules
9292
|> Map.values()
93-
|> header_schedules()
93+
|> Kernel.then(&header_schedules(route, &1))
9494

9595
track_changes = track_changes(trip_schedules, Enum.map(trip_stops, & &1.id))
9696

@@ -513,10 +513,15 @@ defmodule DotcomWeb.ScheduleController.TimetableController do
513513
{{nil, nil}, schedule}
514514
end
515515

516-
@spec header_schedules(list) :: list
517-
defp header_schedules(timetable_schedules) do
518-
timetable_schedules
519-
|> Schedules.Sort.sort_by_first_times()
516+
defp header_schedules(%Route{description: :ferry}, schedules) do
517+
schedules
518+
|> Schedules.Sort.sort_by_first_departure()
519+
|> Enum.map(&List.first/1)
520+
end
521+
522+
defp header_schedules(%Route{}, schedules) do
523+
schedules
524+
|> Schedules.Sort.sort_by_first_shared_stop()
520525
|> Enum.map(&List.first/1)
521526
end
522527

lib/schedules/sort.ex

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,42 +8,77 @@ defmodule Schedules.Sort do
88
@doc """
99
Sorts schedules by grouping them into trips and then comparing the first departure.
1010
"""
11-
@spec sort_by_first_times([Schedule.t()]) :: [[Schedule.t()]]
12-
def sort_by_first_times(schedules) do
11+
@spec sort_by_first_departure([Schedule.t()]) :: [[Schedule.t()]]
12+
def sort_by_first_departure(schedules) do
1313
schedules
14+
|> Enum.sort_by(& &1.departure_time, &datetime_sorter/2)
1415
|> Enum.group_by(& &1.trip.id)
15-
|> Enum.sort_by(&mapper/1, &sorter/2)
16-
|> Enum.map(fn {_, schedules} -> schedules end)
16+
|> Enum.sort_by(&mapper/1, &departure_sorter/2)
17+
|> Enum.map(&mapper/1)
1718
end
1819

19-
# Gets the first departure time for a list of schedules.
20-
defp first_departure_time(schedules) do
20+
@doc """
21+
Sorts schedules by comparing the time of the first shared stop.
22+
"""
23+
@spec sort_by_first_shared_stop([Schedule.t()]) :: [[Schedule.t()]]
24+
def sort_by_first_shared_stop(schedules) do
2125
schedules
22-
|> Enum.map(& &1.departure_time)
23-
|> Enum.reject(&is_nil/1)
24-
|> Enum.sort(&subsorter/2)
25-
|> List.first()
26+
|> Enum.sort_by(& &1.time, &datetime_sorter/2)
27+
|> Enum.group_by(& &1.trip.id)
28+
|> Enum.sort_by(&mapper/1, &first_shared_stop_sorter/2)
29+
|> Enum.map(&mapper/1)
2630
end
2731

28-
# Ignores the trip id and returns the schedules for sorting.
29-
defp mapper({_, schedules}), do: schedules
30-
3132
# Sorts two schedule lists by comparing the first departure time of each.
32-
defp sorter(a, b) do
33+
defp departure_sorter(a, b) do
3334
a_departure_time = first_departure_time(a)
3435

3536
b_departure_time = first_departure_time(b)
3637

37-
subsorter(a_departure_time, b_departure_time)
38+
datetime_sorter(a_departure_time, b_departure_time)
3839
end
3940

4041
# Compares two times.
4142
# Handles the case where one or more are nil.
42-
defp subsorter(nil, nil), do: true
43-
defp subsorter(nil, _), do: false
44-
defp subsorter(_, nil), do: true
43+
defp datetime_sorter(nil, nil), do: true
44+
defp datetime_sorter(nil, _), do: false
45+
defp datetime_sorter(_, nil), do: true
4546

46-
defp subsorter(a, b) do
47+
defp datetime_sorter(a, b) do
4748
Timex.compare(a, b) < 1
4849
end
50+
51+
# Finds the schedule with the given stop id in a list of schedules.
52+
defp find_schedule_with_stop(schedules, stop_id) do
53+
Enum.find(schedules, fn schedule -> schedule.stop.id === stop_id end)
54+
end
55+
56+
# Gets the first departure time for a list of schedules.
57+
defp first_departure_time(schedules) do
58+
schedules
59+
|> Enum.map(& &1.departure_time)
60+
|> Enum.reject(&is_nil/1)
61+
|> Enum.sort(&datetime_sorter/2)
62+
|> List.first()
63+
end
64+
65+
# Sorts two schedule lists by comparing the departure time of the first shared stop.
66+
defp first_shared_stop_sorter(a, b) do
67+
a_stop_ids = Enum.map(a, & &1.stop.id)
68+
b_stop_ids = Enum.map(b, & &1.stop.id)
69+
70+
common_stop = Enum.find(a_stop_ids, &Enum.member?(b_stop_ids, &1))
71+
72+
if common_stop do
73+
a_schedule = find_schedule_with_stop(a, common_stop)
74+
b_schedule = find_schedule_with_stop(b, common_stop)
75+
76+
datetime_sorter(a_schedule.time, b_schedule.time)
77+
else
78+
departure_sorter(a, b)
79+
end
80+
end
81+
82+
# Ignores the trip id and returns the schedules for sorting.
83+
defp mapper({_, schedules}), do: schedules
4984
end

test/schedules/sort_test.exs

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,83 @@ defmodule SortTest do
66

77
alias Schedules.Schedule
88
alias Schedules.Trip
9+
alias Stops.Stop
910

1011
@now Timex.now()
1112

1213
@schedules [
13-
%Schedule{departure_time: shift(@now, minutes: 1), trip: %Trip{id: "1"}},
14-
%Schedule{departure_time: shift(@now, minutes: 2), trip: %Trip{id: "1"}},
15-
%Schedule{departure_time: shift(@now, minutes: 5), trip: %Trip{id: "2"}},
16-
%Schedule{departure_time: shift(@now, minutes: 6), trip: %Trip{id: "2"}},
17-
%Schedule{departure_time: shift(@now, minutes: 3), trip: %Trip{id: "3"}},
18-
%Schedule{departure_time: shift(@now, minutes: 4), trip: %Trip{id: "3"}}
14+
%Schedule{
15+
departure_time: shift(@now, minutes: 2),
16+
stop: %Stop{id: "2"},
17+
time: shift(@now, minutes: 2),
18+
trip: %Trip{id: "1"}
19+
},
20+
%Schedule{
21+
departure_time: shift(@now, minutes: 1),
22+
stop: %Stop{id: "1"},
23+
time: shift(@now, minutes: 1),
24+
trip: %Trip{id: "1"}
25+
},
26+
%Schedule{
27+
departure_time: shift(@now, minutes: 6),
28+
stop: %Stop{id: "2"},
29+
time: shift(@now, minutes: 6),
30+
trip: %Trip{id: "3"}
31+
},
32+
%Schedule{
33+
departure_time: shift(@now, minutes: 5),
34+
stop: %Stop{id: "1"},
35+
time: shift(@now, minutes: 5),
36+
trip: %Trip{id: "3"}
37+
},
38+
%Schedule{
39+
departure_time: shift(@now, minutes: 4),
40+
stop: %Stop{id: "2"},
41+
time: shift(@now, minutes: 4),
42+
trip: %Trip{id: "2"}
43+
},
44+
%Schedule{
45+
departure_time: shift(@now, minutes: 3),
46+
stop: %Stop{id: "1"},
47+
time: shift(@now, minutes: 3),
48+
trip: %Trip{id: "2"}
49+
}
1950
]
2051

21-
describe "sort_by_first_time/1" do
22-
test "groups a list of schedules into lists of trips" do
23-
result = sort_by_first_times(@schedules)
52+
@expected_sorted_trip_stop_ids [
53+
{"1", "1"},
54+
{"1", "2"},
55+
{"2", "1"},
56+
{"2", "2"},
57+
{"3", "1"},
58+
{"3", "2"}
59+
]
60+
61+
describe "sort_by_first_departure/1" do
62+
test "groups by trip and sorts groups on first departure time" do
63+
# Setup / Exercise
64+
sorted_trip_stop_ids =
65+
@schedules
66+
|> sort_by_first_departure()
67+
|> List.flatten()
68+
|> Enum.map(fn schedule -> {schedule.trip.id, schedule.stop.id} end)
2469

25-
for [first | _] = trip_list <- result do
26-
assert Enum.all?(trip_list, &(&1.trip.id == first.trip.id))
27-
end
70+
# Verify
71+
assert sorted_trip_stop_ids == @expected_sorted_trip_stop_ids
2872
end
73+
end
2974

30-
test "sorts schedules by their time at the first departure" do
31-
result = sort_by_first_times(@schedules)
75+
describe "sort_by_first_shared_stop/1" do
76+
test "groups by trip and sorts groups on first stop in common" do
77+
# Setup / Exercise
78+
sorted_trip_stop_ids =
79+
@schedules
80+
|> sort_by_first_shared_stop()
81+
|> List.flatten()
82+
|> Enum.map(fn schedule -> {schedule.trip.id, schedule.stop.id} end)
3283

33-
assert Enum.map(result, &List.first/1) == [
34-
%Schedule{departure_time: shift(@now, minutes: 1), trip: %Trip{id: "1"}},
35-
%Schedule{departure_time: shift(@now, minutes: 3), trip: %Trip{id: "3"}},
36-
%Schedule{departure_time: shift(@now, minutes: 5), trip: %Trip{id: "2"}}
37-
]
84+
# Verify
85+
assert sorted_trip_stop_ids == @expected_sorted_trip_stop_ids
3886
end
3987
end
4088
end

0 commit comments

Comments
 (0)