From a839d4f2f74e4584f3e329961650c3bb10060994 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 4 Oct 2026 13:11:24 +0200 Subject: [PATCH 1/3] Remove Duplicates From a List: fix the manual loops, add objects and in-place, net10.0 - Retarget all three projects to net10.0 and bump packages. - Fix UsingIterationsAndShifting() and UsingIterationsAndSwapping(): both assigned to a local variable and never wrote to the list, so { 1, 1, 2, 3, 4, 5 } came back as { 1 }. Both now work on a copy and write back into it. - Tests: a fixture with a duplicate at index 0 and one with duplicates spread through the list, run against every method. WhenUsingDictionary now tests UsingDictionary(). - UsingRecursion() returns the list without duplicates when it reaches the end. - where T : notnull clears CS8714 and CS8602. - Add RemoveDuplicatesInPlace() (RemoveAll with a HashSet), Person, PersonRecord and PeopleHelper (Distinct on a class, on a record, and DistinctBy), with tests. - Benchmark: [Params] for the number of distinct values in a 2,000 item list, method names match the article, and the two methods that change their list start every call from a fresh copy. --- .../RemoveDuplicatesFromLists/PeopleHelper.cs | 19 ++++ .../RemoveDuplicatesFromLists/Person.cs | 8 ++ .../RemoveDuplicatesFromLists/PersonRecord.cs | 3 + .../RemoveDuplicatesFromLists/Program.cs | 30 ++++++- .../RemoveDuplicatesFromLists.csproj | 2 +- .../RemoveDuplicatesHelper.cs | 31 ++++--- .../RemoveDuplicatesFromListsBenchmark.csproj | 4 +- ...emoveDuplicatesFromListsBenchmarkRunner.cs | 81 +++++++++++------ .../Tests/PeopleAndInPlaceTests.cs | 80 +++++++++++++++++ .../RemoveDuplicatesFromListsTests.csproj | 10 +-- .../RemoveDuplicatesFromLists/Tests/Tests.cs | 90 ++++++++++++++++++- 11 files changed, 307 insertions(+), 51 deletions(-) create mode 100644 collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/PeopleHelper.cs create mode 100644 collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/Person.cs create mode 100644 collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/PersonRecord.cs create mode 100644 collections-lists/RemoveDuplicatesFromLists/Tests/PeopleAndInPlaceTests.cs diff --git a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/PeopleHelper.cs b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/PeopleHelper.cs new file mode 100644 index 0000000000..efc4587ba6 --- /dev/null +++ b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/PeopleHelper.cs @@ -0,0 +1,19 @@ +namespace RemoveDuplicatesFromLists; + +public static class PeopleHelper +{ + public static List UsingDistinct(List people) + { + return people.Distinct().ToList(); + } + + public static List UsingDistinct(List people) + { + return people.Distinct().ToList(); + } + + public static List UsingDistinctBy(List people) + { + return people.DistinctBy(p => p.Email).ToList(); + } +} diff --git a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/Person.cs b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/Person.cs new file mode 100644 index 0000000000..b8e8c2970b --- /dev/null +++ b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/Person.cs @@ -0,0 +1,8 @@ +namespace RemoveDuplicatesFromLists; + +public class Person +{ + public required string Name { get; init; } + public required int Age { get; init; } + public required string Email { get; init; } +} diff --git a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/PersonRecord.cs b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/PersonRecord.cs new file mode 100644 index 0000000000..18241cf15c --- /dev/null +++ b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/PersonRecord.cs @@ -0,0 +1,3 @@ +namespace RemoveDuplicatesFromLists; + +public record PersonRecord(string Name, int Age, string Email); diff --git a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/Program.cs b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/Program.cs index cf817d0411..9e1ae4afed 100644 --- a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/Program.cs +++ b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/Program.cs @@ -37,4 +37,32 @@ Console.WriteLine("Output = {0}", string.Join(",", helper.UsingUnion())); Console.WriteLine("\n------Using sorting------"); -Console.WriteLine("Output = {0}", string.Join(",", helper.Sorting())); \ No newline at end of file +Console.WriteLine("Output = {0}", string.Join(",", helper.Sorting())); + +Console.WriteLine("\n------Removing duplicates in place------"); +var sameList = helper.ListWithDuplicates; +helper.RemoveDuplicatesInPlace(); +Console.WriteLine("Output = {0}", string.Join(",", sameList)); + +var people = new List +{ + new() { Name = "Ann", Age = 30, Email = "ann@example.com" }, + new() { Name = "Ann", Age = 30, Email = "ann@example.com" }, + new() { Name = "Bob", Age = 22, Email = "bob@example.com" } +}; + +var records = new List +{ + new("Ann", 30, "ann@example.com"), + new("Ann", 30, "ann@example.com"), + new("Bob", 22, "bob@example.com") +}; + +Console.WriteLine("\n------Distinct on a class------"); +Console.WriteLine("Count = {0}", PeopleHelper.UsingDistinct(people).Count); + +Console.WriteLine("\n------Distinct on a record------"); +Console.WriteLine("Count = {0}", PeopleHelper.UsingDistinct(records).Count); + +Console.WriteLine("\n------DistinctBy on a class------"); +Console.WriteLine("Output = {0}", string.Join(",", PeopleHelper.UsingDistinctBy(people).Select(p => p.Name))); diff --git a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists.csproj b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists.csproj index 74abf5c976..dfb40caafc 100644 --- a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists.csproj +++ b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net10.0 enable enable diff --git a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesHelper.cs b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesHelper.cs index 615595481e..24e33b6d5f 100644 --- a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesHelper.cs +++ b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesHelper.cs @@ -1,6 +1,6 @@ namespace RemoveDuplicatesFromLists; -public class RemoveDuplicatesHelper +public class RemoveDuplicatesHelper where T : notnull { public RemoveDuplicatesHelper() { @@ -77,20 +77,26 @@ public List UsingEmptyListWithAny() return listWithoutDuplicates; } + public void RemoveDuplicatesInPlace() + { + var seen = new HashSet(); + ListWithDuplicates.RemoveAll(x => !seen.Add(x)); + } + public List UsingIterationsAndShifting() { - var n = ListWithDuplicates.Count; + var list = new List(ListWithDuplicates); + var n = list.Count; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { - if (ListWithDuplicates.ElementAt(i)!.Equals(ListWithDuplicates.ElementAt(j))) + if (list[i].Equals(list[j])) { for (int k = j; k < n - 1; k++) { - T item = ListWithDuplicates.ElementAt(k); - item = ListWithDuplicates.ElementAt(k + 1); + list[k] = list[k + 1]; } j--; n--; @@ -98,27 +104,28 @@ public List UsingIterationsAndShifting() } } - return ListWithDuplicates.Take(n).ToList(); + return list.Take(n).ToList(); } public List UsingIterationsAndSwapping() { - var size = ListWithDuplicates.Count; + var list = new List(ListWithDuplicates); + var size = list.Count; + for (int i = 0; i < size; i++) { for (int j = i + 1; j < size; j++) { - if (ListWithDuplicates.ElementAt(i)!.Equals(ListWithDuplicates.ElementAt(j))) + if (list[i].Equals(list[j])) { size--; - T jThItem = ListWithDuplicates.ElementAt(j); - jThItem = ListWithDuplicates.ElementAt(size); + (list[j], list[size]) = (list[size], list[j]); j--; } } } - return ListWithDuplicates.Take(size).ToList(); + return list.Take(size).ToList(); } public List UsingRecursion(List? listWithoutDuplicates = default, int index = 0) @@ -129,7 +136,7 @@ public List UsingRecursion(List? listWithoutDuplicates = default, int inde } if (index >= ListWithDuplicates.Count) { - return ListWithDuplicates; + return listWithoutDuplicates; } if (listWithoutDuplicates.IndexOf(ListWithDuplicates[index]) < 0) { diff --git a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromListsBenchmark/RemoveDuplicatesFromListsBenchmark.csproj b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromListsBenchmark/RemoveDuplicatesFromListsBenchmark.csproj index 7018487187..71a18b6310 100644 --- a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromListsBenchmark/RemoveDuplicatesFromListsBenchmark.csproj +++ b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromListsBenchmark/RemoveDuplicatesFromListsBenchmark.csproj @@ -2,13 +2,13 @@ Exe - net6.0 + net10.0 enable enable - + diff --git a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromListsBenchmark/RemoveDuplicatesFromListsBenchmarkRunner.cs b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromListsBenchmark/RemoveDuplicatesFromListsBenchmarkRunner.cs index a6565a24b3..546b3eb51f 100644 --- a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromListsBenchmark/RemoveDuplicatesFromListsBenchmarkRunner.cs +++ b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromListsBenchmark/RemoveDuplicatesFromListsBenchmarkRunner.cs @@ -11,83 +11,108 @@ public class RemoveDuplicateFromListBenchmarkRunner [Orderer(SummaryOrderPolicy.FastestToSlowest)] public class RemoveDuplicateElementsBenchmark { - private RemoveDuplicatesHelper _helper = new RemoveDuplicatesHelper(); + private const int ItemCount = 2_000; - public RemoveDuplicateElementsBenchmark() + private readonly RemoveDuplicatesHelper _helper = new RemoveDuplicatesHelper(); + private List _source = new List(); + + // Every case holds 2,000 items. Only the number of distinct values changes, + // because that is what decides which approach wins. + [Params(3, 100, 2_000)] + public int DistinctValues { get; set; } + + [GlobalSetup] + public void GlobalSetup() + { + var random = new Random(42); + _source = Enumerable.Range(0, ItemCount) + .Select(i => i % DistinctValues + 1) + .OrderBy(_ => random.Next()) + .ToList(); + _helper.ListWithDuplicates = _source; + } + + [Benchmark] + public List DistinctLINQMethod() { - _helper.ListWithDuplicates.AddRange(Enumerable.Repeat(1, 50).Concat(Enumerable.Repeat(2, 80)).Concat(Enumerable.Repeat(3, 70)).ToList()); + return _helper.UsingDistinct(); } [Benchmark] - public void DistinctLINQMethod() + public List GroupByLINQMethod() { - _helper.UsingDistinct(); + return _helper.UsingGroupBy(); } [Benchmark] - public void GroupByLINQMethod() + public List UnionLINQMethod() { - _helper.UsingGroupBy(); + return _helper.UsingUnion(); } [Benchmark] - public void UnionLINQMethod() + public List ConvertToHashSetMethod() { - _helper.UsingUnion(); + return _helper.ConvertingToHashSet(); } [Benchmark] - public void HashSetMethod() + public List InitializingHashSetMethod() { - _helper.ConvertingToHashSet(); + return _helper.InitializingAHashSet(); } [Benchmark] - public void InitializingHashetMethod() + public List DictionaryMethod() { - _helper.InitializingAHashSet(); + return _helper.UsingDictionary(); } [Benchmark] - public void DictionaryMethod() + public List EmptyListWithContainsMethod() { - _helper.UsingDictionary(); + return _helper.UsingEmptyListWithContains(); } [Benchmark] - public void EmptyListWithContainsMethod() + public List EmptyListWithAnyMethod() { - _helper.UsingEmptyListWithContains(); + return _helper.UsingEmptyListWithAny(); } [Benchmark] - public void EmptyListWithAnyMethod() + public List IterationsAndShiftingMethod() { - _helper.UsingEmptyListWithAny(); + return _helper.UsingIterationsAndShifting(); } [Benchmark] - public void IterationsAndShiftingMethod() + public List IterationsAndSwappingMethod() { - _helper.UsingIterationsAndShifting(); + return _helper.UsingIterationsAndSwapping(); } [Benchmark] - public void IterationsAndSwappingMethod() + public List RecursiveMethod() { - _helper.UsingIterationsAndSwapping(); + return _helper.UsingRecursion(); } + // Sorting() and RemoveDuplicatesInPlace() change the list they work on, so these two + // start every call from a fresh copy of the same source list. The copy is part of what they measure. [Benchmark] - public void RecursiveMethod() + public List SortMethod() { - _helper.UsingRecursion(); + _helper.ListWithDuplicates = new List(_source); + return _helper.Sorting(); } [Benchmark] - public void SortMethod() + public List RemoveAllInPlaceMethod() { - _helper.Sorting(); + _helper.ListWithDuplicates = new List(_source); + _helper.RemoveDuplicatesInPlace(); + return _helper.ListWithDuplicates; } } -} \ No newline at end of file +} diff --git a/collections-lists/RemoveDuplicatesFromLists/Tests/PeopleAndInPlaceTests.cs b/collections-lists/RemoveDuplicatesFromLists/Tests/PeopleAndInPlaceTests.cs new file mode 100644 index 0000000000..0a06cc6851 --- /dev/null +++ b/collections-lists/RemoveDuplicatesFromLists/Tests/PeopleAndInPlaceTests.cs @@ -0,0 +1,80 @@ +using RemoveDuplicatesFromLists; + +namespace RemoveDuplicatesFromListsTests; + +public class PeopleAndInPlaceTests +{ + [Fact] + public void GivenAClass_WhenUsingDistinct_ThenKeepsEqualValuedPeople() + { + var people = new List + { + new() { Name = "Ann", Age = 30, Email = "ann@example.com" }, + new() { Name = "Ann", Age = 30, Email = "ann@example.com" } + }; + + var response = PeopleHelper.UsingDistinct(people); + + Assert.Equal(2, response.Count); + } + + [Fact] + public void GivenARecord_WhenUsingDistinct_ThenRemovesEqualValuedPeople() + { + var people = new List + { + new("Ann", 30, "ann@example.com"), + new("Ann", 30, "ann@example.com") + }; + + var response = PeopleHelper.UsingDistinct(people); + + Assert.Single(response); + } + + [Fact] + public void GivenAClass_WhenUsingDistinctBy_ThenKeepsTheFirstPersonForEachEmail() + { + var first = new Person { Name = "Ann", Age = 30, Email = "ann@example.com" }; + var people = new List + { + first, + new() { Name = "Ann Smith", Age = 31, Email = "ann@example.com" }, + new() { Name = "Bob", Age = 22, Email = "bob@example.com" } + }; + + var response = PeopleHelper.UsingDistinctBy(people); + + Assert.Equal(2, response.Count); + Assert.Same(first, response[0]); + Assert.Equal("bob@example.com", response[1].Email); + } + + [Fact] + public void WhenRemovingDuplicatesInPlace_ThenChangesTheSameListInstance() + { + var helper = new RemoveDuplicatesHelper + { + ListWithDuplicates = new List() { 1, 1, 2, 3, 4, 5 } + }; + var sameList = helper.ListWithDuplicates; + + helper.RemoveDuplicatesInPlace(); + + Assert.Same(sameList, helper.ListWithDuplicates); + Assert.Equal(new[] { 1, 2, 3, 4, 5 }, sameList); + } + + [Fact] + public void GivenDuplicatesSpreadThroughTheList_WhenRemovingDuplicatesInPlace_ThenKeepsFirstAppearanceOrder() + { + var helper = new RemoveDuplicatesHelper + { + ListWithDuplicates = new List() { 3, 1, 3, 2, 1, 4, 2, 5, 4 } + }; + + helper.RemoveDuplicatesInPlace(); + + Assert.Equal(new[] { 3, 1, 2, 4, 5 }, helper.ListWithDuplicates); + } +} diff --git a/collections-lists/RemoveDuplicatesFromLists/Tests/RemoveDuplicatesFromListsTests.csproj b/collections-lists/RemoveDuplicatesFromLists/Tests/RemoveDuplicatesFromListsTests.csproj index 39fa7144c5..1399b4c951 100644 --- a/collections-lists/RemoveDuplicatesFromLists/Tests/RemoveDuplicatesFromListsTests.csproj +++ b/collections-lists/RemoveDuplicatesFromLists/Tests/RemoveDuplicatesFromListsTests.csproj @@ -1,7 +1,7 @@ - net6.0 + net10.0 enable enable @@ -9,13 +9,13 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/collections-lists/RemoveDuplicatesFromLists/Tests/Tests.cs b/collections-lists/RemoveDuplicatesFromLists/Tests/Tests.cs index 275c0d38d1..acde9e31c4 100644 --- a/collections-lists/RemoveDuplicatesFromLists/Tests/Tests.cs +++ b/collections-lists/RemoveDuplicatesFromLists/Tests/Tests.cs @@ -67,7 +67,7 @@ public void WhenInitializingHashSet_ThenRemovesDuplicates() [Fact] public void WhenUsingDictionary_ThenRemovesDuplicates() { - var response = _helper.ConvertingToHashSet(); + var response = _helper.UsingDictionary(); var unique = response.GroupBy(p => p).All(g => g.Count() == 1); Assert.True(unique); @@ -133,4 +133,90 @@ public void WhenSorting_ThenRemovesDuplicates() Assert.True(unique); Assert.Equal(2, response.Count); } -} \ No newline at end of file + + // The fixture above holds every duplicate at the end of the list, so a method that only + // truncates the list passes it by accident. These cases put a duplicate at index 0. + public static TheoryData OrderKeepingMethods => new() + { + nameof(RemoveDuplicatesHelper.UsingDistinct), + nameof(RemoveDuplicatesHelper.UsingGroupBy), + nameof(RemoveDuplicatesHelper.UsingUnion), + nameof(RemoveDuplicatesHelper.UsingDictionary), + nameof(RemoveDuplicatesHelper.UsingEmptyListWithContains), + nameof(RemoveDuplicatesHelper.UsingEmptyListWithAny), + nameof(RemoveDuplicatesHelper.UsingIterationsAndShifting), + nameof(RemoveDuplicatesHelper.UsingRecursion), + nameof(RemoveDuplicatesHelper.Sorting), + }; + + // A HashSet does not guarantee its enumeration order, and swapping moves the last + // item into the gap a duplicate leaves, so these are checked for content, not order. + public static TheoryData OrderFreeMethods => new() + { + nameof(RemoveDuplicatesHelper.ConvertingToHashSet), + nameof(RemoveDuplicatesHelper.InitializingAHashSet), + nameof(RemoveDuplicatesHelper.UsingIterationsAndSwapping), + }; + + [Theory] + [MemberData(nameof(OrderKeepingMethods))] + public void GivenADuplicateAtIndexZero_WhenRemovingDuplicates_ThenKeepsEveryValueInOrder(string method) + { + var helper = new RemoveDuplicatesHelper + { + ListWithDuplicates = new List() { 1, 1, 2, 3, 4, 5 } + }; + + var response = Run(helper, method); + + Assert.Equal(new[] { 1, 2, 3, 4, 5 }, response); + } + + [Theory] + [MemberData(nameof(OrderFreeMethods))] + public void GivenADuplicateAtIndexZero_WhenRemovingDuplicates_ThenKeepsEveryValueOnce(string method) + { + var helper = new RemoveDuplicatesHelper + { + ListWithDuplicates = new List() { 1, 1, 2, 3, 4, 5 } + }; + + var response = Run(helper, method); + + Assert.Equal(5, response.Count); + Assert.Equal(new[] { 1, 2, 3, 4, 5 }, response.Order()); + } + + [Theory] + [MemberData(nameof(OrderKeepingMethods))] + [MemberData(nameof(OrderFreeMethods))] + public void GivenDuplicatesSpreadThroughTheList_WhenRemovingDuplicates_ThenKeepsEveryValueOnce(string method) + { + var helper = new RemoveDuplicatesHelper + { + ListWithDuplicates = new List() { 3, 1, 3, 2, 1, 4, 2, 5, 4 } + }; + + var response = Run(helper, method); + + Assert.Equal(5, response.Count); + Assert.Equal(new[] { 1, 2, 3, 4, 5 }, response.Order()); + } + + private static List Run(RemoveDuplicatesHelper helper, string method) => method switch + { + nameof(helper.UsingDistinct) => helper.UsingDistinct(), + nameof(helper.UsingGroupBy) => helper.UsingGroupBy(), + nameof(helper.UsingUnion) => helper.UsingUnion(), + nameof(helper.ConvertingToHashSet) => helper.ConvertingToHashSet(), + nameof(helper.InitializingAHashSet) => helper.InitializingAHashSet(), + nameof(helper.UsingDictionary) => helper.UsingDictionary(), + nameof(helper.UsingEmptyListWithContains) => helper.UsingEmptyListWithContains(), + nameof(helper.UsingEmptyListWithAny) => helper.UsingEmptyListWithAny(), + nameof(helper.UsingIterationsAndShifting) => helper.UsingIterationsAndShifting(), + nameof(helper.UsingIterationsAndSwapping) => helper.UsingIterationsAndSwapping(), + nameof(helper.UsingRecursion) => helper.UsingRecursion(), + nameof(helper.Sorting) => helper.Sorting(), + _ => throw new ArgumentOutOfRangeException(nameof(method), method, null) + }; +} From ee16b26a50ec0912253010d5a74f9f312b1331f1 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 4 Oct 2026 14:24:21 +0200 Subject: [PATCH 2/3] Sorting keeps a value equal to default Sorting() compared every item with a start value of default, so a 0 in a List was never added: { 0, 1, 0, 2 } returned { 1, 2 }. The first item is now always added. New theory test runs every method against { 0, 1, 0, 2 }; it fails on the old Sorting() and passes on all methods now. --- .../RemoveDuplicatesHelper.cs | 2 +- .../RemoveDuplicatesFromLists/Tests/Tests.cs | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesHelper.cs b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesHelper.cs index 24e33b6d5f..7c90c689d4 100644 --- a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesHelper.cs +++ b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesHelper.cs @@ -155,7 +155,7 @@ public List Sorting() T? element = default; foreach (T result in ListWithDuplicates) { - if (!result!.Equals(element)) + if (listWithoutDuplicates.Count == 0 || !result.Equals(element)) { listWithoutDuplicates.Add(result); element = result; diff --git a/collections-lists/RemoveDuplicatesFromLists/Tests/Tests.cs b/collections-lists/RemoveDuplicatesFromLists/Tests/Tests.cs index acde9e31c4..520cd9966e 100644 --- a/collections-lists/RemoveDuplicatesFromLists/Tests/Tests.cs +++ b/collections-lists/RemoveDuplicatesFromLists/Tests/Tests.cs @@ -1,4 +1,4 @@ -using RemoveDuplicatesFromLists; +using RemoveDuplicatesFromLists; namespace RemoveDuplicatesFromListsTests; @@ -203,6 +203,21 @@ public void GivenDuplicatesSpreadThroughTheList_WhenRemovingDuplicates_ThenKeeps Assert.Equal(new[] { 1, 2, 3, 4, 5 }, response.Order()); } + [Theory] + [MemberData(nameof(OrderKeepingMethods))] + [MemberData(nameof(OrderFreeMethods))] + public void GivenAZeroInTheList_WhenRemovingDuplicates_ThenKeepsTheZero(string method) + { + var helper = new RemoveDuplicatesHelper + { + ListWithDuplicates = new List() { 0, 1, 0, 2 } + }; + + var response = Run(helper, method); + + Assert.Equal(new[] { 0, 1, 2 }, response.Order()); + } + private static List Run(RemoveDuplicatesHelper helper, string method) => method switch { nameof(helper.UsingDistinct) => helper.UsingDistinct(), From 7efe5f9705a653a2a96ce531526255a5053bf616 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 4 Oct 2026 14:53:40 +0200 Subject: [PATCH 3/3] Compare items without boxing With T only constrained to notnull, x.Equals(item) and list[i].Equals(list[j]) bind to object.Equals(object), so every comparison in the Any(), loop and sorting methods boxed an int. Constraining T to IEquatable makes them call Equals(T) directly, which is what the same code does on a plain List, so the benchmark now measures the code the article shows. --- .../RemoveDuplicatesFromLists/RemoveDuplicatesHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesHelper.cs b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesHelper.cs index 7c90c689d4..61f1aa0d38 100644 --- a/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesHelper.cs +++ b/collections-lists/RemoveDuplicatesFromLists/RemoveDuplicatesFromLists/RemoveDuplicatesHelper.cs @@ -1,6 +1,6 @@ namespace RemoveDuplicatesFromLists; -public class RemoveDuplicatesHelper where T : notnull +public class RemoveDuplicatesHelper where T : IEquatable { public RemoveDuplicatesHelper() {