diff --git a/DataStructures.Tests/BagTests.cs b/DataStructures.Tests/BagTests.cs new file mode 100644 index 00000000..3204cfc4 --- /dev/null +++ b/DataStructures.Tests/BagTests.cs @@ -0,0 +1,153 @@ +using System.Collections.Generic; +using System.Linq; +using DataStructures.Bag; +using FluentAssertions; +using NUnit.Framework; + +namespace DataStructures.Tests; + +internal class BagTests +{ + [Test] + public void Add_ShouldIncreaseCount() + { + // Arrange & Act + var bag = new Bag + { + 1, + 2, + 1 + }; + + // Assert + bag.Count.Should().Be(3); + } + + [Test] + public void Add_ShouldHandleDuplicates() + { + // Arrange & Act + var bag = new Bag + { + "apple", + "apple" + }; + + // Assert + bag.Count.Should().Be(2); + bag.Should().Contain("apple"); + } + + [Test] + public void Clear_ShouldEmptyTheBag() + { + // Arrange + var bag = new Bag + { + 1, + 2 + }; + + // Act + bag.Clear(); + + // Assert + bag.IsEmpty().Should().BeTrue(); + bag.Count.Should().Be(0); + } + + [Test] + public void IsEmpty_ShouldReturnTrueForEmptyBag() + { + // Arrange + var bag = new Bag(); + + // Act & Assert + bag.IsEmpty().Should().BeTrue(); + } + + [Test] + public void IsEmpty_ShouldReturnFalseForNonEmptyBag() + { + // Arrange + var bag = new Bag + { + 1 + }; + + // Act & Assert + bag.IsEmpty().Should().BeFalse(); + } + + [Test] + public void GetEnumerator_ShouldIterateAllItems() + { + // Arrange + var bag = new Bag + { + 1, + 2, + 1 + }; + + // Act + var items = bag.ToList(); + + // Assert + items.Count.Should().Be(3); + items.Should().Contain(1); + items.Should().Contain(2); + } + + [Test] + public void Count_ShouldReturnZeroForEmptyBag() + { + // Arrange + var bag = new Bag(); + + // Act & Assert + bag.Count.Should().Be(0); + } + + [Test] + public void Count_ShouldReturnCorrectCount() + { + // Arrange + var bag = new Bag + { + 1, + 2, + 1 + }; + + // Act & Assert + bag.Count.Should().Be(3); + } + + [Test] + public void IEnumerableGetEnumerator_YieldsAllItemsWithCorrectMultiplicity() + { + // Arrange + var bag = new Bag + { + "apple", + "banana", + "apple" + }; + var genericBag = bag as System.Collections.IEnumerable; + + // Act + var enumerator = genericBag.GetEnumerator(); + var items = new List(); + while (enumerator.MoveNext()) + { + items.Add(enumerator.Current!); + } + + // Assert + items.Count(i => (string)i == "apple").Should().Be(2); + items.Count(i => (string)i == "banana").Should().Be(1); + items.Count.Should().Be(3); + items.Should().BeEquivalentTo(["apple", "apple", "banana"]); + } +} diff --git a/DataStructures/Bag/Bag.cs b/DataStructures/Bag/Bag.cs new file mode 100644 index 00000000..125ddc6f --- /dev/null +++ b/DataStructures/Bag/Bag.cs @@ -0,0 +1,106 @@ +using System.Collections; +using System.Collections.Generic; + +namespace DataStructures.Bag; + +/// +/// Implementation of a Bag (or multiset) data structure using a basic linked list. +/// +/// +/// A bag (or multiset, or mset) is a modification of the concept of a set that, unlike a set, allows for multiple instances for each of its elements. +/// The number of instances given for each element is called the multiplicity of that element in the multiset. +/// As a consequence, an infinite number of multisets exist that contain only elements a and b, but vary in the multiplicities of their elements. +/// See https://en.wikipedia.org/wiki/Multiset for more information. +/// +/// Generic Type. +public class Bag : IEnumerable where T : notnull +{ + private BagNode? head; + private int totalCount; + + /// + /// Initializes a new instance of the class. + /// + public Bag() + { + head = null; + totalCount = 0; + } + + /// + /// Adds an item to the bag. If the item already exists, increases its multiplicity. + /// + public void Add(T item) + { + // If the bag is empty, create the first node + if (head == null) + { + head = new BagNode(item); + totalCount = 1; + return; + } + + // Check if item already exists + var current = head; + BagNode? previous = null; + + while (current != null) + { + if (EqualityComparer.Default.Equals(current.Item, item)) + { + current.Multiplicity++; + totalCount++; + return; + } + + previous = current; + current = current.Next; + } + + previous!.Next = new BagNode(item); + totalCount++; + } + + /// + /// Clears the bag. + /// + public void Clear() + { + head = null; + totalCount = 0; + } + + /// + /// Gets the number of items in the bag. + /// + public int Count => totalCount; + + /// + /// Returns a boolean indicating whether the bag is empty. + /// + public bool IsEmpty() => head == null; + + /// + /// Returns an enumerator that iterates through the bag. + /// + public IEnumerator GetEnumerator() + { + var current = head; + + while (current != null) + { + // Yield the item as many times as its multiplicity, pretending they are separate items + for (var i = 0; i < current.Multiplicity; i++) + { + yield return current.Item; + } + + current = current.Next; + } + } + + /// + /// Returns an enumerator that iterates through the bag. + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/DataStructures/Bag/BagNode.cs b/DataStructures/Bag/BagNode.cs new file mode 100644 index 00000000..e803d7ea --- /dev/null +++ b/DataStructures/Bag/BagNode.cs @@ -0,0 +1,14 @@ +namespace DataStructures.Bag; + +/// +/// Generic node class for Bag. +/// +/// A type for node. +public class BagNode(T item) +{ + public T Item { get; } = item; + + public int Multiplicity { get; set; } = 1; + + public BagNode? Next { get; set; } +} diff --git a/README.md b/README.md index ff928316..cb1358d0 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,7 @@ find more than one implementation for the same objective but using different alg * [Levenshtein Distance](./Algorithms/Problems/DynamicProgramming/LevenshteinDistance/LevenshteinDistance.cs) * [Data Structures](./DataStructures) + * [Bag](./DataStructures/Bag) * [Bit Array](./DataStructures/BitArray.cs) * [Timeline](./DataStructures/Timeline.cs) * [Segment Trees](./DataStructures/SegmentTrees)