Skip to content

Commit

Permalink
range exclusive and linq random operation
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasteles committed May 5, 2023
1 parent a2ebc49 commit 29ba443
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 21 deletions.
60 changes: 60 additions & 0 deletions src/EnumerablePlus/LinqEnumerablePlus.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace System.Linq;

Expand Down Expand Up @@ -139,4 +140,63 @@ public static void ForEach<T>(this IEnumerable<T> source, Action<T, int> action)
/// <returns>Shuffled enumerable</returns>
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random? random = null) =>
source.OrderBy(_ => (random ?? Random.Shared).Next());

/// <summary>
/// Returns a random item from collection
/// </summary>
/// <param name="source">The sequence of elements</param>
/// <param name="defaultValue"></param>
/// <param name="random"></param>
/// <typeparam name="T"></typeparam>
public static T? PickRandomOrDefault<T>(this IEnumerable<T> source,
T? defaultValue = default, Random? random = null)
{
var rnd = random ?? Random.Shared;
return source switch
{
IReadOnlyCollection<T> { Count: 0 } => default,
IReadOnlyList<T> { Count: > 0 } list => list[rnd.Next(list.Count)],
_ => source.Shuffle(rnd).FirstOrDefault(defaultValue),
};
}

/// <summary>
/// Returns a random item from collection
/// </summary>
/// <param name="source">The sequence of elements</param>
/// <param name="random"></param>
/// <typeparam name="T"></typeparam>
/// <returns>Shuffled enumerable</returns>
public static T PickRandom<T>(this IEnumerable<T> source, Random? random = null)
{
var rnd = random ?? Random.Shared;
return source switch
{
IReadOnlyList<T> list => list[rnd.Next(list.Count)],
_ => source.Shuffle(rnd).First(),
};
}

/// <summary>
/// Creates a IReadOnlyList from a IEnumerable.
/// </summary>
/// <param name="enumerable"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static IReadOnlyList<T> ToReadOnlyList<T>(this IEnumerable<T> enumerable) =>
enumerable switch
{
null => Array.Empty<T>(),
IList<T> list => new ReadOnlyCollection<T>(list),
_ => Array.AsReadOnly(enumerable.ToArray()),
};

/// <summary>
/// Creates a IReadOnlyCollection from a IEnumerable.
/// </summary>
/// <param name="enumerable"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static IReadOnlyCollection<T> ToReadOnly<T>(this IEnumerable<T> enumerable) =>
enumerable.ToReadOnlyList();
}
4 changes: 3 additions & 1 deletion src/JsonConverters/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ namespace System.Text.Json.Serialization;
/// <summary>
/// Sets enum to use default string value for json serialization
/// </summary>
[AttributeUsage(AttributeTargets.Enum)]
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct |
AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field)]
public sealed class JsonEnumStringAttribute : JsonConverterAttribute
{
/// <inheritdoc />
Expand Down
19 changes: 11 additions & 8 deletions src/RangeExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,28 @@ public static class RangeExtension
{
/// <summary>
/// Enumerate Range operators
/// ^ sets the value as exclusive
/// </summary>
/// <param name="range"></param>
/// <returns></returns>
public static IEnumerator<int> GetEnumerator(this Range range)
{
static int IndexToInt(Index index) =>
index.Value * (index.IsFromEnd ? -1 : 1);

var start = IndexToInt(range.Start);
var end = IndexToInt(range.End);
var (start, end) = (range.Start.Value, range.End.Value);

if (end > start)
{
if (range.End.IsFromEnd) end--;
if (range.Start.IsFromEnd) start++;
for (var i = start; i <= end; i++)
yield return i;
}
else
{
if (range.End.IsFromEnd) end++;
if (range.Start.IsFromEnd) start--;
for (var i = start; i >= end; i--)
yield return i;
}
}

/// <summary>
Expand Down Expand Up @@ -105,7 +110,6 @@ public static IEnumerable<int> Enumerate(this Range range)
public static IEnumerable<int> SelectMany(this Range range, Func<int, Range> projection) =>
range.SelectMany(projection, (_, n) => n);


/// <summary>
/// Creates an array from a Range
/// </summary>
Expand All @@ -122,12 +126,11 @@ public static IEnumerable<int> Enumerate(this Range range)
this Range range) =>
range.Enumerate().ToList();


/// <summary>
/// Creates an array from a Range
/// </summary>
/// <returns></returns>
public static IReadOnlyCollection<int> ToReadOnly(
this Range range) =>
range.Enumerate().ToImmutableArray();
range.Enumerate().ToReadOnly();
}
25 changes: 25 additions & 0 deletions tests/CSharpPlus.Tests/EnumerablePlus/LinqEnumerablePlusTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,29 @@ public void FilterNullValueTypes()

items.WhereNotNull().Should().BeEquivalentTo(expected);
}

