Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions Tasks/Test4/Test4.Tests/PriorityQueueTests.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Tests for the priority queue implementation.
/// </summary>
public static class PriorityQueueTests
{
/// <summary>
/// Tests adding elements to the queue ny checking the queue size.
/// </summary>
[Test]
public static void Test_EnqueueAndCheckSize_SizeIsUpdated()
{
var queue = new PriorityQueue<int, int>();
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));
}

/// <summary>
/// Tests adding elements to the queue by obtaining max priority elements.
/// </summary>
[Test]
public static void Test_EnqueueAndDequeue_ElementsAreObtainedProperly()
{
var queue = new PriorityQueue<string, float>();
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));
}

/// <summary>
/// Tests the concurrent element adding to the queue.
/// </summary>
[Test]
public static void Test_AddElementsConcurrently_ElementsAreAddedProperly()
{
var queue = new PriorityQueue<int, string>();
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));
}

/// <summary>
/// Tests the concurrent elements adding to the queue.
/// </summary>
[Test]
public static void Test_AddElementsFromManyThreads_ElementsAreAddedProperly()
{
var numberOfThreads = 12;
var threads = new Thread[numberOfThreads];
var queue = new PriorityQueue<int, int>();
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));
}

/// <summary>
/// Tests the obtaining of elements form the queue in case of adding from other queue.
/// </summary>
[Test]
public static void Test_EnqueueAndDequeueFromDifferentThreads_ElementsAreObtainedProperly()
{
var queue = new PriorityQueue<string, int>();
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]);
}

/// <summary>
/// Tests the concurrent element obtaining from the queue.
/// </summary>
[Test]
public static void Test_DequeueConcurrentlyFromManyThreads_ElementsAreObtainedProperly()
{
var numberOfThreads = 10;
var threads = new Thread[numberOfThreads];
var queue = new PriorityQueue<int, int>();
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<Thread> threads)
{
foreach (var thread in threads)
{
thread.Join();
}
}
}
27 changes: 27 additions & 0 deletions Tasks/Test4/Test4.Tests/Test4.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit.Analyzers" Version="4.3.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../Test4/Test4.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>

</Project>
28 changes: 28 additions & 0 deletions Tasks/Test4/Test4.sln
Original file line number Diff line number Diff line change
@@ -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
91 changes: 91 additions & 0 deletions Tasks/Test4/Test4/PriorityQueue.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Thread safe queue with priorities.
/// </summary>
/// <typeparam name="TValue">The type of the value stored in the queue.</typeparam>
/// <typeparam name="TPriority">The type of the priorities used in the queue.</typeparam>
public class PriorityQueue<TValue, TPriority>
where TPriority : IComparable<TPriority>
{
private readonly object lockObject = new ();
private Element? head;

/// <summary>
/// Gets the size of the queue at some moment.
/// </summary>
public int Size { get; private set; }

/// <summary>
/// Inserts the given element with given priority to the queue.
/// </summary>
/// <param name="value">The value to insert.</param>
/// <param name="priority">The priority of the element.</param>
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);
}
}

/// <summary>
/// Gets the element with max priority from the queue.
/// </summary>
/// <returns>The value with the max priority in the queue.</returns>
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;
}
}
10 changes: 10 additions & 0 deletions Tasks/Test4/Test4/Test4.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118"/>
</ItemGroup>
</Project>
Loading