Some considerations:
- I consider a trip starts always from base city (SVQ in our example)
- The arrival hours misses the date, so I assume is the same day as the flight/train started
I have dealt with this exercise same way I would deal with a open source project for converting this kind of input data. Instead of converting the input in the output format directly I created an internal state in the midle. I have used Ecto (maybe too much for this but I wanted to take advantage of the cast/changeset power). I also used Timex for date's formatting.
The main module and entry point is WillyFog.Planner. Here we define two functions:
parse/1
which receives a string and returns a ok tuple with a Planparse_file/1
which receives a path to a file and then calls internally toparse/1
.
For example, lets try with this input:
BASED: SVQ
RESERVATION
SEGMENT: Flight SVQ 2023-03-02 06:40 -> BCN 09:10
RESERVATION
SEGMENT: Hotel BCN 2023-01-05 -> 2023-01-10
RESERVATION
SEGMENT: Flight SVQ 2023-01-05 20:40 -> BCN 22:10
SEGMENT: Flight BCN 2023-01-10 10:30 -> SVQ 11:50
First we need to load the library in our elixir IEX console (this is not an application).
iex -S mix
In the console we can run the library now
iex(2)> WillyFog.Planner.parse input
{:ok,
%WillyFog.Planner.Plans.Plan{
base: "SVQ",
trips: [
%WillyFog.Planner.Plans.Trip{
origin: "SVQ",
destinations: ["BCN"],
segments: [
%WillyFog.Planner.Plans.Segment{
type: "flight",
flight: %WillyFog.Planner.Plans.Segment.Flight{
origin: "SVQ",
destination: "BCN",
starts_at: ~N[2023-01-05 20:40:00],
arrives_at: ~T[22:10:00]
},
train: nil,
hotel: nil
},
%WillyFog.Planner.Plans.Segment{
type: "hotel",
flight: nil,
train: nil,
hotel: %WillyFog.Planner.Plans.Segment.Hotel{
city: "BCN",
checkin: ~D[2023-01-05],
checkout: ~D[2023-01-10]
}
},
%WillyFog.Planner.Plans.Segment{
type: "flight",
flight: %WillyFog.Planner.Plans.Segment.Flight{
origin: "BCN",
destination: "SVQ",
starts_at: ~N[2023-01-10 10:30:00],
arrives_at: ~T[11:50:00]
},
train: nil,
hotel: nil
}
]
},
%WillyFog.Planner.Plans.Trip{
origin: "SVQ",
destinations: ["BCN"],
segments: [
%WillyFog.Planner.Plans.Segment{
type: "flight",
flight: %WillyFog.Planner.Plans.Segment.Flight{
origin: "SVQ",
destination: "BCN",
starts_at: ~N[2023-03-02 06:40:00],
arrives_at: ~T[09:10:00]
},
train: nil,
hotel: nil
}
]
}
]
}}
Note that I am returning a Plan struct instead of the required output String. I have done this having maintainability in mind, this means, today maybe we want to return a simple text but maybe tomorrow we would need to send an email with the itinerary, or a csv, or store it in the db... So the Plan struct is our main model. For this I created the module Formatter which currently only supports the simple_text/1
output.
Lets format the previous response:
iex(4)> WillyFog.Planner.Formatter.simple_text result
"TRIP to BCN\nFlight from SVQ to BCN at 2023-01-05 20:40 to 22:10\nHotel at BCN on 2023-01-05 to 2023-01-10\nFlight from BCN to SVQ at 2023-01-10 10:30 to 11:50\n\nTRIP to BCN\nFlight from SVQ to BCN at 2023-03-02 06:40 to 09:10\n"
In the root of the project we have the required input.txt file. I have created an Elixir script for running it (check /scripts
folder), you can run it with:
$ mix run scripts/read_input_txt.exs
"TRIP to BCN\nFlight from SVQ to BCN at 2023-01-05 20:40 to 22:10\nHotel at BCN on 2023-01-05 to 2023-01-10\nFlight from BCN to SVQ at 2023-01-10 10:30 to 11:50\n\nTRIP to MAD\nTrain from SVQ to MAD at 2023-02-15 09:30 to 11:00\nHotel at MAD on 2023-02-15 to 2023-02-17\nTrain from MAD to SVQ at 2023-02-17 17:00 to 19:30\n\nTRIP to NYC, BOS\nFlight from SVQ to BCN at 2023-03-02 06:40 to 09:10\nFlight from BCN to NYC at 2023-03-02 15:00 to 22:45\nFlight from NYC to BOS at 2023-03-06 08:00 to 09:25\n"
It is calling internally the functions defined before, you can check this file
In order to run tests you run
mix test
I also provided a more extended function
mix check
This add aditional checks like code format, credo, coverage and dialyzer. This function takes some time the first time we run it since the Dialyzer's PLTs has to be built.
Since this is a prototype I didn't pay too much attention to performance. In a real world scenario we would check the size of the inputs, if they were huge we will try to find a better solution for reading the data with streams in a lazy way instead of loading all in memory.