Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions Timetable/Line.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics;
using LanguageExt;

namespace Timetable;

Expand All @@ -14,25 +15,25 @@ public partial record Line
/// </summary>
public required string Name { get; init; }

private readonly Route[] _routes = null!; // Will be set by *required* init-er below.
private readonly Arr<Route> _routes = null!; // Will be set by *required* init-er below.

/// <summary>
/// All <see cref="Line.Route"/>s assigned to this <see cref="Line"/>.
/// </summary>
public required Route[] Routes
public required IReadOnlyList<Route> Routes
{
get => _routes;
init
{
_routes = value;
foreach (var route in Routes)
foreach (var route in value)
{
route.Line = this;
// Validate that stop distances length matches route length.
Debug.Assert(
route.TimeProfiles.All(profile => profile.StopDistances.Length == route.StopPositions.Length - 1),
$"For {this.Name}, {route.ToString()}, at least one time profile has an incorrect stop distance count.");
}
_routes = Arr.createRange(value);
}
}

Expand Down Expand Up @@ -82,10 +83,16 @@ public required Route[] Routes
public IEnumerable<Trip> TripsOfRouteIndex(Index routeIndex) =>
Trips.Where(trip => trip.Route == Routes[routeIndex]);

private readonly Arr<TripCreate> _tripsCreate = null!; // Will be set by *required* init-er below.

/// <summary>
/// All <see cref="TripCreate"/>s used to specify which <see cref="Line.Trip"/>s exist for this <see cref="Line"/>.
/// </summary>
public required ICollection<TripCreate> TripsCreate { get; init; }
public required IReadOnlyList<TripCreate> TripsCreate
{
get => _tripsCreate;
init => _tripsCreate = Arr.createRange(value);
}

/// <summary>
/// Which medium of transportation is the one used by this <see cref="Line"/>.
Expand All @@ -102,10 +109,16 @@ public IEnumerable<Trip> TripsOfRouteIndex(Index routeIndex) =>
/// </summary>
public IEnumerable<Route> MainRoutes => MainRouteIndices.Select(index => Routes[index]);

private readonly Arr<Index> _mainRouteIndices = null!; // Will be set by *required* init-er below.

/// <summary>
/// Specifies the indices of the <see cref="Line.Route"/>s that are considers <see cref="MainRoutes"/>.
/// Specifies the indices of the <see cref="Line.Route"/>s that are considered <see cref="MainRoutes"/>.
/// </summary>
public required Index[] MainRouteIndices { get; init; }
public required IReadOnlyList<Index> MainRouteIndices
{
get => _mainRouteIndices;
init => _mainRouteIndices = Arr.createRange(value);
}

/// <summary>
/// <see cref="Line.Route"/>s that are considered to be representative <see cref="Line.Route"/>s for this <see cref="Line"/>.
Expand All @@ -115,15 +128,28 @@ public IEnumerable<Trip> TripsOfRouteIndex(Index routeIndex) =>
/// </summary>
public IEnumerable<Route> OverviewRoutes => OverviewRouteIndices.Select(index => Routes[index]);

private readonly Arr<Index> _overviewRouteIndices = null!; // Will be set by *required* init-er below.

/// <summary>
/// Specifies the indices of the <see cref="Line.Route"/>s that are considers <see cref="OverviewRoutes"/>.
/// </summary>
public required Index[] OverviewRouteIndices { get; init; }
public required IReadOnlyList<Index> OverviewRouteIndices
{
get => _overviewRouteIndices;
init => _overviewRouteIndices = Arr.createRange(value);
}

private readonly HashMap<string, string> _annotations = HashMap<string, string>.Empty;

/// <summary>
/// Manual annotations, indexed by their symbol, mapping to their text.
/// </summary>
public Dictionary<string, string> Annotations { get; init; } = new();
// TODO: Find a better way here, potentially with a native value equality dictionary.
public IReadOnlyDictionary<string, string> Annotations
{
get => _annotations.ToReadOnlyDictionary();
init => _annotations = HashMap.createRange(value);
}

/// <summary>
/// The typical time of day where this <see cref="Line"/> operates.
Expand Down
44 changes: 38 additions & 6 deletions Timetable/Route.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
using System.Diagnostics.Contracts;
using System.Numerics;
using System.Runtime.CompilerServices;
using LanguageExt;

namespace Timetable;

public partial record Line
Expand All @@ -15,15 +20,27 @@ public partial record Route

internal Line? Line { get; set; }

private readonly Arr<Stop.Position> _stopPositions = null!; // Will be set by *required* init-er below.

/// <summary>
/// The <see cref="Stop.Position"/>s where this route calls at.
/// </summary>
public required Stop.Position[] StopPositions { get; init; }
public required IReadOnlyList<Stop.Position> StopPositions
{
get => _stopPositions;
init => _stopPositions = Arr.createRange(value);
}

private readonly Arr<TimeProfile> _timeProfiles = null!; // Will be set by *required* init-er below.

/// <summary>
/// All the existing timing patterns for this route.
/// </summary>
public required TimeProfile[] TimeProfiles { get; init; }
public required IReadOnlyList<TimeProfile> TimeProfiles
{
get => _timeProfiles;
init => _timeProfiles = Arr.createRange(value);
}

