Skip to content

Commit

Permalink
minor: add array linq extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasteles committed Apr 26, 2023
1 parent c8aa403 commit a2ebc49
Show file tree
Hide file tree
Showing 16 changed files with 250 additions and 19 deletions.
2 changes: 1 addition & 1 deletion src/AssemblyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ public static IEnumerable<T> InstantiateAllImplementations<T>(this Assembly asse
? Activator.CreateInstance(t)
: t.GetConstructor(Type.EmptyTypes)
?.Invoke(Array.Empty<object>()))
.NotNullOnly()
.WhereNotNull()
.Cast<T>();
}
51 changes: 51 additions & 0 deletions src/EnumerablePlus/LinqArrayExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Collections.Generic;

namespace System.Linq;

/// <summary>
/// Enumerable Plus Extensions
/// </summary>
public static partial class EnumerablePlus
{
/// <summary>
/// Filters an array of values based on a predicate.
/// </summary>
public static TSource[]
WhereArray<TSource>(this TSource[] @this, Func<TSource, bool> predicate) =>
Array.FindAll(@this, new Predicate<TSource>(predicate));

/// <summary>
/// Sorts the elements of an array in ascending order according to a key.
/// </summary>
public static TSource[]
OrderByArray<TSource, TKey>(this TSource[] @this, Func<TSource, TKey> selector)
where TKey : IComparable<TKey>
{
var copy = new TSource[@this.Length];
Array.Copy(@this, copy, @this.Length);
Array.Sort(copy, KeyComparison(selector));
return copy;
}

/// <summary>
/// Sorts the elements of an array in ascending order.
/// </summary>
public static TSource[] OrderArray<TSource>(this TSource[] @this)
{
var copy = new TSource[@this.Length];
Array.Copy(@this, copy, @this.Length);
Array.Sort(copy);
return copy;
}

/// <summary>
/// Projects each element of an array into a new form.
/// </summary>
public static TResult[]
SelectArray<TSource, TResult>(this TSource[] @this, Func<TSource, TResult> selector) =>
Array.ConvertAll(@this, new Converter<TSource, TResult>(selector));

static Comparison<TSource> KeyComparison<TSource, TKey>(Func<TSource, TKey> key)
where TKey : IComparable<TKey> =>
(x, y) => Comparer<TKey>.Default.Compare(key(x), key(y));
}
9 changes: 4 additions & 5 deletions src/EnumerablePlus/LinqEnumerablePlus.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace System.Linq;

/// <summary>
/// Enumerable Plus Extensions
Expand Down Expand Up @@ -69,7 +69,7 @@ public static (T? Min, T? Max) MinMax<T>(this IEnumerable<T> @this)
/// <param name="enumerable"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static IEnumerable<T> NotNullOnly<T>(this IEnumerable<T?> enumerable) where T : class =>
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> enumerable) where T : class =>
enumerable.Where(e => e is not null).Cast<T>();

/// <summary>
Expand All @@ -78,11 +78,10 @@ public static (T? Min, T? Max) MinMax<T>(this IEnumerable<T> @this)
/// <param name="enumerable"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static IEnumerable<T> NotNullOnly<T>(this IEnumerable<T?> enumerable)
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> enumerable)
where T : struct =>
enumerable.Where(e => e is not null).Select(e => e!.Value);


/// <summary>
/// Enumerate the enumerable to a collection of value tuples (int Index, T Value)
/// </summary>
Expand Down
4 changes: 3 additions & 1 deletion src/EnumerablePlus/Operators/Repeat.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

