From b09935a9592efce9f841e4e8c903dd3af1f93fae Mon Sep 17 00:00:00 2001 From: Kerry M-R Date: Wed, 1 Oct 2025 19:31:32 +0930 Subject: [PATCH 1/4] Add a basic Bag data structure --- DataStructures/Bag.cs | 75 +++++++++++++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 76 insertions(+) create mode 100644 DataStructures/Bag.cs diff --git a/DataStructures/Bag.cs b/DataStructures/Bag.cs new file mode 100644 index 00000000..1ac46462 --- /dev/null +++ b/DataStructures/Bag.cs @@ -0,0 +1,75 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace DataStructures; + +/// +/// Implementation of a Bag data structure using a hashmap that allows adding items and iterating through them. +/// Items with the same value are stored as a single entry with a count. +/// +/// Generic Type. +public class Bag : IEnumerable where T : notnull +{ + private readonly Dictionary items; + + /// + /// Initializes a new instance of the class. + /// + public Bag() + { + items = []; + } + + /// + /// Adds an item to the bag. + /// + public void Add(T item) + { + if (items.TryGetValue(item, out var count)) + { + items[item] = count + 1; + } + else + { + items[item] = 1; + } + } + + /// + /// Clears the bag. + /// + public void Clear() => items.Clear(); + + /// + /// Gets the number of items in the bag. + /// + public int Count => items.Values.Sum(); + + /// + /// Returns a boolean indicating whether the bag is empty. + /// + public bool IsEmpty() => items.Count == 0; + + /// + /// Returns an enumerator that iterates through the bag. + /// + public IEnumerator GetEnumerator() + { + foreach (var pair in items) + { + for (var i = 0; i < pair.Value; i++) + { + yield return pair.Key; + } + } + } + + /// + /// Returns an enumerator that iterates through the bag. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} diff --git a/README.md b/README.md index ff928316..4758fcb6 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.cs) * [Bit Array](./DataStructures/BitArray.cs) * [Timeline](./DataStructures/Timeline.cs) * [Segment Trees](./DataStructures/SegmentTrees) From c720f4de71df7937f47ebefacfce8ad2580c8c02 Mon Sep 17 00:00:00 2001 From: Kerry M-R Date: Wed, 1 Oct 2025 21:45:31 +0930 Subject: [PATCH 2/4] Move bag to implement it's own linked list base Updated bag documentation Add tests for bag --- DataStructures.Tests/BagTests.cs | 125 +++++++++++++++++++++++++++++++ DataStructures/Bag.cs | 75 ------------------- DataStructures/Bag/Bag.cs | 118 +++++++++++++++++++++++++++++ DataStructures/Bag/BagNode.cs | 14 ++++ README.md | 2 +- 5 files changed, 258 insertions(+), 76 deletions(-) create mode 100644 DataStructures.Tests/BagTests.cs delete mode 100644 DataStructures/Bag.cs create mode 100644 DataStructures/Bag/Bag.cs create mode 100644 DataStructures/Bag/BagNode.cs diff --git a/DataStructures.Tests/BagTests.cs b/DataStructures.Tests/BagTests.cs new file mode 100644 index 00000000..56e008dc --- /dev/null +++ b/DataStructures.Tests/BagTests.cs @@ -0,0 +1,125 @@ +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); + } +} diff --git a/DataStructures/Bag.cs b/DataStructures/Bag.cs deleted file mode 100644 index 1ac46462..00000000 --- a/DataStructures/Bag.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace DataStructures; - -/// -/// Implementation of a Bag data structure using a hashmap that allows adding items and iterating through them. -/// Items with the same value are stored as a single entry with a count. -/// -/// Generic Type. -public class Bag : IEnumerable where T : notnull -{ - private readonly Dictionary items; - - /// - /// Initializes a new instance of the class. - /// - public Bag() - { - items = []; - } - - /// - /// Adds an item to the bag. - /// - public void Add(T item) - { - if (items.TryGetValue(item, out var count)) - { - items[item] = count + 1; - } - else - { - items[item] = 1; - } - } - - /// - /// Clears the bag. - /// - public void Clear() => items.Clear(); - - /// - /// Gets the number of items in the bag. - /// - public int Count => items.Values.Sum(); - - /// - /// Returns a boolean indicating whether the bag is empty. - /// - public bool IsEmpty() => items.Count == 0; - - /// - /// Returns an enumerator that iterates through the bag. - /// - public IEnumerator GetEnumerator() - { - foreach (var pair in items) - { - for (var i = 0; i < pair.Value; i++) - { - yield return pair.Key; - } - } - } - - /// - /// Returns an enumerator that iterates through the bag. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } -} diff --git a/DataStructures/Bag/Bag.cs b/DataStructures/Bag/Bag.cs new file mode 100644 index 00000000..cfb0bc0d --- /dev/null +++ b/DataStructures/Bag/Bag.cs @@ -0,0 +1,118 @@ +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; + } + + // Item not found, add it + if (previous != null) + { + 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; + + /// + /// Gets the total number of items in the bag, including all multiplicities. + /// + public int TotalCount => 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() + { + return GetEnumerator(); + } +} diff --git a/DataStructures/Bag/BagNode.cs b/DataStructures/Bag/BagNode.cs new file mode 100644 index 00000000..b1f21b5d --- /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; } = null; +} diff --git a/README.md b/README.md index 4758fcb6..cb1358d0 100644 --- a/README.md +++ b/README.md @@ -247,7 +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.cs) + * [Bag](./DataStructures/Bag) * [Bit Array](./DataStructures/BitArray.cs) * [Timeline](./DataStructures/Timeline.cs) * [Segment Trees](./DataStructures/SegmentTrees) From e13150528d6d650637d2f6d5be425d76ce33ef88 Mon Sep 17 00:00:00 2001 From: Kerry M-R Date: Thu, 2 Oct 2025 08:18:33 +0930 Subject: [PATCH 3/4] Remove redundant totalcount Adjust null handling for previous in Bag.Add function --- DataStructures/Bag/Bag.cs | 13 ++----------- DataStructures/Bag/BagNode.cs | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/DataStructures/Bag/Bag.cs b/DataStructures/Bag/Bag.cs index cfb0bc0d..0e5774b2 100644 --- a/DataStructures/Bag/Bag.cs +++ b/DataStructures/Bag/Bag.cs @@ -57,12 +57,8 @@ public void Add(T item) current = current.Next; } - // Item not found, add it - if (previous != null) - { - previous.Next = new BagNode(item); - totalCount++; - } + previous!.Next = new BagNode(item); + totalCount++; } /// @@ -79,11 +75,6 @@ public void Clear() /// public int Count => totalCount; - /// - /// Gets the total number of items in the bag, including all multiplicities. - /// - public int TotalCount => totalCount; - /// /// Returns a boolean indicating whether the bag is empty. /// diff --git a/DataStructures/Bag/BagNode.cs b/DataStructures/Bag/BagNode.cs index b1f21b5d..e803d7ea 100644 --- a/DataStructures/Bag/BagNode.cs +++ b/DataStructures/Bag/BagNode.cs @@ -10,5 +10,5 @@ public class BagNode(T item) public int Multiplicity { get; set; } = 1; - public BagNode? Next { get; set; } = null; + public BagNode? Next { get; set; } } From c6d0a8bd579b70b8102a2dcc0fa7b460e9917387 Mon Sep 17 00:00:00 2001 From: Kerry M-R Date: Thu, 2 Oct 2025 11:36:32 +0930 Subject: [PATCH 4/4] Add test for generic IEnumerable.GetEnumerator() --- DataStructures.Tests/BagTests.cs | 28 ++++++++++++++++++++++++++++ DataStructures/Bag/Bag.cs | 5 +---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/DataStructures.Tests/BagTests.cs b/DataStructures.Tests/BagTests.cs index 56e008dc..3204cfc4 100644 --- a/DataStructures.Tests/BagTests.cs +++ b/DataStructures.Tests/BagTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using DataStructures.Bag; using FluentAssertions; @@ -122,4 +123,31 @@ public void Count_ShouldReturnCorrectCount() // 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 index 0e5774b2..125ddc6f 100644 --- a/DataStructures/Bag/Bag.cs +++ b/DataStructures/Bag/Bag.cs @@ -102,8 +102,5 @@ public IEnumerator GetEnumerator() /// /// Returns an enumerator that iterates through the bag. /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }