281 lines
No EOL
7.9 KiB
C#
281 lines
No EOL
7.9 KiB
C#
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; }
|
|
|
|
/// <summary>
|
|
/// The constructor for the Marathon class
|
|
/// </summary>
|
|
/// <param name="city">The city</param>
|
|
/// <param name="date">The Date of the Marathon</param>
|
|
public Marathon(string city, DateOnly date)
|
|
{
|
|
City = city;
|
|
Date = date;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a participant to the list
|
|
/// </summary>
|
|
/// <param name="participant">The participant</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a participant from the list
|
|
/// </summary>
|
|
/// <param name="startNo">The start number of the participant to remove</param>
|
|
/// <returns>true: if the operation succeeded; false: if it failed</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The list of participants in the marathon
|
|
/// </summary>
|
|
/// <returns>the array string of the list of participants</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the string representation of the Marathon
|
|
/// </summary>
|
|
/// <returns>the string representation</returns>
|
|
public override string ToString()
|
|
{
|
|
return $"{City} marathon on {Date.ToString(Const.Culture)}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
/// <param name="participant">the participant you want to check</param>
|
|
/// <param name="exactPosFound">true: found exact location; false: no exact location/doesn't exist</param>
|
|
/// <returns>The index where the participant is or might be</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Makes the connection to the specified node
|
|
/// A -> B -> C
|
|
/// If no middle node specified, then it will be A -> C
|
|
/// </summary>
|
|
/// <param name="startingNode">The starting Node, or A node</param>
|
|
/// <param name="targetNode">The targetNode, or the B node if no middle node specified, else C node</param>
|
|
/// <param name="middleNode">The middle node or the B node</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the node by the index
|
|
/// </summary>
|
|
/// <param name="index">The index to return</param>
|
|
/// <returns>the found node</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the node by the start number
|
|
/// </summary>
|
|
/// <param name="startNo">The start number</param>
|
|
/// <returns>the found node</returns>
|
|
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;
|
|
}
|
|
} |