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; } }