/// <summary>
/// Enumerable Plus Extensions
/// </summary>
public static partial class EnumerablePlus
{
/// <summary>
Expand Down
1 change: 0 additions & 1 deletion src/EnumerablePlus/Operators/Until.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;

public static partial class EnumerablePlus
{
Expand Down
1 change: 1 addition & 0 deletions src/EnumerablePlus/StringEnumerablePlus.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;

namespace System.Linq;
/// <summary>
/// Enumerable Plus Extensions
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions src/EnumerablePlus/TaskEnumerablePlus.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Threading.Tasks;

namespace System.Linq;

/// <summary>
/// Enumerable Plus Extensions
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/EnumerablePlus/TupleEnumerablePlus.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace System.Linq;

/// <summary>
/// Enumerable Plus Extensions
Expand Down
2 changes: 1 addition & 1 deletion src/Enumeration.cs → src/EnumerationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
/// <summary>
/// Enum extensions
/// </summary>
public static class Enumeration
public static class EnumerationExtensions
{
static TAttr? GetAttribute<TEnum, TAttr>(this TEnum value)
where TEnum : Enum
Expand Down
17 changes: 14 additions & 3 deletions src/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,28 @@ namespace CSharpPlus;
public static class ExpressionExtensions
{
/// <summary>
/// Get the member name of an expression
/// Get the member name of an expression if it is valid otherwise return null
/// </summary>
/// <param name="expression"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public static string GetMemberName<T>(this Expression<T> expression) =>
public static string? GetMemberNameOrNull<T>(this Expression<T> expression) =>
expression.Body switch
{
MemberExpression m => m.Member.Name,
UnaryExpression { Operand: MemberExpression m } => m.Member.Name,
_ => throw new NotImplementedException(expression.GetType().ToString())
_ => null,
};

/// <summary>
/// Get the member name of an expression
/// </summary>
/// <param name="expression"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public static string GetMemberName<T>(this Expression<T> expression) =>
expression.GetMemberNameOrNull() ??
throw new NotImplementedException(expression.GetType().ToString());
}
5 changes: 2 additions & 3 deletions src/JsonConverters/Base/Converters.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Globalization;

namespace CSharpPlus.JsonConverters.Base;

Expand All @@ -14,7 +13,7 @@ class JsonEnumMemberValueConverter<TEnum> : JsonEnumCustomStringConverter<TEnum>
protected override string GetCustomString(TEnum value) => value.GetEnumMemberValue();

protected override TEnum? GetValueFromString(string value, StringComparison comparison) =>
Enumeration.GetEnumFromEnumMemberValue<TEnum>(value, comparison);
EnumerationExtensions.GetEnumFromEnumMemberValue<TEnum>(value, comparison);
}

class JsonEnumDescriptionConverter<TEnum> : JsonEnumCustomStringConverter<TEnum>
Expand All @@ -28,7 +27,7 @@ class JsonEnumDescriptionConverter<TEnum> : JsonEnumCustomStringConverter<TEnum>
protected override string GetCustomString(TEnum value) => value.GetDescription();

protected override TEnum? GetValueFromString(string value, StringComparison comparison) =>
Enumeration.GetEnumFromDescription<TEnum>(value, comparison);
EnumerationExtensions.GetEnumFromDescription<TEnum>(value, comparison);
}

class JsonEnumNumericAsStringConverter<TEnum> : JsonEnumCustomStringConverter<TEnum>
Expand Down
50 changes: 49 additions & 1 deletion tests/CSharpPlus.Tests/EnumerablePlus/LinqEnumerablePlusTests.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
// ReSharper disable PossibleMultipleEnumeration

using System.ComponentModel.DataAnnotations;
using FsCheck;

namespace CSharpPlus.Tests.EnumerablePlus;

public class LinqEnumerablePlusTests : BaseTest
{
[Test]
public void StringJoinSampleTest()
{
var collection = new[]
{
4, 8, 15, 16, 23, 42,
};

const string expected = "4;8;15;16;23;42";

collection.JoinString(";").Should().Be(expected);
}

[PropertyTest]
public void StringJoinChar(string[] value, char chr) =>
value.JoinString(chr).Should().Be(string.Join(chr, value));
Expand Down Expand Up @@ -129,4 +141,40 @@ public void RepeatForever(NonEmptyArray<int> array, PositiveInt times)

sut.Should().AllSatisfy(v => v.First.Should().Be(v.Second));
}

record TestRefType(string Id);

[Test]
public void FilterNullRefTypes()
{
TestRefType?[] items =
{
new("A"), null, new("B"), null, new("C"), null, null, new("D"),
};

TestRefType[] expected =
{
new("A"), new("B"), new("C"), new("D"),
};

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

record struct TestValueType(string Id);

[Test]
public void FilterNullValueTypes()
{
TestValueType?[] items =
{
new("A"), null, new("B"), null, new("C"), null, null, new("D"),
};

TestValueType[] expected =
{
new("A"), new("B"), new("C"), new("D"),
};

items.WhereNotNull().Should().BeEquivalentTo(expected);
}
}
20 changes: 20 additions & 0 deletions tests/CSharpPlus.Tests/ExpressionExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Linq.Expressions;

namespace CSharpPlus.Tests;

public class ExpressionExtensionsTests
{
record ClassWithMembers(int Foo, string Bar);

[Test]
public void ShouldGetClassMemberName()
{
Expression<Func<ClassWithMembers, object>> bar = x => x.Bar;
Expression<Func<ClassWithMembers, object>> foo = x => x.Foo;
Expression<Func<ClassWithMembers, object>> length = x => x.Bar.Length;

bar.GetMemberName().Should().Be(nameof(ClassWithMembers.Bar));
foo.GetMemberName().Should().Be(nameof(ClassWithMembers.Foo));
length.GetMemberName().Should().Be(nameof(string.Length));
}
}
94 changes: 94 additions & 0 deletions tests/CSharpPlus.Tests/LinqArrayExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
namespace CSharpPlus.Tests;

public class LinqArrayExtensionsTests
{
[Test]
public void ShouldFilterSampleArray()
{
int[] items =
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
};

int[] expected =
{
2, 4, 6, 8, 10, 11, 12, 13,
};

items.WhereArray(x => x % 2 == 0 || x.ToString().Length is 2)
.Should().BeEquivalentTo(expected);
}

[PropertyTest]
public void ShouldFilterArray(int[] items, Func<int, bool> pred)
{
var expected = items.Where(pred).ToArray();
items.WhereArray(pred).Should().BeEquivalentTo(expected);
}

[Test]
public void ShouldMapSampleTest()
{
int[] items =
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
};

string[] expected =
{
"odd[1]",
"even[2]",
"odd[3]",
"even[4]",
"odd[5]",
"even[6]",
"odd[7]",
"even[8]",
"odd[9]",
"even[10]",
};

items.SelectArray(x => $"{(x % 2 == 0 ? "even" : "odd")}[{x}]")
.Should().BeEquivalentTo(expected);
}

