namespace Marathons;
public sealed class Marathon
{
private Node? _head;
private Node? _tail;
public readonly string City;
public readonly DateOnly Date;
public int ParticipantCount { get; private set; }
///
/// The constructor for the Marathon class
///
/// The city
/// The Date of the Marathon
public Marathon(string city, DateOnly date)
{
City = city;
Date = date;
}
///
/// Adds a participant to the list
///
/// The participant
public void AddParticipant(Participant participant)
{
int insertIndex = GetIndex(participant, out bool exactPosFound);
if (exactPosFound)
{
return;
}
ParticipantCount++;
Node middleNode = new Node(participant);
Node? leftNode = GetNodeByIndex(insertIndex - 1);
Node? rightNode = GetNodeByIndex(insertIndex);
if (leftNode == null && rightNode == null)
{
_head = middleNode;
_tail = middleNode;
return;
}
// Checking all the possibilities
if(leftNode == null && rightNode != null)
{
_head = middleNode;
MoveNode(middleNode, rightNode);
return;
}
if(rightNode == null && leftNode != null)
{
_tail = middleNode;
MoveNode(leftNode, middleNode);
return;
}
if(rightNode == null || leftNode == null)
{
return;
}
if (insertIndex == 0)
{
MoveNode(middleNode, rightNode);
_head = middleNode;
return;
}
MoveNode(leftNode, rightNode, middleNode);
}
///
/// Removes a participant from the list
///
/// The start number of the participant to remove
/// true: if the operation succeeded; false: if it failed
public bool RemoveParticipant(int startNo)
{
Node? nodeToRemove = GetNodeByStartNo(startNo);
if (nodeToRemove == null)
{
return false;
}
ParticipantCount--;
Node? leftNode = GetNodeByIndex(GetIndex(nodeToRemove.Data!, out _) - 1);
// some explanation here |↑ node of → |↑ index of → |↑ Participant
Node? rightNode = nodeToRemove.Next;
// If nodeToRemove is the first node
if (leftNode == null)
{
_head = rightNode;
return true;
}
// If nodeToRemove is the last node
if (rightNode == null)
{
_tail = leftNode;
return true;
}
MoveNode(leftNode, rightNode);
return true;
}
///
/// The list of participants in the marathon
///
/// the array string of the list of participants
public string[] GetResultList()
{
if(ParticipantCount == 0)
{
return [];
}
string[] result = new string[ParticipantCount];
for (int i = 0; i < ParticipantCount; i++)
{
Node? current = GetNodeByIndex(i);
if (current == null)
{
return [];
}
result[i] = $"#{i+1:00} {current.Data}";
}
return result;
}
///
/// Returns the string representation of the Marathon
///
/// the string representation
public override string ToString()
{
return $"{City} marathon on {Date.ToString(Const.Culture)}";
}
///
/// Returns the index of the participant in the list
/// It's a merged method that can be used to determine the index of a participant,
/// or the index where the participant can be placed
/// Should be efficient for VERY large lists
///
/// the participant you want to check
/// true: found exact location; false: no exact location/doesn't exist
/// The index where the participant is or might be
private int GetIndex(Participant participant, out bool exactPosFound)
{
//check if the list is empty
if (_head is null || _tail is null)
{
exactPosFound = false;
return 0;
}
//check if the participant is the first or last
int result = _head.Data?.CompareTo(participant) ?? 0;
// If the participant is the first or can be placed before the first
if (result >= 0)
{
exactPosFound = result == 0;
return 0;
}
// If the participant is the last or can be placed after the last
result = _tail.Data?.CompareTo(participant) ?? 0;
if (result <= 0)
{
exactPosFound = result == 0;
return ParticipantCount;
}
// Half the list and check both sides
int left = 0;
int right = ParticipantCount;
while (left < right)
{
int middle = (left + right) / 2;
Node? current = _head;
for (int i = 0; i < middle; i++)
{
current = current?.Next;
}
result = current?.Data?.CompareTo(participant) ?? 0;
if (result == 0)
{
exactPosFound = true;
return middle;
}
if (result < 0)
{
left = middle + 1;
}
else
{
right = middle;
}
}
// If not in the list, return the index where it should be
exactPosFound = false;
return left;
}
///
/// Makes the connection to the specified node
/// A -> B -> C
/// If no middle node specified, then it will be A -> C
///
/// The starting Node, or A node
/// The targetNode, or the B node if no middle node specified, else C node
/// The middle node or the B node
private void MoveNode(Node startingNode, Node targetNode, Node? middleNode = null)
{
if(middleNode is null)
{
startingNode.Next = targetNode;
return;
}
if(startingNode == targetNode)
{
_tail = middleNode;
}
else middleNode.Next = targetNode;
startingNode.Next = middleNode;
}
///
/// Gets the node by the index
///
/// The index to return
/// the found node
Node? GetNodeByIndex(int index)
{
if(index <= 0)
{
return _head;
}
if(index >= ParticipantCount)
{
return _tail;
}
Node? current = _head;
for (int i = 0; i < index; i++)
{
current = current?.Next;
}
return current;
}
///
/// Gets the node by the start number
///
/// The start number
/// the found node
Node? GetNodeByStartNo(int startNo)
{
Node? current = _head;
for (int i = 0; i < ParticipantCount; i++)
{
if (current == null)
{
return null;
}
if (current.Data?.StartNo == startNo)
{
return current;
}
current = current.Next;
}
return null;
}
}