From 5a236fdbee5e75afbcdff42bc9b3a0dbb7eff488 Mon Sep 17 00:00:00 2001 From: YoshiRulz Date: Sat, 15 Feb 2025 05:01:31 +1000 Subject: [PATCH] Implement remaining `IList` methods on `SortedList` --- src/BizHawk.Common/CustomCollections.cs | 38 ++++++++++++++++++- .../CustomCollectionTests.cs | 33 ++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/BizHawk.Common/CustomCollections.cs b/src/BizHawk.Common/CustomCollections.cs index eb2738c1a0..8e596b7fc1 100644 --- a/src/BizHawk.Common/CustomCollections.cs +++ b/src/BizHawk.Common/CustomCollections.cs @@ -3,9 +3,11 @@ using System.Collections.Generic; namespace BizHawk.Common { - public class SortedList : ICollection + public class SortedList : IList where T : IComparable { + private const string ERR_MSG_OUT_OF_ORDER = "setting/inserting elements must preserve ordering"; + protected readonly List _list; public virtual int Count => _list.Count; @@ -20,7 +22,24 @@ namespace BizHawk.Common _list.Sort(); } - public virtual T this[int index] => _list[index]; + public virtual T this[int index] + { + get => _list[index]; + set + { + // NOT allowing appends, to match BCL `List` + if (index < 0 || Count <= index) throw new ArgumentOutOfRangeException(paramName: nameof(index), index, message: $"index must be in 0..<{Count}"); + if (Count is 0) + { + _list.Add(value); + return; + } + var willBeGeqPrevious = index is 0 || value.CompareTo(_list[index - 1]) >= 0; + var willBeLeqFollowing = index == Count - 1 || _list[index + 1].CompareTo(value) >= 0; + if (willBeGeqPrevious && willBeLeqFollowing) _list[index] = value; + else throw new NotSupportedException(ERR_MSG_OUT_OF_ORDER); + } + } public virtual void Add(T item) { @@ -55,6 +74,21 @@ namespace BizHawk.Common return i < 0 ? -1 : i; } + public virtual void Insert(int index, T item) + { + // allowing appends per `IList` docs + if (index < 0 || Count < index) throw new ArgumentOutOfRangeException(paramName: nameof(index), index, message: $"index must be in 0..{Count}"); + if (Count is 0) + { + _list.Add(item); + return; + } + var willBeGeqPrevious = index is 0 || item.CompareTo(_list[index - 1]) >= 0; + var willBeLeqFollowing = index >= Count - 1 || _list[index].CompareTo(item) >= 0; + if (willBeGeqPrevious && willBeLeqFollowing) _list.Insert(index, item); + else throw new NotSupportedException(ERR_MSG_OUT_OF_ORDER); + } + public T LastOrDefault() => _list.Count is 0 ? default! : _list[_list.Count - 1]; diff --git a/src/BizHawk.Tests/Common/CustomCollections/CustomCollectionTests.cs b/src/BizHawk.Tests/Common/CustomCollections/CustomCollectionTests.cs index 83835f741b..f15333a887 100644 --- a/src/BizHawk.Tests/Common/CustomCollections/CustomCollectionTests.cs +++ b/src/BizHawk.Tests/Common/CustomCollections/CustomCollectionTests.cs @@ -26,6 +26,23 @@ namespace BizHawk.Tests.Common.CustomCollections Assert.IsTrue(list.Contains(11)); // `Contains` when `BinarySearch` returns non-negative } + [TestMethod] + public void TestSortedListInsert() + { + SortedList list = new([ 1, 4, 7 ]); + Assert.ThrowsException(() => list.Insert(index: 3, item: 0), "setting [^0] (appending) out-of-order should throw"); + list.Insert(index: 3, item: 10); + Assert.IsTrue(list.SequenceEqual([ 1, 4, 7, 10 ]), "expecting [ 1, 4, 7, 10 ]"); + Assert.ThrowsException(() => list.Insert(index: 3, item: 0), "setting [^1] out-of-order should throw"); + list.Insert(index: 3, item: 9); + Assert.IsTrue(list.SequenceEqual([ 1, 4, 7, 9, 10 ]), "expecting [ 1, 4, 7, 9, 10 ]"); + Assert.ThrowsException(() => list.Insert(index: 1, item: 9), "setting [1] out-of-order should throw"); + list.Insert(index: 1, item: 3); + Assert.IsTrue(list.SequenceEqual([ 1, 3, 4, 7, 9, 10 ]), "expecting [ 1, 3, 4, 7, 9, 10 ]"); + Assert.ThrowsException(() => list.Insert(index: 0, item: 9), "setting [0] out-of-order should throw"); + list.Insert(index: 0, item: 0); + Assert.IsTrue(list.SequenceEqual([ 0, 1, 3, 4, 7, 9, 10 ]), "expecting [ 0, 1, 3, 4, 7, 9, 10 ]"); + } [TestMethod] [DataRow(new[] {1, 5, 9, 10, 11, 12}, new[] {1, 5, 9}, 9)] @@ -37,5 +54,21 @@ namespace BizHawk.Tests.Common.CustomCollections sortlist.RemoveAfter(removeItem); Assert.IsTrue(sortlist.ToArray().SequenceEqual(after)); } + + [TestMethod] + public void TestSortedListSetIndexer() + { + SortedList list = new([ 1, 3, 4 ]); + Assert.ThrowsException(() => list[1] = 9, "setting [1] out-of-order should throw"); + list[1] = 2; + Assert.IsTrue(list.SequenceEqual([ 1, 2, 4 ]), "expecting [ 1, 2, 4 ]"); + Assert.ThrowsException(() => list[0] = 9, "setting [0] out-of-order should throw"); + list[0] = 0; + Assert.ThrowsException(() => list[2] = 0, "setting [^1] out-of-order should throw"); + list[2] = 9; + Assert.ThrowsException(() => list[3] = 0, "setting [^0] (appending) out-of-order should throw"); + Assert.ThrowsException(() => list[3] = 10, "setting [^0] (appending) properly should throw"); // to match BCL `List` + Assert.IsTrue(list.SequenceEqual([ 0, 2, 9 ]), "expecting [ 0, 2, 9 ]"); + } } }