Skip to content

Commit

Permalink
Add ROLSlice<TList,T> for slicing IReadOnlyList<T>
Browse files Browse the repository at this point in the history
This is an improvement over Slice_<T> which can't slice BCL lists,
though Slice_<T>.TryGet would often perform better for Loyc lists.
Includes unit tests for the new slice type and two existing ones.

Also:
- rename `Throws` to `ThrowsAny` to match convention in other libraries
- `AListBase.Slice` should have a default parameter
  • Loading branch information
qwertie committed Mar 30, 2020
1 parent 3ff8c2b commit 5bdce68
Show file tree
Hide file tree
Showing 15 changed files with 299 additions and 25 deletions.
4 changes: 2 additions & 2 deletions Core/AssemblyVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
// command to change the version number which, I guess, produces an incompatible
// assembly in the presence of strong names (strong naming prevents two assemblies
// from linking together without an exact match.)
[assembly: AssemblyVersion("2.7.1.3")]
[assembly: AssemblyFileVersion("2.7.1.3")]
[assembly: AssemblyVersion("2.7.1.4")]
[assembly: AssemblyFileVersion("2.7.1.4")]
2 changes: 1 addition & 1 deletion Core/Loyc.Collections/ALists/AList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,7 @@ public AListBase<T> RemoveSection(int start, int count)
return cov_RemoveSection(start, count);
}

public new ListSlice<T> Slice(int start, int length)
public new ListSlice<T> Slice(int start, int length = int.MaxValue)
{
return new ListSlice<T>(this, start, length);
}
Expand Down
143 changes: 143 additions & 0 deletions Core/Loyc.Essentials/Collections/Adapters/ROLSlice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Loyc.Collections;
using Loyc.Math;

