transfer_trains/
timetable.rs

1/*!
2 * A timetable vocabulary.
3 *
4 * Copyright (C) 2023-2025 kaoru  <https://www.tetengo.org/>
5 */
6
7use std::collections::HashMap;
8use std::error;
9use std::fmt::Write;
10use std::hash::{DefaultHasher, Hash, Hasher};
11use std::io::{BufRead, Lines};
12use std::rc::Rc;
13
14use tetengo_lattice::{Entry, HashMapVocabulary, StringInput, Vocabulary};
15
16/**
17 * A timetable error.
18 */
19#[derive(Debug, thiserror::Error)]
20pub(crate) enum TimetableError {
21    /**
22     * Unexpected end of file.
23     */
24    #[error("unexpected end of file")]
25    UnexpectedEndOfFile,
26
27    /**
28     * Station names and telegram codes unmatch.
29     */
30    #[error("station names and telegram codes unmatch")]
31    StationNamesAndTelegramCodesUnmatch,
32
33    /**
34     * Invalid train line found.
35     */
36    #[error("invalid train line found")]
37    InvalidTrainLineFound,
38
39    /**
40     * Invalid arrival/departure time found.
41     */
42    #[error("invalid arrival/departure time found")]
43    InvalidArrivalOrDepartureTimeFound,
44
45    /**
46     * Invalid time found.
47     */
48    #[error("invalid time found")]
49    InvalidTimeFound,
50
51    /**
52     * Both arrival and departure time not found.
53     */
54    #[error("both arrival and departure time not found")]
55    BothArrivalAndDepartureTimeNotFound,
56
57    /**
58     * An error returned from an internal crate.
59     */
60    #[error("internal error: {0}")]
61    InternalError(#[from] Box<dyn error::Error>),
62}
63
64/**
65 * A station.
66 */
67#[derive(Debug)]
68pub(crate) struct Station {
69    name: String,
70    telegram_code: String,
71}
72
73impl Station {
74    /**
75     * Creates a station.
76     *
77     * # Arguments
78     * * `name`          - A name.
79     * * `telegram_code` - A telegram code.
80     */
81    pub(crate) const fn new(name: String, telegram_code: String) -> Self {
82        Self {
83            name,
84            telegram_code,
85        }
86    }
87
88    /**
89     * Returns the name.
90     *
91     * # Returns
92     * The name.
93     */
94    pub(crate) const fn name(&self) -> &str {
95        self.name.as_str()
96    }
97
98    /**
99     * Returns the telegram code.
100     *
101     * # Returns
102     * The telegram code.
103     */
104    pub(crate) const fn telegram_code(&self) -> &str {
105        self.telegram_code.as_str()
106    }
107}
108
109/**
110 * A stop.
111 */
112#[derive(Clone, Debug)]
113pub(crate) struct Stop {
114    arrival_time: Option<usize>,
115    departure_time: Option<usize>,
116}
117
118impl Stop {
119    /**
120     * Creates a stop.
121     *
122     * # Arguments
123     * * `arrival_time`   - An arrival time.
124     * * `departure_time` - A departure time.
125     */
126    pub(crate) const fn new(arrival_time: Option<usize>, departure_time: Option<usize>) -> Self {
127        Self {
128            arrival_time,
129            departure_time,
130        }
131    }
132
133    /**
134     * Returns the arrival time.
135     *
136     * # Returns
137     * The arrival time.
138     */
139    pub(crate) const fn arrival_time(&self) -> Option<usize> {
140        self.arrival_time
141    }
142
143    /**
144     * Sets an arrival time.
145     *
146     * # Arguments
147     * * `time` - An arrival time.
148     */
149    pub(crate) const fn set_arrival_time(&mut self, time: usize) {
150        self.arrival_time = Some(time);
151    }
152
153    /**
154     * Returns the departure time.
155     *
156     * # Returns
157     * The departure time.
158     */
159    pub(crate) const fn departure_time(&self) -> Option<usize> {
160        self.departure_time
161    }
162
163    /**
164     * Sets a departure time.
165     *
166     * # Arguments
167     * * `time` - A departure time.
168     */
169    pub(crate) const fn set_departure_time(&mut self, time: usize) {
170        self.departure_time = Some(time);
171    }
172}
173
174/**
175 * A train.
176 */
177#[derive(Clone, Debug)]
178pub(crate) struct Train {
179    number: String,
180    name: String,
181    stops: Vec<Stop>,
182}
183
184impl Train {
185    /**
186     * Creates a train.
187     *
188     * # Arguments
189     * * `number` - A number.
190     * * `name`   - A name.
191     * * `stops`  - Stops.
192     */
193    pub(crate) const fn new(number: String, name: String, stops: Vec<Stop>) -> Self {
194        Self {
195            number,
196            name,
197            stops,
198        }
199    }
200
201    /**
202     * Returns the number.
203     *
204     * # Returns
205     * The number.
206     */
207    pub(crate) const fn number(&self) -> &str {
208        self.number.as_str()
209    }
210
211    /**
212     * Returns the name.
213     *
214     * # Returns
215     * The name.
216     */
217    pub(crate) const fn name(&self) -> &str {
218        self.name.as_str()
219    }
220
221    /**
222     * Returns the stops.
223     *
224     * # Returns
225     * The stops.
226     */
227    pub(crate) const fn stops(&self) -> &[Stop] {
228        self.stops.as_slice()
229    }
230
231    /**
232     * Returns the stops.
233     *
234     * # Returns
235     * The stops.
236     */
237    pub(crate) const fn stops_mut(&mut self) -> &mut Vec<Stop> {
238        &mut self.stops
239    }
240}
241
242/**
243 * A section.
244 */
245#[derive(Clone, Debug)]
246pub(crate) struct Section {
247    train: Rc<Train>,
248    from: usize,
249    to: usize,
250}
251
252impl Section {
253    /**
254     * Creates a section.
255     *
256     * # Arguments
257     * * `train` - A train.
258     * * `from`  - A departure station index.
259     * * `to`    - An arrival station index.
260     */
261    pub(crate) const fn new(train: Rc<Train>, from: usize, to: usize) -> Self {
262        Self { train, from, to }
263    }
264
265    /**
266     * Returns the train.
267     *
268     * # Returns
269     * The train.
270     */
271    pub(crate) fn train(&self) -> &Train {
272        self.train.as_ref()
273    }
274
275    /**
276     * Returns the departure station index.
277     *
278     * # Returns
279     * The departure station index.
280     */
281    pub(crate) const fn from(&self) -> usize {
282        self.from
283    }
284
285    /**
286     * Returns the arrival station index.
287     *
288     * # Returns
289     * The arrival station index.
290     */
291    pub(crate) const fn to(&self) -> usize {
292        self.to
293    }
294}
295
296#[derive(Debug)]
297struct TimetableValue {
298    stations: Vec<Station>,
299    trains: Vec<Train>,
300}
301
302impl TimetableValue {
303    const fn new(stations: Vec<Station>, trains: Vec<Train>) -> Self {
304        Self { stations, trains }
305    }
306}
307
308/**
309 * A timetable vocabulary.
310 */
311#[derive(Debug)]
312pub(crate) struct Timetable {
313    value: TimetableValue,
314}
315
316impl Timetable {
317    /**
318     * Creates a timetable vocabulary.
319     *
320     * # Arguments
321     * * `reader` - A reader.
322     */
323    pub(crate) fn new(reader: Box<dyn BufRead>) -> Result<Self, TimetableError> {
324        Ok(Self {
325            value: Self::build_timetable(reader)?,
326        })
327    }
328
329    fn build_timetable(mut reader: Box<dyn BufRead>) -> Result<TimetableValue, TimetableError> {
330        let mut value = Self::parse_input(reader.as_mut())?;
331        Self::guess_arrival_times(&mut value)?;
332        Ok(value)
333    }
334
335    fn parse_input(reader: &mut dyn BufRead) -> Result<TimetableValue, TimetableError> {
336        let mut lines = reader.lines();
337
338        let stations = {
339            let Some(line1) = Self::read_line(&mut lines)? else {
340                return Err(TimetableError::UnexpectedEndOfFile);
341            };
342            let Some(line2) = Self::read_line(&mut lines)? else {
343                return Err(TimetableError::UnexpectedEndOfFile);
344            };
345            Self::parse_stations(line1, line2)?
346        };
347
348        let trains = {
349            let mut trains = Vec::new();
350            while let Some(line) = Self::read_line(&mut lines)? {
351                if line.is_empty() || (line.len() == 1 && line[0].is_empty()) {
352                    continue;
353                }
354                trains.push(Self::parse_train(line, stations.len())?);
355            }
356            trains
357        };
358
359        Ok(TimetableValue::new(stations, trains))
360    }
361
362    fn read_line(
363        lines: &mut Lines<&mut dyn BufRead>,
364    ) -> Result<Option<Vec<String>>, TimetableError> {
365        let Some(line) = lines.next() else {
366            return Ok(None);
367        };
368        let line = line.map_err(|e| TimetableError::InternalError(e.into()))?;
369        let elements = line
370            .split(',')
371            .map(|e| e.trim().to_string())
372            .collect::<Vec<_>>();
373        Ok(Some(elements))
374    }
375
376    fn parse_stations(
377        line1: Vec<String>,
378        line2: Vec<String>,
379    ) -> Result<Vec<Station>, TimetableError> {
380        if line1.len() != line2.len() {
381            return Err(TimetableError::StationNamesAndTelegramCodesUnmatch);
382        }
383        let stations = line1
384            .into_iter()
385            .skip(2)
386            .zip(line2.into_iter().skip(2))
387            .map(|(name, telegram_code)| Station::new(name, telegram_code))
388            .collect::<Vec<_>>();
389        Ok(stations)
390    }
391
392    fn parse_train(mut line: Vec<String>, station_count: usize) -> Result<Train, TimetableError> {
393        if line.len() > station_count + 2 {
394            return Err(TimetableError::InvalidTrainLineFound);
395        }
396        line.resize(station_count + 2, String::new());
397        let number = line[0].clone();
398        let name = line[1].clone();
399        let stops = line
400            .into_iter()
401            .skip(2)
402            .map(|s| Self::to_stop(&s))
403            .collect::<Result<Vec<_>, TimetableError>>()?;
404        Ok(Train::new(number, name, stops))
405    }
406
407    fn to_stop(element: &str) -> Result<Stop, TimetableError> {
408        let string_times = element
409            .split('/')
410            .map(|e| e.trim().to_string())
411            .collect::<Vec<_>>();
412        if string_times.is_empty() || string_times.len() > 2 {
413            Err(TimetableError::InvalidArrivalOrDepartureTimeFound)
414        } else if string_times.len() == 1 {
415            Ok(Stop::new(None, Self::to_minutes(string_times[0].as_str())?))
416        } else {
417            Ok(Stop::new(
418                Self::to_minutes(string_times[0].as_str())?,
419                Self::to_minutes(string_times[1].as_str())?,
420            ))
421        }
422    }
423
424    fn to_minutes(string_time: &str) -> Result<Option<usize>, TimetableError> {
425        if string_time.is_empty() || string_time == "-" {
426            return Ok(None);
427        }
428        let int_time = string_time
429            .parse::<usize>()
430            .map_err(|e| TimetableError::InternalError(e.into()))?;
431        let hour = int_time / 100;
432        let minute = int_time - hour * 100;
433        if hour >= 24 || minute >= 60 {
434            return Err(TimetableError::InvalidTimeFound);
435        }
436        Ok(Some(hour * 60 + minute))
437    }
438
439    fn guess_arrival_times(value: &mut TimetableValue) -> Result<(), TimetableError> {
440        for from in 0..value.stations.len() - 1 {
441            for to in from + 1..value.stations.len() {
442                let minimum_duration = Self::minimum_duration(value.trains.as_ref(), from, to)?;
443                for train in &mut value.trains {
444                    if !Self::all_passing(train.stops(), from, to) {
445                        continue;
446                    }
447                    if train.stops()[to].arrival_time().is_none() {
448                        let Some(from_departure_time) = train.stops()[from].departure_time() else {
449                            return Err(TimetableError::BothArrivalAndDepartureTimeNotFound);
450                        };
451                        train.stops_mut()[to].set_arrival_time(Self::add_time(
452                            from_departure_time,
453                            minimum_duration,
454                        )?);
455                    } else if train.stops()[from].departure_time().is_none() {
456                        let Some(to_arrival_time) = train.stops()[to].arrival_time() else {
457                            return Err(TimetableError::BothArrivalAndDepartureTimeNotFound);
458                        };
459                        train.stops_mut()[from].set_departure_time(Self::add_time(
460                            to_arrival_time,
461                            -minimum_duration,
462                        )?);
463                    }
464                }
465            }
466        }
467        Ok(())
468    }
469
470    fn minimum_duration(trains: &[Train], from: usize, to: usize) -> Result<isize, TimetableError> {
471        let mut minimum = isize::MAX;
472        for train in trains {
473            if !Self::all_passing(train.stops(), from, to) {
474                continue;
475            }
476            let from_time = if let Some(departure_time) = train.stops()[from].departure_time() {
477                departure_time
478            } else if let Some(arrival_time) = train.stops()[from].arrival_time() {
479                arrival_time
480            } else {
481                return Err(TimetableError::BothArrivalAndDepartureTimeNotFound);
482            };
483            let to_time = if let Some(arrival_time) = train.stops()[to].arrival_time() {
484                arrival_time
485            } else if let Some(departure_time) = train.stops()[to].departure_time() {
486                departure_time
487            } else {
488                return Err(TimetableError::BothArrivalAndDepartureTimeNotFound);
489            };
490            let duration = Self::diff_time(to_time, from_time)?;
491            if duration < minimum {
492                minimum = duration;
493            }
494        }
495        Ok(minimum)
496    }
497
498    /**
499     * Returns the stations.
500     *
501     * # Returns
502     * The stations.
503     */
504    pub(crate) const fn stations(&self) -> &[Station] {
505        self.value.stations.as_slice()
506    }
507
508    /**
509     * Returns the station index.
510     *
511     * # Arguments
512     * * `name_or_telegram_code` - A name or telegram code.
513     *
514     * # Returns
515     * The index. Or `stations().len()` if no station is found.
516     */
517    pub(crate) fn station_index(&self, name_or_telegram_code: &str) -> usize {
518        for (i, station) in self.value.stations.iter().enumerate() {
519            if station.name().to_lowercase() == name_or_telegram_code.to_lowercase()
520                || station.telegram_code().to_uppercase() == name_or_telegram_code.to_uppercase()
521            {
522                return i;
523            }
524        }
525        self.value.stations.len()
526    }
527
528    /**
529     * Creates a vocabulary.
530     *
531     * # Arguments
532     * * `departure_time` - A departure time.
533     *
534     * # Returns
535     * A vocabulary.
536     */
537    pub(crate) fn create_vocabulary(
538        &self,
539        departure_time: usize,
540    ) -> Result<Rc<dyn Vocabulary>, TimetableError> {
541        let entries = Self::build_entries(&self.value)?;
542        let connections = Self::build_connections(&entries, departure_time)?;
543        Ok(Rc::new(HashMapVocabulary::new(
544            entries,
545            connections,
546            &Self::entry_hash_value,
547            &Self::entry_equal_to,
548        )))
549    }
550
551    fn build_entries(
552        timetable: &TimetableValue,
553    ) -> Result<Vec<(String, Vec<Entry>)>, TimetableError> {
554        let mut map = HashMap::<String, Vec<Entry>>::new();
555        for train in &timetable.trains {
556            for from in 0..timetable.stations.len() - 1 {
557                for to in from + 1..timetable.stations.len() {
558                    if !Self::all_passing(train.stops(), from, to) {
559                        continue;
560                    }
561
562                    let section_name = Self::make_section_name(&timetable.stations, from, to);
563                    let found = map.entry(section_name.clone()).or_default();
564                    let section = Section::new(Rc::new(train.clone()), from, to);
565                    let section_duration = Self::make_section_duration(train.stops(), from, to)?;
566                    let cost = i32::try_from(section_duration)
567                        .map_err(|e| TimetableError::InternalError(e.into()))?;
568                    found.push(Entry::new(
569                        Box::new(StringInput::new(section_name)),
570                        Box::new(section),
571                        cost,
572                    ));
573                }
574            }
575        }
576        Ok(map.into_iter().collect::<Vec<_>>())
577    }
578
579    fn all_passing(stops: &[Stop], from: usize, to: usize) -> bool {
580        if stops[from].arrival_time().is_none() && stops[from].departure_time().is_none() {
581            return false;
582        }
583        if stops[to].arrival_time().is_none() && stops[to].departure_time().is_none() {
584            return false;
585        }
586        for stop in stops.iter().take(to).skip(from + 1) {
587            if stop.arrival_time().is_some() || stop.departure_time().is_some() {
588                return false;
589            }
590        }
591        true
592    }
593
594    fn make_section_name(stations: &[Station], from: usize, to: usize) -> String {
595        let mut name = String::new();
596        for i in from..to {
597            let _ = write!(
598                name,
599                "{}-{}/",
600                stations[i].telegram_code(),
601                stations[i + 1].telegram_code()
602            );
603        }
604        name
605    }
606
607    fn make_section_duration(
608        stops: &[Stop],
609        from: usize,
610        to: usize,
611    ) -> Result<usize, TimetableError> {
612        let departure_time = stops[from].departure_time().unwrap_or_else(|| {
613            unreachable!("departure_time must be set.");
614        });
615        let arrival_time = stops[to].arrival_time().unwrap_or_else(|| {
616            unreachable!("arrival_time must be set.");
617        });
618        let diff_result = Self::diff_time(arrival_time, departure_time)?;
619        usize::try_from(diff_result).map_err(|e| TimetableError::InternalError(e.into()))
620    }
621
622    fn build_connections(
623        entries: &[(String, Vec<Entry>)],
624        departure_time: usize,
625    ) -> Result<Vec<((Entry, Entry), i32)>, TimetableError> {
626        let mut connections = Vec::<((Entry, Entry), i32)>::new();
627
628        for (_, from_entries) in entries {
629            for (_, to_entries) in entries {
630                for from_entry in from_entries {
631                    for to_entry in to_entries {
632                        let from_value = from_entry
633                            .value()
634                            .unwrap_or_else(|| {
635                                unreachable!("from_entry.value() must not be empty.")
636                            })
637                            .downcast_ref::<Section>()
638                            .unwrap_or_else(|| unreachable!("from_entry.value() must be Section."));
639                        let to_value = to_entry
640                            .value()
641                            .unwrap_or_else(|| unreachable!("to_entry.value() must not be empty."))
642                            .downcast_ref::<Section>()
643                            .unwrap_or_else(|| unreachable!("to_entry.value() must be Section."));
644                        if from_value.to() != to_value.from() {
645                            continue;
646                        }
647
648                        let from_arrival_time = from_value.train().stops()[from_value.to()]
649                            .arrival_time()
650                            .unwrap_or_else(|| {
651                                unreachable!("from arrival_time must be set.");
652                            });
653                        let to_departure_time = to_value.train().stops()[to_value.from()]
654                            .departure_time()
655                            .unwrap_or_else(|| {
656                                unreachable!("to departure_time must be set.");
657                            });
658                        let time_diff = Self::diff_time(to_departure_time, from_arrival_time)?;
659                        let cost = i32::try_from(time_diff)
660                            .map_err(|e| TimetableError::InternalError(e.into()))?;
661                        if cost > 60 {
662                            continue;
663                        }
664                        if from_value.train().number() == to_value.train().number() {
665                            connections.push(((from_entry.clone(), to_entry.clone()), cost));
666                        } else {
667                            connections.push(((from_entry.clone(), to_entry.clone()), cost + 1));
668                        }
669                    }
670                }
671            }
672        }
673
674        for (_, entries) in entries {
675            for entry in entries {
676                let section = entry
677                    .value()
678                    .unwrap_or_else(|| unreachable!("entry.value() must not be empty."))
679                    .downcast_ref::<Section>()
680                    .unwrap_or_else(|| unreachable!("entry.value() must be Section."));
681                let section_departure_time = section.train().stops()[section.from()]
682                    .departure_time()
683                    .unwrap_or_else(|| {
684                        unreachable!("departure_time() must be set.");
685                    });
686                let time_diff = Self::diff_time(section_departure_time, departure_time)?;
687                let bos_cost = i32::try_from(time_diff)
688                    .map_err(|e| TimetableError::InternalError(e.into()))?;
689                if bos_cost <= 240 {
690                    connections.push(((Entry::BosEos, entry.clone()), bos_cost * 9 / 10));
691                }
692                connections.push(((entry.clone(), Entry::BosEos), 0));
693            }
694        }
695
696        Ok(connections)
697    }
698
699    fn add_time(time: usize, duration: isize) -> Result<usize, TimetableError> {
700        assert!(time < 1440);
701        assert!(-1440 < duration && duration < 1440);
702        let time_isize =
703            isize::try_from(time).map_err(|e| TimetableError::InternalError(e.into()))?;
704        let result = (time_isize + 1440 + duration) % 1440;
705        usize::try_from(result).map_err(|e| TimetableError::InternalError(e.into()))
706    }
707
708    fn diff_time(time1: usize, time2: usize) -> Result<isize, TimetableError> {
709        assert!(time1 < 1440);
710        assert!(time2 < 1440);
711        let time1_isize =
712            isize::try_from(time1).map_err(|e| TimetableError::InternalError(e.into()))?;
713        let time2_isize =
714            isize::try_from(time2).map_err(|e| TimetableError::InternalError(e.into()))?;
715        Ok((time1_isize + 1440 - time2_isize) % 1440)
716    }
717
718    fn entry_hash_value(entry: &Entry) -> u64 {
719        let mut hasher = DefaultHasher::new();
720
721        hasher.write_u64(entry.key().map_or(0, tetengo_lattice::Input::hash_value));
722        let section = entry
723            .value()
724            .and_then(|value| value.downcast_ref::<Section>());
725        if let Some(section) = section {
726            section.train().number().hash(&mut hasher);
727            section.train().name().hash(&mut hasher);
728            section.from().hash(&mut hasher);
729            section.to().hash(&mut hasher);
730        } else {
731            "".hash(&mut hasher);
732            "".hash(&mut hasher);
733            0usize.hash(&mut hasher);
734            0usize.hash(&mut hasher);
735        }
736        hasher.finish()
737    }
738
739    fn entry_equal_to(one: &Entry, another: &Entry) -> bool {
740        one.value().map_or_else(
741            || {
742                if another.value().is_none() {
743                    one.key().map_or_else(
744                        || another.key().is_none(),
745                        |one_key| {
746                            another
747                                .key()
748                                .is_some_and(|another_key| one_key.equal_to(another_key))
749                        },
750                    )
751                } else {
752                    false
753                }
754            },
755            |one_value| {
756                another.value().is_some_and(|another_value| {
757                    let Some(one_section) = one_value.downcast_ref::<Section>() else {
758                        unreachable!("one.value() must be Section.");
759                    };
760                    let Some(another_section) = another_value.downcast_ref::<Section>() else {
761                        unreachable!("another.value() must be Section.");
762                    };
763                    one.key().map_or_else(
764                        || another.key().is_none(),
765                        |one_key| {
766                            another
767                                .key()
768                                .is_some_and(|another_key| one_key.equal_to(another_key))
769                        },
770                    ) && one_section.train().number() == another_section.train().number()
771                        && one_section.train().name() == another_section.train().name()
772                        && one_section.from() == another_section.from()
773                        && one_section.to() == another_section.to()
774                })
775            },
776        )
777    }
778}