[PropertyTest]
public void ShouldParseIListToReadOnly(NonEmptyArray<int> values)
{
var original = values.Item.ToArray();
var sut = (IList<int>)values.Item.ToReadOnly();
var action = () => sut[0]++;
action.Should().Throw<NotSupportedException>().WithMessage("Collection is read-only.");
values.Item.Should().BeEquivalentTo(original);
}

[PropertyTest]
public void ShouldParseNonIListToReadOnly(NonEmptySet<int> values)
{
var original = values.Item.ToArray();
var sut = (IList<int>)values.Item.ToReadOnly();
var action = () => sut.Add(42);
action.Should().Throw<NotSupportedException>().WithMessage("Collection is read-only.");
values.Item.Should().BeEquivalentTo(original);
}

[Test]
public void ShouldParseNullCollectionToReadOnly() =>
(null as int[])!.ToReadOnly().Should()
.BeEquivalentTo(Array.Empty<int>());
}
56 changes: 45 additions & 11 deletions tests/CSharpPlus.Tests/RangeExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ public class RangeExtensionsTests
public void ShouldEnumerateRange()
{
var numbers = (..10).Enumerate().ToArray();
var expected = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var expected = new[]
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
};
numbers.Should().BeEquivalentTo(expected);
}

Expand All @@ -17,15 +20,21 @@ public void ShouldBeIterable()
foreach (var n in ..10)
list.Add(n);

var expected = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var expected = new[]
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
};
list.Should().BeEquivalentTo(expected);
}

[Test]
public void ShouldEnumerateBackwardsRange()
{
var numbers = (10..0).Enumerate().ToArray();
var expected = new[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
var expected = new[]
{
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
};
numbers.Should().BeEquivalentTo(expected);
}

Expand All @@ -36,21 +45,42 @@ public void ShouldBeBackwardsIterable()
foreach (var n in 10..0)
list.Add(n);

var expected = new[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
var expected = new[]
{
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
};
list.Should().BeEquivalentTo(expected);
}

[Test]
public void ShouldIterateFromNegative()
public void ShouldIterateExclusive()
{
var list = new List<int>();
foreach (var n in ^10..1)
foreach (var n in ^0..^10)
list.Add(n);

var expected = new[] { -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, -0, 1 };
var expected = new[]
{
1, 2, 3, 4, 5, 6, 7, 8, 9
};
list.Should().BeEquivalentTo(expected);
}

[Test]
public void ShouldBeBackwardsExclusiveIterable()
{
var list = new List<int>();
foreach (var n in ^10..^0)
list.Add(n);

var expected = new[]
{
9, 8, 7, 6, 5, 4, 3, 2, 1
};
list.Should().BeEquivalentTo(expected);
}


[PropertyTest]
public void ShouldProject(Index begin, Index end, Func<int, int> map)
{
Expand All @@ -67,7 +97,6 @@ public void ShouldProjectMany(Index begin, Index end, Func<int, int[]> map)
sut.Should().BeEquivalentTo(expected);
}


[PropertyTest]
public void ShouldProjectLinq(Index begin, Index end, Func<int, int> map)
{
Expand Down Expand Up @@ -102,7 +131,8 @@ public void ShouldProjectLinq(Index begin, Index end, Func<int, int> map)
Func<int, int, int> map)
{
var sut = (begin1..end1).SelectMany(_ => (begin2..end2), map);
var expected = InclusiveRange(begin1, end1).SelectMany(_ => InclusiveRange(begin2, end2), map);
var expected = InclusiveRange(begin1, end1)
.SelectMany(_ => InclusiveRange(begin2, end2), map);
sut.Should().BeEquivalentTo(expected);
}

Expand All @@ -112,13 +142,17 @@ public void ShouldProjectLinq(Index begin, Index end, Func<int, int> map)
Index begin2, Index end2)
{
var sut = (begin1..end1).SelectMany(_ => begin2..end2).ToArray();
var expected = InclusiveRange(begin1, end1).SelectMany(_ => InclusiveRange(begin2, end2)).ToArray();
var expected = InclusiveRange(begin1, end1).SelectMany(_ => InclusiveRange(begin2, end2))
.ToArray();
sut.Should().BeEquivalentTo(expected);
}

static IEnumerable<int> InclusiveRange(Index begin, Index end)
{
var boundaries = new[] { end.Int(), begin.Int() };
var boundaries = new[]
{
end.Int(), begin.Int()
};
var (min, max) = (boundaries.Min(), boundaries.Max());
var size = Math.Abs(min - max) + 1;
return Enumerable.Range(min, size);
Expand Down
3 changes: 2 additions & 1 deletion tests/CSharpPlus.Tests/Utils/Generators.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FsCheck;
using Random = System.Random;

public readonly record struct DistinctNonEmptyArray<T>(T[] Items)
{
Expand All @@ -22,7 +23,7 @@ public static Arbitrary<Index> IndexGenerator()
{
var generator =
from v in Arb.From<int>().Generator
select new Index(Math.Abs(v), v < 0);
select new Index(Math.Abs(v));
return Arb.From(generator);
}

Expand Down

0 comments on commit 29ba443

Please sign in to comment.