From 2a749e817396ae7c4b967fc8c1607ab1ff1b5d9a Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Sun, 4 Jul 2021 11:11:00 -0700 Subject: [PATCH] Added LazyListUnsafe. Improved CopyTo methods for use with Span. Migrated tests. --- Open.Collections.sln | 31 ++++ source/CollectionWrapper.cs | 6 +- source/DictionaryToHashSetWrapper.cs | 28 ++- source/Extensions.cs | 45 +++++ source/LazyList.cs | 125 +++---------- source/LazyListUnsafe.cs | 170 ++++++++++++++++++ source/Open.Collections.csproj | 2 +- source/OrderedDictionary.cs | 4 - source/ReadOnlyCollectionWrapper.cs | 4 + .../LockSynchronizedCollectionWrapper.cs | 13 ++ .../Open.Collections.Tests/LazyListTests.cs | 37 ++++ .../Open.Collections.Tests.csproj | 1 - .../SumCombinationTests.cs | 120 ------------- 13 files changed, 352 insertions(+), 234 deletions(-) create mode 100644 Open.Collections.sln create mode 100644 source/LazyListUnsafe.cs create mode 100644 testing/Open.Collections.Tests/LazyListTests.cs delete mode 100644 testing/Open.Collections.Tests/SumCombinationTests.cs diff --git a/Open.Collections.sln b/Open.Collections.sln new file mode 100644 index 0000000..1ee8142 --- /dev/null +++ b/Open.Collections.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31410.357 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Open.Collections", "source\Open.Collections.csproj", "{F1C64CD8-E90A-46C8-9373-3BAB5498CC14}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Open.Collections.Tests", "testing\Open.Collections.Tests\Open.Collections.Tests.csproj", "{44063AEE-8909-4A26-80BE-8623F5EBBA1F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F1C64CD8-E90A-46C8-9373-3BAB5498CC14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1C64CD8-E90A-46C8-9373-3BAB5498CC14}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1C64CD8-E90A-46C8-9373-3BAB5498CC14}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1C64CD8-E90A-46C8-9373-3BAB5498CC14}.Release|Any CPU.Build.0 = Release|Any CPU + {44063AEE-8909-4A26-80BE-8623F5EBBA1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44063AEE-8909-4A26-80BE-8623F5EBBA1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44063AEE-8909-4A26-80BE-8623F5EBBA1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44063AEE-8909-4A26-80BE-8623F5EBBA1F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {323AE57A-3C44-4EB8-890D-FD1107AADD8D} + EndGlobalSection +EndGlobal diff --git a/source/CollectionWrapper.cs b/source/CollectionWrapper.cs index 1edf7c0..58e4940 100644 --- a/source/CollectionWrapper.cs +++ b/source/CollectionWrapper.cs @@ -48,13 +48,9 @@ public virtual void Clear() public virtual bool Remove(T item) => InternalSource.Remove(item); - /// + /// public override bool IsReadOnly => InternalSource.IsReadOnly; - - /// - public override void CopyTo(T[] array, int arrayIndex) - => InternalSource.CopyTo(array, arrayIndex); #endregion } } diff --git a/source/DictionaryToHashSetWrapper.cs b/source/DictionaryToHashSetWrapper.cs index 668d395..c5323eb 100644 --- a/source/DictionaryToHashSetWrapper.cs +++ b/source/DictionaryToHashSetWrapper.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; namespace Open.Collections @@ -13,12 +14,15 @@ public DictionaryToHashSetWrapper(IDictionary source) InternalSource = source; } + /// public int Count => InternalSource.Count; + /// public bool IsReadOnly => InternalSource.IsReadOnly; + /// public virtual bool Add(T item) { if (InternalSource.ContainsKey(item)) @@ -35,31 +39,45 @@ public virtual bool Add(T item) return true; } + /// public bool Remove(T item) // ReSharper disable once AssignNullToNotNullAttribute => InternalSource.Remove(item); + /// public void Clear() => InternalSource.Clear(); + /// public bool Contains(T item) // ReSharper disable once AssignNullToNotNullAttribute => InternalSource.ContainsKey(item); + /// public void CopyTo(T[] array, int arrayIndex) => InternalSource.Keys.CopyTo(array, arrayIndex); + /// + public virtual Span CopyTo(Span span) + => InternalSource.Keys.CopyToSpan(span); + + /// + /// Returns a copy of the underlying keys. + /// public HashSet ToHashSet() => new(InternalSource.Keys); + /// public IEnumerator GetEnumerator() => InternalSource.Keys.GetEnumerator(); + /// public void ExceptWith(IEnumerable other) { foreach (var e in other) Remove(e); } + /// public void IntersectWith(IEnumerable other) { foreach (var e in other) @@ -69,24 +87,31 @@ public void IntersectWith(IEnumerable other) } } + /// public bool IsProperSubsetOf(IEnumerable other) => ToHashSet().IsProperSubsetOf(other); + /// public bool IsProperSupersetOf(IEnumerable other) => ToHashSet().IsProperSupersetOf(other); + /// public bool IsSubsetOf(IEnumerable other) => ToHashSet().IsSubsetOf(other); + /// public bool IsSupersetOf(IEnumerable other) => ToHashSet().IsSupersetOf(other); + /// public bool Overlaps(IEnumerable other) => ToHashSet().Overlaps(other); + /// public bool SetEquals(IEnumerable other) => ToHashSet().SetEquals(other); + /// public void SymmetricExceptWith(IEnumerable other) { foreach (var e in other) @@ -98,6 +123,7 @@ public void SymmetricExceptWith(IEnumerable other) } } + /// public void UnionWith(IEnumerable other) { foreach (var e in other) Add(e); diff --git a/source/Extensions.cs b/source/Extensions.cs index 1b15c2f..38c3fff 100644 --- a/source/Extensions.cs +++ b/source/Extensions.cs @@ -816,9 +816,30 @@ public static IEnumerable Weave(this IEnumerable> source) } } + /// + /// Caches the results up to the last index requested. + /// + /// The source enumerable to cache. + /// When true, will throw an InvalidOperationException if anything causes the list to evaluate to completion. + /// A LazyList for accessing the cached results. public static LazyList Memoize(this IEnumerable list, bool isEndless = false) => new(list, isEndless); + /// + /// Caches the results up to the last index requested. + /// + /// WARNING: + /// - Is not thread safe. + /// - There is a risk of recursion. + /// - An endless enumerable may result in a stack overflow. + /// + /// If any of the above are of concern, then use .Memoize() instead. + /// + /// The source enumerable to cache. + /// A LazyList for accessing the cached results. + public static LazyListUnsafe MemoizeUnsafe(this IEnumerable list) + => new(list); + /// /// .IndexOf extension optimized for an array. /// @@ -834,5 +855,29 @@ public static int IndexOf(this T[] source, T value) return -1; } + /// + /// Copies the results to the provided span up to its length or until the end of the results. + /// + /// The span to copy to. + /// + /// A span representing the results. + /// If the count was less than the target length, a new span representing the results. + /// Otherwise the target is returned. + /// + public static Span CopyToSpan(this IEnumerable source, Span target) + { + var len = target.Length; + if (len == 0) return target; + + var count = 0; + foreach (var e in source) + { + target[count] = e; + if (len == ++count) return target; + } + + return target.Slice(0, count); + } + } } diff --git a/source/LazyList.cs b/source/LazyList.cs index 4eca0eb..50e5550 100644 --- a/source/LazyList.cs +++ b/source/LazyList.cs @@ -11,125 +11,46 @@ namespace Open.Collections { - public class LazyList : DisposableBase, IReadOnlyList + /// + /// A a thread-safe list for caching the results of an enumerable. + /// Note: should be disposed manually whenever possible as the locking mechanism is a ReaderWriterLockSlim. + /// + public class LazyList : LazyListUnsafe { - List _cached; - IEnumerator _enumerator; - ReaderWriterLockSlim Sync; + int _safeCount; + /// + /// A value indicating whether the results are known or expected to be finite. + /// A list that was constructed as endless but has reached the end of the results will return false. + /// public bool IsEndless { get; private set; } - public LazyList(IEnumerable source, bool isEndless = false) + + public LazyList(IEnumerable source, bool isEndless = false) : base(source) { - _enumerator = source.GetEnumerator(); - _cached = new List(); Sync = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); // This is important as it's possible to recurse infinitely to generate a result. :( IsEndless = isEndless; // To indicate if a source is not allowed to fully enumerate. } protected override void OnDispose() { - using (Sync.WriteLock()) - { - DisposeOf(ref _enumerator); - Nullify(ref _cached)?.Clear(); - } + using (Sync.WriteLock()) base.OnDispose(); DisposeOf(ref Sync); } - public T this[int index] - { - get - { - AssertIsAlive(); - - if (index < 0) - throw new ArgumentOutOfRangeException(nameof(index), "Cannot be less than zero."); - if (!EnsureIndex(index)) - throw new ArgumentOutOfRangeException(nameof(index), "Greater than total count."); - - return _cached[index]; - } - } + private const string MARKED_ENDLESS = "This list is marked as endless and may never complete."; - private int _safeCount; - public int Count + /// + public override int IndexOf(T item) { - get - { - AssertIsAlive(); - Finish(); - return _cached.Count; - } - } + const string MESSAGE = MARKED_ENDLESS+" Use an enumerator, then Take(x).IndexOf()."; + if (IsEndless) throw new InvalidOperationException(MESSAGE); - public bool TryGetValueAt(int index, out T value) - { - if (EnsureIndex(index)) - { - value = _cached[index]; - return true; - } - - value = default!; - return false; - } - - public IEnumerator GetEnumerator() - { - AssertIsAlive(); - - var index = -1; - // Interlocked allows for multi-threaded access to this enumerator. - while (TryGetValueAt(Interlocked.Increment(ref index), out var value)) - yield return value; - } - - public int IndexOf(T item) - { - AssertIsAlive(); - if (IsEndless) - throw new InvalidOperationException("This list is marked as endless and may never complete. Use an enumerator, then Take(x).IndexOf()."); - - var index = 0; - while (EnsureIndex(index)) - { - var value = _cached[index]; - if (value is null) - { - if (item is null) return index; - } - else if (value.Equals(item)) - return index; - - index++; - } - - return -1; - - } - - public bool Contains(T item) - { - AssertIsAlive(); - return IndexOf(item) != -1; - } - - public void CopyTo(T[] array, int arrayIndex = 0) - { - AssertIsAlive(); - var len = Math.Min(IsEndless ? int.MaxValue : Count, array.Length - arrayIndex); - for (var i = 0; i < len; i++) - array[i + arrayIndex] = this[i]; - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); + return base.IndexOf(item); } - private bool EnsureIndex(int maxIndex) + protected override bool EnsureIndex(int maxIndex) { if (maxIndex < _safeCount) return true; @@ -186,11 +107,11 @@ private bool EnsureIndex(int maxIndex) return false; } - private void Finish() + protected override void Finish() { if (IsEndless) - throw new InvalidOperationException("This list is marked as endless and may never complete."); - while (EnsureIndex(int.MaxValue)) { } + throw new InvalidOperationException(MARKED_ENDLESS); + base.Finish(); } } diff --git a/source/LazyListUnsafe.cs b/source/LazyListUnsafe.cs new file mode 100644 index 0000000..d88be0c --- /dev/null +++ b/source/LazyListUnsafe.cs @@ -0,0 +1,170 @@ +/*! + * @author electricessence / https://github.com/electricessence/ + * Partly based on: http://www.fallingcanbedeadly.com/posts/crazy-extention-methods-tolazylist/ + */ + +using Open.Disposable; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Open.Collections +{ + /// + /// An "unsafe" lazy list is one that is not thread safe, does not manage recursion, and does not protect against stack overflow (infinite length). + /// Only use if you know the results are finite and access is thread safe. + /// Note: disposing releases the underlying enumerator if it never reached the end of the results. + /// + public class LazyListUnsafe : DisposableBase, IReadOnlyList + { + protected List _cached; + protected IEnumerator _enumerator; + + public LazyListUnsafe(IEnumerable source) + { + _enumerator = source.GetEnumerator(); + _cached = new List(); + } + + protected override void OnDispose() + { + DisposeOf(ref _enumerator); + Nullify(ref _cached)?.Clear(); + } + + const string MUST_BE_AT_LEAST_ZERO = "Must be at least zero."; + const string GREATER_THAN_TOTAL = "Greater than total count."; + + /// + public T this[int index] + { + get + { + AssertIsAlive(); + + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), MUST_BE_AT_LEAST_ZERO); + if (!EnsureIndex(index)) + throw new ArgumentOutOfRangeException(nameof(index), GREATER_THAN_TOTAL); + + return _cached[index]; + } + } + + /// + public int Count + { + get + { + AssertIsAlive(); + Finish(); + return _cached.Count; + } + } + + /// + /// Will attempt to get a value in the list if it is within the count of the results. + /// + /// The index to look up. + /// The value at that index. + /// True if acquired. False if the index is greater + public bool TryGetValueAt(int index, out T value) + { + AssertIsAlive(); + if (index < 0) throw new ArgumentOutOfRangeException(nameof(index), index, MUST_BE_AT_LEAST_ZERO); + + if (EnsureIndex(index)) + { + value = _cached[index]; + return true; + } + + value = default!; + return false; + } + + /// + public IEnumerator GetEnumerator() + { + AssertIsAlive(); + + var index = -1; + // Interlocked allows for multi-threaded access to this enumerator. + while (TryGetValueAt(Interlocked.Increment(ref index), out var value)) + yield return value; + } + + /// + public virtual int IndexOf(T item) + { + AssertIsAlive(); + + var index = 0; + while (EnsureIndex(index)) + { + var value = _cached[index]; + if (value is null) + { + if (item is null) return index; + } + else if (value.Equals(item)) + return index; + + index++; + } + + return -1; + + } + + /// + public bool Contains(T item) + { + AssertIsAlive(); + return IndexOf(item) != -1; + } + + /// + public Span CopyTo(T[] array, int startIndex = 0) + { + if (array is null) throw new ArgumentNullException(nameof(array)); + if (startIndex >= array.Length) + throw new ArgumentOutOfRangeException(nameof(startIndex), startIndex, "Must be less than the length of the provided array."); + + var span = array.AsSpan(); + return this.CopyToSpan(startIndex == 0 ? span : span.Slice(startIndex)); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + + protected virtual bool EnsureIndex(int maxIndex) + { + if (maxIndex < _cached.Count) + return true; + + if (_enumerator is null) + return false; + + while (_enumerator.MoveNext()) + { + if (_cached.Count == int.MaxValue) + throw new Exception("Reached maximium contents for a single list. Cannot memoize further."); + + _cached.Add(_enumerator.Current); + + if (maxIndex < _cached.Count) + return true; + } + + DisposeOf(ref _enumerator); + + return false; + } + + protected virtual void Finish() + { + while (EnsureIndex(int.MaxValue)) { } + } + + } +} diff --git a/source/Open.Collections.csproj b/source/Open.Collections.csproj index d445bb7..972d1e0 100644 --- a/source/Open.Collections.csproj +++ b/source/Open.Collections.csproj @@ -17,7 +17,7 @@ https://github.com/Open-NET-Libraries/Open.Collections/ https://github.com/Open-NET-Libraries/Open.Collections/ git - 2.10.2 + 2.10.3 MIT true diff --git a/source/OrderedDictionary.cs b/source/OrderedDictionary.cs index 5aafeee..335be74 100644 --- a/source/OrderedDictionary.cs +++ b/source/OrderedDictionary.cs @@ -505,19 +505,15 @@ public bool TryGetValue(TKey key, out TValue value) /// public ICollection Values => Dictionary.Values; - /// void ICollection>.Add(KeyValuePair item) => Add(item.Key, item.Value); - /// bool ICollection>.Contains(KeyValuePair item) => ((ICollection>)Dictionary).Contains(item); - /// void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => ((ICollection>)Dictionary).CopyTo(array, arrayIndex); - /// bool ICollection>.Remove(KeyValuePair item) => Remove(item.Key); diff --git a/source/ReadOnlyCollectionWrapper.cs b/source/ReadOnlyCollectionWrapper.cs index a6a1887..efc2ea8 100644 --- a/source/ReadOnlyCollectionWrapper.cs +++ b/source/ReadOnlyCollectionWrapper.cs @@ -44,6 +44,10 @@ public virtual void CopyTo(T[] array, int arrayIndex) => InternalSource.CopyTo(array, arrayIndex); #endregion + /// + public virtual Span CopyTo(Span span) + => InternalSource.CopyToSpan(span); + /// public virtual void Export(ICollection to) => to.Add(InternalSource); diff --git a/source/Synchronized/LockSynchronizedCollectionWrapper.cs b/source/Synchronized/LockSynchronizedCollectionWrapper.cs index 43a3004..4191002 100644 --- a/source/Synchronized/LockSynchronizedCollectionWrapper.cs +++ b/source/Synchronized/LockSynchronizedCollectionWrapper.cs @@ -111,6 +111,19 @@ public override void CopyTo(T[] array, int arrayIndex) lock (Sync) InternalSource.CopyTo(array, arrayIndex); } + /// + /// Copies the results to the provided span up to its length or until the end of the results. + /// + /// + /// A span representing the results. + /// If the count was less than the target length, a new span representing the results. + /// Otherwise the target is returned. + /// + public Span CopyTo(Span span) + { + lock (Sync) return InternalSource.CopyToSpan(span); + } + /// public void Modify(Action action) { diff --git a/testing/Open.Collections.Tests/LazyListTests.cs b/testing/Open.Collections.Tests/LazyListTests.cs new file mode 100644 index 0000000..d13e1c9 --- /dev/null +++ b/testing/Open.Collections.Tests/LazyListTests.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Open.Collections.Tests +{ + public class LazyListTests + { + [Fact] + public void LazyListSmokeTest() + { + { + var e = Enumerable.Range(0, 5); + Assert.Equal(5, e.Memoize().Count); + Assert.Equal(5, e.MemoizeUnsafe().Count); + + Assert.Throws(() => e.Memoize(true).Count); + } + + { + var e = Enumerable.Range(0, 30); + var a = e.Memoize(); + var b = e.MemoizeUnsafe(); + Assert.Equal(7, a[7]); + Assert.Equal(7, b[7]); + + Assert.Equal(6, a.IndexOf(6)); + Assert.Equal(6, b.IndexOf(6)); + Assert.Equal(9, a.IndexOf(9)); + Assert.Equal(9, b.IndexOf(9)); + } + } + } +} diff --git a/testing/Open.Collections.Tests/Open.Collections.Tests.csproj b/testing/Open.Collections.Tests/Open.Collections.Tests.csproj index 74736fb..c2379ce 100644 --- a/testing/Open.Collections.Tests/Open.Collections.Tests.csproj +++ b/testing/Open.Collections.Tests/Open.Collections.Tests.csproj @@ -20,7 +20,6 @@ - diff --git a/testing/Open.Collections.Tests/SumCombinationTests.cs b/testing/Open.Collections.Tests/SumCombinationTests.cs deleted file mode 100644 index 1ccb1ee..0000000 --- a/testing/Open.Collections.Tests/SumCombinationTests.cs +++ /dev/null @@ -1,120 +0,0 @@ -using Open.Collections.Numeric; -using Xunit; - -namespace Open.Collections.Tests -{ - public class SumCombinationTests - { - readonly PossibleAddends SC = new(); - - [Fact] - public void NoAddendsLessThan2() - { - for (int i = 0; i < 2; i++) - Assert.Equal(0, SC.UniqueAddendsFor(7, i).Count); - } - - [Fact] - public void NoAddendsWithLowSum() - { - for (int i = 0; i < 4; i++) - Assert.Equal(0, SC.UniqueAddendsFor(2, i).Count); - } - - [Fact] - public void AddendsFor2() - { - { - var result = SC.UniqueAddendsFor(3, 2); - Assert.Equal(1, result.Count); - Assert.Equal(new int[] { 1, 2 }, result[0]); - } - { - var result = SC.UniqueAddendsFor(4, 2); - Assert.Equal(1, result.Count); - Assert.Equal(new int[] { 1, 3 }, result[0]); - } - { - var result = SC.UniqueAddendsFor(5, 2); - Assert.Equal(2, result.Count); - Assert.Equal(new int[] { 1, 4 }, result[0]); - Assert.Equal(new int[] { 2, 3 }, result[1]); - } - { - var result = SC.UniqueAddendsFor(6, 2); - Assert.Equal(2, result.Count); - Assert.Equal(new int[] { 1, 5 }, result[0]); - Assert.Equal(new int[] { 2, 4 }, result[1]); - } - { - var result = SC.UniqueAddendsFor(7, 2); - Assert.Equal(3, result.Count); - Assert.Equal(new int[] { 1, 6 }, result[0]); - Assert.Equal(new int[] { 2, 5 }, result[1]); - Assert.Equal(new int[] { 3, 4 }, result[2]); - } - } - - - [Fact] - public void AddendsFor3() - { - { - for (int i = 0; i < 6; i++) - { - var result = SC.UniqueAddendsFor(i, 3); - Assert.Equal(0, result.Count); - } - } - { - var result = SC.UniqueAddendsFor(6, 3); - Assert.Equal(1, result.Count); - Assert.Equal(new int[] { 1, 2, 3 }, result[0]); - } - { - var result = SC.UniqueAddendsFor(7, 3); - Assert.Equal(1, result.Count); - Assert.Equal(new int[] { 1, 2, 4 }, result[0]); - } - { - var result = SC.UniqueAddendsFor(8, 3); - Assert.Equal(2, result.Count); - Assert.Equal(new int[] { 1, 2, 5 }, result[0]); - Assert.Equal(new int[] { 1, 3, 4 }, result[1]); - } - { - var result = SC.UniqueAddendsFor(9, 3); - Assert.Equal(3, result.Count); - Assert.Equal(new int[] { 1, 2, 6 }, result[0]); - Assert.Equal(new int[] { 1, 3, 5 }, result[1]); - Assert.Equal(new int[] { 2, 3, 4 }, result[2]); - } - { - var result = SC.UniqueAddendsFor(10, 3); - Assert.Equal(4, result.Count); - Assert.Equal(new int[] { 1, 2, 7 }, result[0]); - Assert.Equal(new int[] { 1, 3, 6 }, result[1]); - Assert.Equal(new int[] { 1, 4, 5 }, result[2]); - Assert.Equal(new int[] { 2, 3, 5 }, result[3]); - } - - { - var result = SC.UniqueAddendsFor(15, 3); - Assert.Equal(12, result.Count); - Assert.Equal(new int[] { 1, 2, 12 }, result[0]); - Assert.Equal(new int[] { 1, 3, 11 }, result[1]); - Assert.Equal(new int[] { 1, 4, 10 }, result[2]); - Assert.Equal(new int[] { 2, 3, 10 }, result[3]); - Assert.Equal(new int[] { 1, 5, 9 }, result[4]); - Assert.Equal(new int[] { 2, 4, 9 }, result[5]); - Assert.Equal(new int[] { 1, 6, 8 }, result[6]); - Assert.Equal(new int[] { 2, 5, 8 }, result[7]); - Assert.Equal(new int[] { 3, 4, 8 }, result[8]); - Assert.Equal(new int[] { 2, 6, 7 }, result[9]); - Assert.Equal(new int[] { 3, 5, 7 }, result[10]); - Assert.Equal(new int[] { 4, 5, 6 }, result[11]); - } - } - - } -}