namespace Loyc.Collections
{
public static partial class ListExt
{
public static ROLSlice<TList, T> Slice<TList, T>(this TList list, int start, int count = int.MaxValue) where TList : IReadOnlyList<T>
=> new ROLSlice<TList, T>(list, start, count);
}

/// <summary>Adapter: a random-access range for a slice of an <see cref="IReadOnlyList{T}"/>.</summary>
/// <typeparam name="T">Item type in the list</typeparam>
/// <typeparam name="TList">List type</typeparam>
public struct ROLSlice<TList, T> : IListSource<T>, IRange<T>, ICloneable<ROLSlice<TList,T>> where TList : IReadOnlyList<T>
{
TList _list;
int _start, _count;

/// <summary>Initializes a slice.</summary>
/// <exception cref="ArgumentException">The start index was below zero.</exception>
/// <remarks>The (start, count) range is allowed to be invalid, as long
/// as 'start' and 'count' are zero or above.
/// <ul>
/// <li>If 'start' is above the original Count, the Count of the new slice
/// is set to zero.</li>
/// <li>if (start + count) is above the original Count, the Count of the new
/// slice is reduced to <c>list.Count - start</c>.</li>
/// </ul>
/// </remarks>
public ROLSlice(TList list, int start, int count = int.MaxValue)
{
_list = list;
_start = start;
_count = count;
if (start < 0) throw new ArgumentException("The start index was below zero.");
if (count < 0) throw new ArgumentException("The count was below zero.");
if (count > _list.Count - start)
_count = System.Math.Max(_list.Count - start, 0);
}
public ROLSlice(TList list)
{
_list = list;
_start = 0;
_count = list.Count;
}

public int Count
{
get { return _count; }
}
public bool IsEmpty
{
get { return _count == 0; }
}
public T First
{
get { return this[0]; }
}
public T Last
{
get { return this[_count - 1]; }
}

public T PopFirst(out bool empty)
{
if (_count != 0)
{
empty = false;
_count--;
return _list[_start++];
}
empty = true;
return default(T);
}
public T PopLast(out bool empty)
{
if (_count != 0)
{
empty = false;
_count--;
return _list[_start + _count];
}
empty = true;
return default(T);
}

ROLSlice<TList, T> ICloneable<ROLSlice<TList, T>>.Clone() => this;
IRange<T> ICloneable<IRange<T>>.Clone() => this;
IFRange<T> ICloneable<IFRange<T>>.Clone() => this;
IBRange<T> ICloneable<IBRange<T>>.Clone() => this;

IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
public RangeEnumerator<ROLSlice<TList, T>, T> GetEnumerator()
{
return new RangeEnumerator<ROLSlice<TList, T>, T>(this);
}

public T this[int index]
{
get {
if ((uint)index < (uint)_count)
return _list[_start + index];
throw new ArgumentOutOfRangeException(nameof(index));
}
}
public T this[int index, T defaultValue]
{
get {
if ((uint)index < (uint)_count)
return _list[_start + index];
return defaultValue;
}
}
public T TryGet(int index, out bool fail)
{
int i = _start + index;
if (!(fail = (uint)index >= (uint)_count || (uint)i >= (uint)_list.Count))
return _list[i];
else
return default(T);
}

IRange<T> IListSource<T>.Slice(int start, int count) => Slice(start, count);
public ROLSlice<TList, T> Slice(int start, int count = int.MaxValue)
{
if (start < 0) throw new ArgumentException("The start index was below zero.");
if (count < 0) throw new ArgumentException("The count was below zero.");
var slice = new ROLSlice<TList, T>();
slice._list = this._list;
slice._start = this._start + start;
slice._count = count;
if (slice._count > this._count - start)
slice._count = System.Math.Max(this._count - start, 0);
return slice;
}
}
}
1 change: 1 addition & 0 deletions Core/Loyc.Essentials/Collections/Adapters/Slice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static IRange<T> AsRange<T>(this IRange<T> list)
/// </remarks>
public struct Slice_<T> : IRange<T>, ICloneable<Slice_<T>>, IIsEmpty
{
[Obsolete("I doubt anyone is using this.")]
public static readonly Slice_<T> Empty = new Slice_<T>();

IListSource<T> _list;
Expand Down
1 change: 1 addition & 0 deletions Core/Loyc.Essentials/Loyc.Essentials.net45.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
<Compile Include="..\AssemblyVersion.cs">
<Link>Properties\AssemblyVersion.cs</Link>
</Compile>
<Compile Include="Collections\Adapters\ROLSlice.cs" />
<Compile Include="Collections\Adapters\SelectDictionaryFromKeys.cs" />
<Compile Include="Collections\ExtensionMethods\IPush, IPop.cs" />
<Compile Include="Collections\ExtensionMethods\LinqToLists.out.cs">
Expand Down
26 changes: 18 additions & 8 deletions Core/Loyc.Essentials/Utilities/MiniTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Loyc.Math;
using System.Collections;
Expand Down Expand Up @@ -536,7 +536,7 @@ public static void Expect(bool condition)
/// <param name="code">A method to run</param>
/// <param name="message">The message that will be displayed on failure</param>
/// <param name="args">Arguments to be used in formatting the message</param>
public static Exception Throws(Type expectedExceptionType, Action code, string message, params object[] args)
public static Exception ThrowsAny(Type expectedExceptionType, Action code, string message, params object[] args)
{
try {
code();
Expand All @@ -549,18 +549,28 @@ public static Exception Throws(Type expectedExceptionType, Action code, string m
return null; // normally unreachable
}

public static Exception Throws(Type expectedExceptionType, Action code)
public static Exception ThrowsAny(Type expectedExceptionType, Action code)
{
return Throws(expectedExceptionType, code, null, null);
return ThrowsAny(expectedExceptionType, code, null, null);
}

[Obsolete("Use ThrowsAny (this method inadvertantly means ThrowsAny anyway)")]
public static T Throws<T>(Action code, string message, params object[] args) where T : Exception
{
return (T)Throws(typeof(T), code, message, args);
return (T)ThrowsAny(typeof(T), code, message, args);
}
[Obsolete("Use ThrowsAny (this method inadvertantly means ThrowsAny anyway)")]
public static T Throws<T>(Action code) where T : Exception
{
return (T)Throws(typeof(T), code);
return (T)ThrowsAny(typeof(T), code);
}
public static T ThrowsAny<T>(Action code, string message, params object[] args) where T : Exception
{
return (T)ThrowsAny(typeof(T), code, message, args);
}
public static T ThrowsAny<T>(Action code) where T : Exception
{
return (T)ThrowsAny(typeof(T), code);
}

/// <summary>
Expand All @@ -571,7 +581,7 @@ public static T Throws<T>(Action code) where T : Exception
/// <param name="args">Arguments to be used in formatting the message</param>
public static Exception Catch(Action code, string message, params object[] args)
{
return Throws(typeof(Exception), code, message, args);
return ThrowsAny(typeof(Exception), code, message, args);
}

/// <summary>
Expand All @@ -581,7 +591,7 @@ public static Exception Catch(Action code, string message, params object[] args)
/// <param name="code">A TestDelegate</param>
public static Exception Catch(Action code)
{
return Throws(typeof(Exception), code);
return ThrowsAny(typeof(Exception), code);
}

/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions Core/Tests/Collections/ALists/SparseAListTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,9 @@ public void TestClearSpace()
Assert.AreEqual(0, list[i1 - 1]);
if (_testExceptions)
{
Assert.Throws<ArgumentOutOfRangeException>(() => { var _ = list[i1]; });
Assert.Throws<ArgumentOutOfRangeException>(() => { list.ClearSpace(0, -1); });
Assert.Throws<ArgumentOutOfRangeException>(() => { list.ClearSpace(-1, 10); });
Assert.ThrowsAny<ArgumentOutOfRangeException>(() => { var _ = list[i1]; });
Assert.ThrowsAny<ArgumentOutOfRangeException>(() => { list.ClearSpace(0, -1); });
Assert.ThrowsAny<ArgumentOutOfRangeException>(() => { list.ClearSpace(-1, 10); });
}
list.ClearSpace(i0, i2 - i0);
Assert.AreEqual(i2, list.Count);
Expand Down
4 changes: 2 additions & 2 deletions Core/Tests/Collections/DictionaryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void TestAllTheBasics()
Assert.AreEqual(value, null);
Assert.AreEqual(1, dict["A"]);
Assert.AreEqual(2, dict[1]);
Assert.Throws(typeof(KeyNotFoundException), () => { var _ = dict["C"]; });
Assert.ThrowsAny(typeof(KeyNotFoundException), () => { var _ = dict["C"]; });
dict.Clear();
ExpectSet(dict);

Expand Down Expand Up @@ -94,7 +94,7 @@ public void TestAllTheBasics()
Assert.IsFalse(Remove(dict, 2.0, 2.0));
Assert.IsTrue(Remove(dict, 2.0, null));
ExpectSet(dict, P("2", null), P(2F, null), P(2UL, "You're a 2ul!"));
Assert.Throws(typeof(ArgumentException), () => dict.Add("2", 2));
Assert.ThrowsAny(typeof(ArgumentException), () => dict.Add("2", 2));
Assert.IsNull(dict["2"]);
dict["2"] = 2;
Assert.AreEqual(2, dict["2"]);
Expand Down
4 changes: 2 additions & 2 deletions Core/Tests/Collections/HeapTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public void TestPopMax()
Assert.AreEqual(7, heap.Pop());
Assert.AreEqual(6, heap.Pop());
Assert.AreEqual(1, heap.Pop());
Assert.Throws<EmptySequenceException>(() => heap.Pop());
Assert.ThrowsAny<EmptySequenceException>(() => heap.Pop());
}

[Test]
Expand All @@ -37,7 +37,7 @@ public void TestPopMin()
Assert.AreEqual(6, heap.Pop());
Assert.AreEqual(9, heap.Pop());
Assert.AreEqual(11, heap.Pop());
Assert.Throws<EmptySequenceException>(() => heap.Pop());
Assert.ThrowsAny<EmptySequenceException>(() => heap.Pop());
}

[Test]
Expand Down
2 changes: 1 addition & 1 deletion Core/Tests/Collections/ReadOnlyDictionaryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void GeneralTests()
AreEqual(_expect[k], _dict[k]);
} else {
AreEqual(default(V), v);
Throws<KeyNotFoundException>(() => { var _ = _dict[k]; });
ThrowsAny<KeyNotFoundException>(() => { var _ = _dict[k]; });
}
}
}
Expand Down
Loading

0 comments on commit 5bdce68

Please sign in to comment.