diff --git a/Tasks/Test4/Test4.Tests/PriorityQueueTests.cs b/Tasks/Test4/Test4.Tests/PriorityQueueTests.cs new file mode 100644 index 0000000..dabb6de --- /dev/null +++ b/Tasks/Test4/Test4.Tests/PriorityQueueTests.cs @@ -0,0 +1,160 @@ +// Copyright (c) Alexander Bugaev 2024 +// +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +namespace Test4.Tests; + +/// +/// Tests for the priority queue implementation. +/// +public static class PriorityQueueTests +{ + /// + /// Tests adding elements to the queue ny checking the queue size. + /// + [Test] + public static void Test_EnqueueAndCheckSize_SizeIsUpdated() + { + var queue = new PriorityQueue(); + Assert.That(queue.Size, Is.EqualTo(0)); + queue.Enqueue(20, 1); + Assert.That(queue.Size, Is.EqualTo(1)); + queue.Enqueue(2, 100); + Assert.That(queue.Size, Is.EqualTo(2)); + } + + /// + /// Tests adding elements to the queue by obtaining max priority elements. + /// + [Test] + public static void Test_EnqueueAndDequeue_ElementsAreObtainedProperly() + { + var queue = new PriorityQueue(); + queue.Enqueue("qwew", 12f); + queue.Enqueue("0", 54f); + queue.Enqueue("ioio", 12f); + queue.Enqueue("abc", 0); + Assert.That(queue.Dequeue(), Is.EqualTo("0")); + Assert.That(queue.Size, Is.EqualTo(3)); + Assert.That(queue.Dequeue(), Is.EqualTo("qwew")); + Assert.That(queue.Dequeue(), Is.EqualTo("ioio")); + Assert.That(queue.Dequeue(), Is.EqualTo("abc")); + Assert.That(queue.Size, Is.EqualTo(0)); + } + + /// + /// Tests the concurrent element adding to the queue. + /// + [Test] + public static void Test_AddElementsConcurrently_ElementsAreAddedProperly() + { + var queue = new PriorityQueue(); + var firstThread = new Thread(() => + { + queue.Enqueue(12, "a"); + queue.Enqueue(990, "c"); + queue.Enqueue(12, "dc"); + }); + var secondThread = new Thread(() => + { + queue.Enqueue(-100, "d"); + queue.Enqueue(0, "b"); + queue.Enqueue(190, "ab"); + }); + firstThread.Start(); + secondThread.Start(); + JoinThreads([firstThread, secondThread]); + Assert.That(queue.Size, Is.EqualTo(6)); + Assert.That(queue.Dequeue(), Is.EqualTo(12)); + Assert.That(queue.Dequeue(), Is.EqualTo(-100)); + Assert.That(queue.Dequeue(), Is.EqualTo(990)); + Assert.That(queue.Size, Is.EqualTo(3)); + } + + /// + /// Tests the concurrent elements adding to the queue. + /// + [Test] + public static void Test_AddElementsFromManyThreads_ElementsAreAddedProperly() + { + var numberOfThreads = 12; + var threads = new Thread[numberOfThreads]; + var queue = new PriorityQueue(); + for (int i = 0; i < numberOfThreads; ++i) + { + var localI = i + 1; + threads[i] = new Thread(() => + { + Thread.Sleep(1000); + queue.Enqueue(localI, localI); + }); + threads[i].Start(); + } + + JoinThreads(threads); + Assert.That(queue.Size, Is.EqualTo(numberOfThreads)); + Assert.That(queue.Dequeue(), Is.EqualTo(numberOfThreads)); + Assert.That(queue.Dequeue(), Is.EqualTo(numberOfThreads - 1)); + Assert.That(queue.Size, Is.EqualTo(numberOfThreads - 2)); + } + + /// + /// Tests the obtaining of elements form the queue in case of adding from other queue. + /// + [Test] + public static void Test_EnqueueAndDequeueFromDifferentThreads_ElementsAreObtainedProperly() + { + var queue = new PriorityQueue(); + var firstThread = new Thread(() => + { + Thread.Sleep(1000); + queue.Enqueue("fas", 231); + queue.Enqueue("2313211", 1); + queue.Enqueue("op", -1); + }); + var secondThread = new Thread(() => + { + Assert.That(queue.Size, Is.EqualTo(0)); + Assert.That(queue.Dequeue(), Is.EqualTo("fas")); + Assert.That(queue.Dequeue(), Is.EqualTo("2313211")); + Assert.That(queue.Dequeue(), Is.EqualTo("op")); + Assert.That(queue.Size, Is.EqualTo(0)); + }); + firstThread.Start(); + secondThread.Start(); + JoinThreads([firstThread, secondThread]); + } + + /// + /// Tests the concurrent element obtaining from the queue. + /// + [Test] + public static void Test_DequeueConcurrentlyFromManyThreads_ElementsAreObtainedProperly() + { + var numberOfThreads = 10; + var threads = new Thread[numberOfThreads]; + var queue = new PriorityQueue(); + for (int i = 0; i < numberOfThreads; ++i) + { + queue.Enqueue(42, 42); + threads[i] = new Thread(() => + { + Thread.Sleep(1000); + Assert.That(queue.Dequeue(), Is.EqualTo(42)); + }); + threads[i].Start(); + } + + JoinThreads(threads); + } + + private static void JoinThreads(IEnumerable threads) + { + foreach (var thread in threads) + { + thread.Join(); + } + } +} diff --git a/Tasks/Test4/Test4.Tests/Test4.Tests.csproj b/Tasks/Test4/Test4.Tests/Test4.Tests.csproj new file mode 100644 index 0000000..af8a78a --- /dev/null +++ b/Tasks/Test4/Test4.Tests/Test4.Tests.csproj @@ -0,0 +1,27 @@ + + + + net9.0 + latest + enable + enable + false + + + + + + + + + + + + + + + + + + + diff --git a/Tasks/Test4/Test4.sln b/Tasks/Test4/Test4.sln new file mode 100644 index 0000000..6ffdcb0 --- /dev/null +++ b/Tasks/Test4/Test4.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test4", "Test4\Test4.csproj", "{648707F5-317C-4E02-AE4A-40A2BDED9F38}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test4.Tests", "Test4.Tests\Test4.Tests.csproj", "{9C4AA1E4-8D0F-4632-8D78-5FA4EE0F100A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {648707F5-317C-4E02-AE4A-40A2BDED9F38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {648707F5-317C-4E02-AE4A-40A2BDED9F38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {648707F5-317C-4E02-AE4A-40A2BDED9F38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {648707F5-317C-4E02-AE4A-40A2BDED9F38}.Release|Any CPU.Build.0 = Release|Any CPU + {9C4AA1E4-8D0F-4632-8D78-5FA4EE0F100A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C4AA1E4-8D0F-4632-8D78-5FA4EE0F100A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C4AA1E4-8D0F-4632-8D78-5FA4EE0F100A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C4AA1E4-8D0F-4632-8D78-5FA4EE0F100A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Tasks/Test4/Test4/PriorityQueue.cs b/Tasks/Test4/Test4/PriorityQueue.cs new file mode 100644 index 0000000..59e44c2 --- /dev/null +++ b/Tasks/Test4/Test4/PriorityQueue.cs @@ -0,0 +1,91 @@ +// Copyright (c) Alexander Bugaev 2024 +// +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +namespace Test4; + +/// +/// Thread safe queue with priorities. +/// +/// The type of the value stored in the queue. +/// The type of the priorities used in the queue. +public class PriorityQueue + where TPriority : IComparable +{ + private readonly object lockObject = new (); + private Element? head; + + /// + /// Gets the size of the queue at some moment. + /// + public int Size { get; private set; } + + /// + /// Inserts the given element with given priority to the queue. + /// + /// The value to insert. + /// The priority of the element. + public void Enqueue(TValue value, TPriority priority) + { + lock (this.lockObject) + { + var (previous, current) = this.GetElementByPriority(priority); + var elementToInsert = new Element(value, priority, current); + if (previous is not null) + { + previous.Next = elementToInsert; + } + else + { + this.head = elementToInsert; + } + + ++this.Size; + Monitor.Pulse(this.lockObject); + } + } + + /// + /// Gets the element with max priority from the queue. + /// + /// The value with the max priority in the queue. + public TValue Dequeue() + { + lock (this.lockObject) + { + while (this.Size == 0) + { + Monitor.Wait(this.lockObject); + } + + ArgumentNullException.ThrowIfNull(this.head); + var valueToReturn = this.head.Value; + this.head = this.head.Next; + --this.Size; + return valueToReturn; + } + } + + private (Element?, Element?) GetElementByPriority(TPriority priority) + { + Element? previous = null; + Element? current = this.head; + for (; current is not null && current.Priority.CompareTo(priority) >= 0; current = current.Next) + { + previous = current; + } + + return (previous, current); + } + + private class Element(TValue value, TPriority priority, Element? next = null) + { + public TValue Value { get; } = value; + + public TPriority Priority { get; } = priority; + + public Element? Next { get; set; } = next; + } +} diff --git a/Tasks/Test4/Test4/Test4.csproj b/Tasks/Test4/Test4/Test4.csproj new file mode 100644 index 0000000..c7e645b --- /dev/null +++ b/Tasks/Test4/Test4/Test4.csproj @@ -0,0 +1,10 @@ + + + net9.0 + enable + enable + + + + + \ No newline at end of file