[PropertyTest]
public void ShouldMapArray(int[] items)
{
string Selector(int x) => $"{(x % 2 == 0 ? "even" : "odd")}[{x}]";
var expected = items.Select(Selector).ToArray();
items.SelectArray(Selector).Should().BeEquivalentTo(expected);
}


[Test]
public void ShouldOrderSampleArray()
{
int[] items =
{
10, 5, 3, 8, 2, 4, 9, 6, 1, 7,
};

int[] expected =
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
};

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

[PropertyTest]
public void ShouldOrderArray(int[] items)
{
var expected = items.OrderBy(x => x).ToArray();
items.OrderArray().Should().BeEquivalentTo(expected);
}

[PropertyTest]
public void ShouldOrderByArray(int[] items, Func<int, int> pred)
{
var expected = items.OrderBy(pred).ToArray();
items.OrderByArray(pred).Should().BeEquivalentTo(expected);
}
}
1 change: 0 additions & 1 deletion tests/CSharpPlus.Tests/Usings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@
global using CSharpPlus.Tests.Utils;
global using FluentAssertions;
global using NUnit.Framework;
global using PropertyTestAttribute = FsCheck.NUnit.PropertyAttribute;
6 changes: 6 additions & 0 deletions tests/CSharpPlus.Tests/Utils/PropertyTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace CSharpPlus.Tests.Utils;

public sealed class PropertyTestAttribute : FsCheck.NUnit.PropertyAttribute
{
public PropertyTestAttribute() => this.QuietOnSuccess = true;
}

0 comments on commit a2ebc49

Please sign in to comment.