From 9505b06ac75e3deda87f08197c62924a6bc72105 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Thu, 26 Dec 2024 12:00:04 +0300 Subject: [PATCH 1/3] add priorityQueue class --- Tasks/Test4/Test4.sln | 22 +++++++ Tasks/Test4/Test4/PriorityQueue.cs | 92 ++++++++++++++++++++++++++++++ Tasks/Test4/Test4/Test4.csproj | 10 ++++ 3 files changed, 124 insertions(+) create mode 100644 Tasks/Test4/Test4.sln create mode 100644 Tasks/Test4/Test4/PriorityQueue.cs create mode 100644 Tasks/Test4/Test4/Test4.csproj diff --git a/Tasks/Test4/Test4.sln b/Tasks/Test4/Test4.sln new file mode 100644 index 0000000..844ee03 --- /dev/null +++ b/Tasks/Test4/Test4.sln @@ -0,0 +1,22 @@ + +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 +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 + EndGlobalSection +EndGlobal diff --git a/Tasks/Test4/Test4/PriorityQueue.cs b/Tasks/Test4/Test4/PriorityQueue.cs new file mode 100644 index 0000000..a5c7ac2 --- /dev/null +++ b/Tasks/Test4/Test4/PriorityQueue.cs @@ -0,0 +1,92 @@ +// 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; + } + + if (current is null) + { + 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); + } + + ArgumentNullException.ThrowIfNull(this.head); + var elementToReturn = this.head; + this.head = elementToReturn.Next; + --this.Size; + return elementToReturn.Value; + } + } + + private (Element?, Element?) GetElementByPriority(TPriority priority) + { + Element? previous = null; + Element? current = this.head; + for (; current?.Next 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 From 0304eb32b7dd156e14c13ac302b575039a32c914 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Thu, 26 Dec 2024 12:48:54 +0300 Subject: [PATCH 2/3] add tests --- Tasks/Test4/Test4.Tests/PriorityQueueTests.cs | 159 ++++++++++++++++++ Tasks/Test4/Test4.Tests/Test4.Tests.csproj | 27 +++ Tasks/Test4/Test4.sln | 6 + Tasks/Test4/Test4/PriorityQueue.cs | 13 +- 4 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 Tasks/Test4/Test4.Tests/PriorityQueueTests.cs create mode 100644 Tasks/Test4/Test4.Tests/Test4.Tests.csproj diff --git a/Tasks/Test4/Test4.Tests/PriorityQueueTests.cs b/Tasks/Test4/Test4.Tests/PriorityQueueTests.cs new file mode 100644 index 0000000..c3de03c --- /dev/null +++ b/Tasks/Test4/Test4.Tests/PriorityQueueTests.cs @@ -0,0 +1,159 @@ +// 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; + 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")); + }); + 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 index 844ee03..6ffdcb0 100644 --- a/Tasks/Test4/Test4.sln +++ b/Tasks/Test4/Test4.sln @@ -5,6 +5,8 @@ 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 @@ -18,5 +20,9 @@ Global {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 index a5c7ac2..59e44c2 100644 --- a/Tasks/Test4/Test4/PriorityQueue.cs +++ b/Tasks/Test4/Test4/PriorityQueue.cs @@ -37,8 +37,7 @@ public void Enqueue(TValue value, TPriority priority) { previous.Next = elementToInsert; } - - if (current is null) + else { this.head = elementToInsert; } @@ -58,14 +57,14 @@ public TValue Dequeue() { while (this.Size == 0) { - Monitor.Wait(this); + Monitor.Wait(this.lockObject); } ArgumentNullException.ThrowIfNull(this.head); - var elementToReturn = this.head; - this.head = elementToReturn.Next; + var valueToReturn = this.head.Value; + this.head = this.head.Next; --this.Size; - return elementToReturn.Value; + return valueToReturn; } } @@ -73,7 +72,7 @@ public TValue Dequeue() { Element? previous = null; Element? current = this.head; - for (; current?.Next is not null && current.Priority.CompareTo(priority) > 0; current = current.Next) + for (; current is not null && current.Priority.CompareTo(priority) >= 0; current = current.Next) { previous = current; } From 5d62179c72449cd492345fe46b44589d2a80d32f Mon Sep 17 00:00:00 2001 From: bygu4 Date: Thu, 26 Dec 2024 12:51:42 +0300 Subject: [PATCH 3/3] correct tests --- Tasks/Test4/Test4.Tests/PriorityQueueTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tasks/Test4/Test4.Tests/PriorityQueueTests.cs b/Tasks/Test4/Test4.Tests/PriorityQueueTests.cs index c3de03c..dabb6de 100644 --- a/Tasks/Test4/Test4.Tests/PriorityQueueTests.cs +++ b/Tasks/Test4/Test4.Tests/PriorityQueueTests.cs @@ -84,7 +84,7 @@ public static void Test_AddElementsFromManyThreads_ElementsAreAddedProperly() var queue = new PriorityQueue(); for (int i = 0; i < numberOfThreads; ++i) { - var localI = i; + var localI = i + 1; threads[i] = new Thread(() => { Thread.Sleep(1000); @@ -120,6 +120,7 @@ public static void Test_EnqueueAndDequeueFromDifferentThreads_ElementsAreObtaine 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();