/// <summary>
/// A text to be displayed alongside this route.
Expand Down Expand Up @@ -93,7 +110,7 @@ public bool TryGetIndexOfStopFirst(Stop stop, out int index)
/// <param name="stop">The <see cref="Stop"/> to check for.</param>
/// <param name="onlyDepartures">Only consider departures, i.e. the last stop of this route is ignored.</param>
public bool DoesStopAt(Stop stop, bool onlyDepartures) => StopPositions
.Take(StopPositions.Length - (onlyDepartures
.Take(StopPositions.Count - (onlyDepartures
? /* when we only want departures, the last stop is no longer relevant */ 1
: 0)).Select(pos => pos.Stop).Contains(stop);

Expand Down Expand Up @@ -168,11 +185,16 @@ public Route WithoutStop(Stop stopToExclude) => StopPositions.Any(pos => pos.Sto
/// if those two stations exist and are next to each other.
/// </summary>
/// <returns></returns>
public Route WithStopBetween(Stop before, Stop.Position inserted, Stop after, TimeSpan firstTime, TimeSpan secondTime)
public Route WithStopBetween(Stop before, Stop.Position inserted, Stop after, TimeSpan firstTime,
TimeSpan secondTime)
{
var beforeIndex = Array.IndexOf(StopPositions, before);
List<int> list = [1, 2, 3];
IReadOnlyList<int> d = list;
var x = list[1..];
Console.WriteLine(x.Count);
var beforeIndex = StopPositions.IndexOf(before);
if (beforeIndex == -1) return this;
var afterIndex = Array.IndexOf(StopPositions, after);
var afterIndex = StopPositions.IndexOf(after);
if (afterIndex == -1) return this;
if (afterIndex != beforeIndex + 1) return this;
Stop.Position[] insertedPositions =
Expand Down Expand Up @@ -206,3 +228,13 @@ public Route WithStopBetween(Stop before, Stop.Position inserted, Stop after, Ti
.Select(trip => int.PopCount((int)(trip.DaysOfOperation & days))).Sum();
}
}

file static class CollectionExtensions
{
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOf<T>(this IReadOnlyList<T> list, T element) where T : IEqualityOperators<T, T, bool> =>
list is Arr<T> arr
? arr.IndexOf(element)
: list.Index().Where(tuple => tuple.Item == element).Select(tuple => tuple.Index).FirstOrDefault(-1);
}
20 changes: 16 additions & 4 deletions Timetable/Stop.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using R4Utils.ValueEqualityCollections;

namespace Timetable;

/// <summary>
Expand All @@ -19,10 +21,19 @@ public Stop()
/// </summary>
public required string InitialName { private get; init; }

private readonly ValueEqualityCollection<(DateOnly Date, string Name), List<(DateOnly Date, string Name)>>
_nameChanges = null!; // Will be set by *required* init-er below.

/// <summary>
/// Maps the <see cref="DateOnly"/> of a change of name of this <see cref="Stop"/> to its new name.
/// </summary>
public List<(DateOnly Date, string Name)> NameChanges { get; init; } = [];
public List<(DateOnly Date, string Name)> NameChanges
{
get => _nameChanges.Underlying;
init => _nameChanges =
value.AsGenericOrderedValueEqualityCollection<(DateOnly Date, string Name),
List<(DateOnly Date, string Name)>>();
}

/// <summary>
/// The <see cref="City"/> of this <see cref="Stop"/>.
Expand All @@ -49,18 +60,19 @@ public string NameAt(DateOnly date) => NameChanges.Count == 0 || NameChanges[0].
private string DisplayNameFor(string name, City referenceCity) =>
referenceCity == City ? name : $"{City.Name}, {name}";

private readonly Position[] _positions = null!; // Will be set below.
private readonly ValueEqualityCollection<Position, Position[]> _positions =
Array.Empty<Position>().AsGenericOrderedValueEqualityCollection<Position, Position[]>();

/// <summary>
/// All <see cref="Position"/>s of this <see cref="Stop"/>.
/// </summary>
/// <remarks>Defaults to a single-element array if omitted.</remarks>
public /*required*/ Position[] Positions
{
get => _positions;
get => _positions.Underlying;
init
{
_positions = value;
_positions = value.AsGenericOrderedValueEqualityCollection<Position, Position[]>();
foreach (var position in _positions)
{
if (position.Stop is not null)
Expand Down
11 changes: 10 additions & 1 deletion Timetable/TimeProfile.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using R4Utils.ValueEqualityCollections;

namespace Timetable;

public partial record Line
Expand All @@ -11,10 +13,17 @@ public partial record Route
/// </summary>
public record TimeProfile
{
private readonly ValueEqualityCollection<TimeSpan, TimeSpan[]>
_stopDistances = null!; // Will be set by *required* init-er below.

/// <summary>
/// At index <c>i</c> there is the time it takes to travel from stop position <c>i</c> to <c>i+1</c>.
/// </summary>
public required TimeSpan[] StopDistances { get; init; }
public required TimeSpan[] StopDistances
{
get => _stopDistances.Underlying;
init => _stopDistances = value.AsGenericOrderedValueEqualityCollection<TimeSpan, TimeSpan[]>();
}

/// <summary>
/// Get the time it takes to travel from stop index <paramref name="fromIndex"/> to <paramref name="toIndex"/>.
Expand Down
4 changes: 4 additions & 0 deletions Timetable/Timetable.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@
<DocumentationFile>bin\Release\net8.0\Timetable.xml</DocumentationFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LanguageExt.Core" Version="4.4.9" />
</ItemGroup>

</Project>
22 changes: 18 additions & 4 deletions Timetable/Trip.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using LanguageExt;

namespace Timetable;

public partial record Line
Expand Down Expand Up @@ -32,25 +34,37 @@ public partial record Trip
/// </summary>
public required DaysOfOperation DaysOfOperation { get; init; }

private readonly Arr<ManualAnnotation> _annotations = null!; // Will be set by *required* init-er below.

/// <summary>
/// All manually (in the timetable) specified <see cref="ManualAnnotation"/> for this <see cref="Trip"/>.
/// </summary>
public required List<ManualAnnotation> Annotations { get; init; }
public required IReadOnlyList<ManualAnnotation> Annotations
{
get => _annotations;
init => _annotations = Arr.createRange(value);
}

private readonly Arr<TripCreate.Connection> _connections = null!; // Will be set by *required* init-er below.

/// <summary>
/// All through services this <see cref="Trip"/> participates in.
/// <br/><br/>
/// This should have 0 (no through service), 1 (start or end of through service), or 2 (middle of a through service) elements.
/// </summary>
public required List<TripCreate.Connection> Connections { private get; init; }
public required IReadOnlyList<TripCreate.Connection> Connections
{
private get => _connections;
init => _connections = Arr.createRange(value);
}

/// <summary>
/// Translate the trip-create <see cref="Timetable.Line.TripCreate.Connection"/>s present on this <see cref="Trip"/> into trip <see cref="Connection"/>s.
/// </summary>
/// <param name="allLines">All <see cref="Line"/>s present in the current network, indexed by their id.</param>
// Consider adding more validation steps if it becomes a problem.
public IEnumerable<Connection> GetConnections(IReadOnlyDictionary<string, Line> allLines) => Connections.Select(
connection => new Connection
public IEnumerable<Connection> GetConnections(IReadOnlyDictionary<string, Line> allLines) =>
Connections.Select(connection => new Connection
{
Type = connection.Type,
NotableViaStop = connection.NotableViaStop,
Expand Down
20 changes: 17 additions & 3 deletions Timetable/TripCreate.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using LanguageExt;

namespace Timetable;

public partial record Line
Expand Down Expand Up @@ -42,20 +44,32 @@ public TripCreate()
[Obsolete($"Use {nameof(AnnotationSymbols)} instead.", false)]
public string AnnotationSymbol
{
init => AnnotationSymbols.Add(value);
init => _annotationSymbols = Arr.create(value);
}

private readonly Arr<string> _annotationSymbols = Arr.empty<string>();

/// <summary>
/// The symbols (defined as the keys of <see cref="Line.Annotations"/>) of the annotations this trip has.
/// </summary>
public List<string> AnnotationSymbols { get; init; } = [];
public IReadOnlyList<string> AnnotationSymbols
{
get => _annotationSymbols;
init => _annotationSymbols = Arr.createRange(value);
}

private readonly Arr<Connection> _connections = Arr.empty<Connection>();

/// <summary>
/// All through services this trip participates in.
/// <br/><br/>
/// This should have 0 (no through service), 1 (start or end of through service), or 2 (middle of a through service) elements.
/// </summary>
public List<Connection> Connections { get; init; } = [];
public IReadOnlyList<Connection> Connections
{
get => _connections;
init => _connections = Arr.createRange(value);
}

/// <summary>
/// Repeat this <see cref="TripCreate"/> instance for every <paramref name="interval"/>.
Expand Down
33 changes: 33 additions & 0 deletions TimetableConsole/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*

using System.Diagnostics;
using Timetable;

var sw = new Stopwatch();
sw.Start();
var history = VipTimetable.VipHistory.History;
var entry = history[0];
var lines = entry.OrderedLinesById.ToArray();
var line = lines[0].Value;
var routes = line.Routes;
var route = routes[0];
var trips = line.TripsOfRouteIndex(0).ToArray();
var stopTimetableView = new Timetable.Views.StopTimetableView(lines.ToDictionary(), trips,
[DaysOfOperation.Weekday, DaysOfOperation.Weekend], route.StopPositions[0].Stop, new DateOnly(2024, 1, 3));
sw.Stop();
Console.WriteLine($"Got {trips.Length} trips for route {route}.");
Console.WriteLine($"Last stop of route 0: {stopTimetableView.LastStopOfRoute(0)}");
Console.WriteLine($"Took {sw.Elapsed}");
*/

List<int> Test()
{
List<int> list = [1, 2, 3];
IReadOnlyList<int> d = list;
var x = list[1..];
var y = d.Mem
return x;
}


Console.WriteLine(Test().Count);
Loading