Skip to content

Commit

Permalink
Merge pull request #139 from ColmBhandal/feature/array-compare
Browse files Browse the repository at this point in the history
Feature/array compare
  • Loading branch information
ColmBhandal authored Mar 7, 2022
2 parents 51f992c + b5c03c6 commit d5a65fb
Show file tree
Hide file tree
Showing 13 changed files with 560 additions and 37 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added zip methods for sparse array and sparse array 2D (#132)
- Fixed bug: default values were not overwriting non-defaults for sparse arrays (#134)
- Added ZipEnum methods to 1D and 2D arrays via extension methods and also added one-based equivalent functions (#136)
- Added Compare functions for comparing 1D and 2D arrays, both one and zero-based. (#138)

### Added
- Support for writing a 1D one-based array to a 2D one-based array (#9)
Expand Down
59 changes: 59 additions & 0 deletions CsharpExtras/Compare/Array/ArrayComparisonResultImpl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CsharpExtras.Compare.Array
{
internal class ArrayComparisonResultImpl<TVal> : IComparisonResult
{
private readonly int _indexBase;
public bool IsEqual => MessageAndIsEqual.isEqual;
public string Message => MessageAndIsEqual.message;

private (string, bool)? _messageAndIsEqual;
private (string message, bool isEqual) MessageAndIsEqual => _messageAndIsEqual ??= GetMessageAndIsEqual();

private IEnumerable<int> ThisShape { get; }
private IEnumerable<int> OtherShape { get; }

private (IEnumerable<int>, TVal)? FirstMismatchZeroBased { get; }

private string? OtherValMistmachOrNull { get; }

public ArrayComparisonResultImpl(IEnumerable<int> thisShape,
IEnumerable<int> otherShape,
int indexBase,
(IEnumerable<int>, TVal)? firstMismatch,
string? otherValMistmachOrNull)
{
ThisShape = thisShape ?? throw new ArgumentNullException(nameof(thisShape));
OtherShape = otherShape ?? throw new ArgumentNullException(nameof(otherShape));
FirstMismatchZeroBased = firstMismatch;
OtherValMistmachOrNull = otherValMistmachOrNull;
_indexBase = indexBase;
}

private (string message, bool isEqual) GetMessageAndIsEqual()
{
if (!Enumerable.SequenceEqual(ThisShape, OtherShape))
{
return ($"Shape mismatch. This array has shape {PrintTuple(ThisShape)}. " +
$"Other array has shape {PrintTuple(OtherShape)}.",
false);
}
if (FirstMismatchZeroBased is (IEnumerable<int> k, TVal v))
{
string otherVal = OtherValMistmachOrNull ?? "NOT FOUND";
return ($"Mismatch found at indices {PrintTuple(k.Select(i => i+_indexBase))}. " +
$"This value: {v}. Other value: {otherVal}", false);
}
return ("Arrays are equal", true);
}

private string PrintTuple(IEnumerable<int> tuple)
{
return string.Join(",", tuple.Select(i => i.ToString()));
}
}
}
10 changes: 10 additions & 0 deletions CsharpExtras/Enumerable/OneBased/IOneBasedArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using static CsharpExtras.Extensions.ArrayOrientationClass;
using static CsharpExtras.Extensions.ArrayExtension;
using CsharpExtras.Map.Dictionary.Collection;
using CsharpExtras.Compare;

namespace CsharpExtras._Enumerable.OneBased
{
Expand Down Expand Up @@ -196,5 +197,14 @@ public interface IOneBasedArray<TVal> : IEnumerable<TVal>
/// and the values of which are the result of applying the zipper across all values at the corresponding indices
/// in all the arrays.</returns>
IOneBasedArray<TResult> ZipEnum<TOther, TResult>(Func<TVal, IEnumerable<TOther>, TResult> zipper, IEnumerable<IOneBasedArray<TOther>> others);

/// <summary>
/// Compares this array to the other array value-by-value
/// </summary>
/// <param name="other">The other array against which to compare</param>
/// <param name="isEqualValues">A function which checks if values are equal</param>
/// <returns>The result of the comparison, which can be used to determine if the arrays are equal according to the value-equality function passed.
/// If the arrays are of a different shape, then the comparison will always be unequal</returns>
IComparisonResult Compare(IOneBasedArray<TVal> other, Func<TVal, TVal, bool> isEqualValues);
}
}
12 changes: 11 additions & 1 deletion CsharpExtras/Enumerable/OneBased/IOneBasedArray2D.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using CsharpExtras.Compare;
using System;
using System.Collections.Generic;

namespace CsharpExtras._Enumerable.OneBased
Expand Down Expand Up @@ -81,5 +82,14 @@ public interface IOneBasedArray2D<TVal> : IEnumerable<TVal>
/// and the values of which are the result of applying the zipper across all values at the corresponding indices
/// in all the arrays.</returns>
IOneBasedArray2D<TResult> ZipEnum<TOther, TResult>(Func<TVal, IEnumerable<TOther>, TResult> zipper, IEnumerable<IOneBasedArray2D<TOther>> others);

/// <summary>
/// Compares this array to the other array value-by-value
/// </summary>
/// <param name="other">The other array against which to compare</param>
/// <param name="isEqualValues">A function which checks if values are equal</param>
/// <returns>The result of the comparison, which can be used to determine if the arrays are equal according to the value-equality function passed.
/// If the arrays are of a different shape, then the comparison will always be unequal</returns>
IComparisonResult Compare(IOneBasedArray2D<TVal> other, Func<TVal, TVal, bool> isEqualValues);
}
}
6 changes: 6 additions & 0 deletions CsharpExtras/Enumerable/OneBased/OneBasedArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
using static CsharpExtras.Extensions.ArrayOrientationClass;
using CsharpExtras.Map.Dictionary.Collection;
using System.Linq;
using CsharpExtras.Compare;
using CsharpExtras.Compare.Array;
using CsharpExtras.Extensions.Helper;

namespace CsharpExtras._Enumerable.OneBased
{
Expand Down Expand Up @@ -40,6 +43,9 @@ public TVal this[int oneBasedIndex]

public TVal[] ZeroBasedEquivalent => _backingArray;

public IComparisonResult Compare(IOneBasedArray<TVal> other, Func<TVal, TVal, bool> isEqualValues)
=> ZeroBasedEquivalent.Compare(other.ZeroBasedEquivalent, isEqualValues, 1);

public IEnumerator<TVal> GetEnumerator()
{
for(int i = 0; i < Length; i++)
Expand Down
41 changes: 25 additions & 16 deletions CsharpExtras/Enumerable/OneBased/OneBasedArray2D.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using CsharpExtras.Extensions;
using CsharpExtras.Compare;
using CsharpExtras.Compare.Array;
using CsharpExtras.Extensions;
using CsharpExtras.Extensions.Helper;
using System;
using System.Collections;
using System.Collections.Generic;
Expand All @@ -8,27 +11,16 @@ namespace CsharpExtras._Enumerable.OneBased
{
class OneBasedArray2DImpl<TVal> : IOneBasedArray2D<TVal>
{
public int LastUsedRow(Predicate<TVal> isUsed)
{
int zeroBasedRow = ZeroBasedEquivalent.LastUsedRow(isUsed);
if (zeroBasedRow < 0) return zeroBasedRow;
return zeroBasedRow + 1;
}
public int LastUsedColumn(Predicate<TVal> isUsed)
{
int zeroBasedColumn = ZeroBasedEquivalent.LastUsedColumn(isUsed);
if (zeroBasedColumn < 0) return zeroBasedColumn;
return zeroBasedColumn + 1;
}

public TVal[,] ZeroBasedEquivalent { get; }

public OneBasedArray2DImpl(TVal[,] backingArray)
public OneBasedArray2DImpl(int rows, int columns) : this(new TVal[rows, columns])
{
ZeroBasedEquivalent = backingArray;
}

public OneBasedArray2DImpl(int rows, int columns) : this(new TVal[rows, columns])
public OneBasedArray2DImpl(TVal[,] backingArray)
{
ZeroBasedEquivalent = backingArray;
}

public TVal this[int oneBasedIndex0, int oneBasedIndex1]
Expand All @@ -45,6 +37,23 @@ public OneBasedArray2DImpl(int rows, int columns) : this(new TVal[rows, columns]
}
}

public IComparisonResult Compare(IOneBasedArray2D<TVal> other,
Func<TVal, TVal, bool> isEqualValues) =>
ZeroBasedEquivalent.Compare(other.ZeroBasedEquivalent, isEqualValues, 1);

public int LastUsedRow(Predicate<TVal> isUsed)
{
int zeroBasedRow = ZeroBasedEquivalent.LastUsedRow(isUsed);
if (zeroBasedRow < 0) return zeroBasedRow;
return zeroBasedRow + 1;
}
public int LastUsedColumn(Predicate<TVal> isUsed)
{
int zeroBasedColumn = ZeroBasedEquivalent.LastUsedColumn(isUsed);
if (zeroBasedColumn < 0) return zeroBasedColumn;
return zeroBasedColumn + 1;
}

private void ValidateIndices(int oneBasedIndex0, int oneBasedIndex1)
{
int len0 = ZeroBasedEquivalent.GetLength(0);
Expand Down
40 changes: 23 additions & 17 deletions CsharpExtras/Extensions/ArrayExtension.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using CsharpExtras.Map.Dictionary;
using CsharpExtras.Compare;
using CsharpExtras.Compare.Array;
using CsharpExtras.Extensions.Helper;
using CsharpExtras.Map.Dictionary;
using CsharpExtras.Map.Dictionary.Collection;
using System;
using System.Collections.Generic;
Expand All @@ -9,9 +12,12 @@ namespace CsharpExtras.Extensions
{
public static class ArrayExtension
{
public static IComparisonResult Compare<TVal>(this TVal[] arr, TVal[] other, Func<TVal, TVal, bool> isEqualValues)
=> arr.Compare(other, isEqualValues, 0);

//Non-mvp: Test this
/// <returns>A pair indicating the first element found and its index, or (-1, default) if nothing found.</returns>
public static (int index, T element)? FindFirstOccurrenceOfSet<T>(this T[] arr, ISet<T> set)
public static (int index, TVal element)? FindFirstOccurrenceOfSet<TVal>(this TVal[] arr, ISet<TVal> set)
{
return FindFirstOccurrenceOfSet(arr, set, 0, arr.Length);
}
Expand All @@ -20,11 +26,11 @@ public static (int index, T element)? FindFirstOccurrenceOfSet<T>(this T[] arr,
/// <param name="startIndex">Start searching the array from this index inclusive i.e. don't look at lower indices</param>
/// <param name="endIndex">Stop searching the array beyond this index, and don't include this index in the search</param>
/// <returns>A pair indicating the first element found and its index, or null if nothing found.</returns>
public static (int index, T element)? FindFirstOccurrenceOfSet<T>(this T[] arr, ISet<T> set, int startIndex, int endIndex)
public static (int index, TVal element)? FindFirstOccurrenceOfSet<TVal>(this TVal[] arr, ISet<TVal> set, int startIndex, int endIndex)
{
for (int i = startIndex; i < arr.Length && i < endIndex; i++)
{
T element = arr[i];
TVal element = arr[i];
if (set.Contains(element))
{
return (i, element);
Expand All @@ -40,18 +46,18 @@ public static (int index, T element)? FindFirstOccurrenceOfSet<T>(this T[] arr,
/// <param name="startAt">Index to start at (inclusive). Negative indices will be truncated to zero.</param>
/// <param name="stopBefore">Index before which to stop. Indices greater than array length will be truncated to the array length.</param>
/// <returns></returns>
public static T[] SubArray<T>(this T[] data, int startAt, int stopBefore)
public static TVal[] SubArray<TVal>(this TVal[] data, int startAt, int stopBefore)
{
startAt = Math.Max(startAt, 0);
stopBefore = Math.Min(stopBefore, data.Length);

int length = stopBefore - startAt;
T[] result = new T[length];
TVal[] result = new TVal[length];
Array.Copy(data, startAt, result, 0, length);
return result;
}

public static T[] SubArray<T>(this T[] data, int startAt)
public static TVal[] SubArray<TVal>(this TVal[] data, int startAt)
{
return data.SubArray(startAt, data.Length);
}
Expand All @@ -67,16 +73,16 @@ public static string[] RemoveBlankEntries(this string[] data)
/// <param name="orientation">If ROW, then the 2D array created will have a single row,
/// whose values will match that of the source array. Else the 2D array will have a single column, matching the source array.</param>
/// <returns>A new 2D array whose values match that of the original 1D array and which is oriented according to the given orientation.</returns>
public static T[,] To2DArray<T>(this T[] array, ArrayOrientation orientation)
public static TVal[,] To2DArray<TVal>(this TVal[] array, ArrayOrientation orientation)
{
T[,] outputArray;
TVal[,] outputArray;
if (orientation == ArrayOrientation.COLUMN)
{
outputArray = new T[array.Length, 1];
outputArray = new TVal[array.Length, 1];
}
else
{
outputArray = new T[1, array.Length];
outputArray = new TVal[1, array.Length];
}

for (int i = 0; i < array.Length; i++)
Expand All @@ -94,21 +100,21 @@ public static string[] RemoveBlankEntries(this string[] data)
}

//TODO: Test
public static T[] DeepCopy<T>(this T[] array)
public static TVal[] DeepCopy<TVal>(this TVal[] array)
{
int length = array.Length;
T[] copy = new T[length];
TVal[] copy = new TVal[length];
array.CopyTo(copy, 0);
return copy;
}

public static IDictionary<T, IList<int>> Inverse<T>(this T[] array)
public static IDictionary<TVal, IList<int>> Inverse<TVal>(this TVal[] array)
{
IDictionary<T, IList<int>> inverseMap = new Dictionary<T, IList<int>>();
IDictionary<TVal, IList<int>> inverseMap = new Dictionary<TVal, IList<int>>();

for (int index = 0; index < array.Length; index++)
{
T value = array[index];
TVal value = array[index];
if (inverseMap.ContainsKey(value))
{
inverseMap[value].Add(index);
Expand All @@ -122,7 +128,7 @@ public static IDictionary<T, IList<int>> Inverse<T>(this T[] array)
return inverseMap;
}

public static IDictionary<T, IList<int>> FindDuplicateIndices<T>(this T[] array)
public static IDictionary<TVal, IList<int>> FindDuplicateIndices<TVal>(this TVal[] array)
{
return array.Inverse().FilterValues(lst => lst.Count > 1);
}
Expand Down
7 changes: 6 additions & 1 deletion CsharpExtras/Extensions/ArrayExtension2D.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using CsharpExtras.Compare;
using CsharpExtras.Compare.Array;
using CsharpExtras.Extensions.Helper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
Expand All @@ -8,6 +11,8 @@ namespace CsharpExtras.Extensions
{
public static class ArrayExtension2D
{
public static IComparisonResult Compare<TVal>(this TVal[,] arr, TVal[,] other,
Func<TVal, TVal, bool> isEqualValues) => arr.Compare(other, isEqualValues, 0);

/// <param name="p">A predicate on an entire column</param>
/// <returns>The last column where the predicate is true, or -1 if none found</returns>
Expand Down
Loading

0 comments on commit d5a65fb

Please sign in to comment.