FluentAssertions: Test if an Enumerable contains "subset" of itself - c#

I'm looking for the elegant expression to test if an enumerable contains a 'subset' of itself.
Lets me illustrate with a sample:
[Fact]
public void Test1()
{
// Arrange
var expected = new[]
{
new {F1 = 1, F2 = "1" },
new {F1 = 2, F2 = "2" },
};
// Act
var actual = new[]
{
new {F1 = 1, F2 = "1", F3 = true },
new {F1 = 2, F2 = "2", F3 = true },
new {F1 = 3, F2 = "3", F3 = true },
};
// Assert
actual
.Should()
.LookingForFluentAssertionsExpression( //<-- I'm looking for this part
expected,
options => options.SomeOptions(),
because: "expected is a 'subset' of actual"
);
}
I have tried unsuccessfully with Object graph comparison:
// Assert
actual
.Should()
.BeEquivalentTo(expected, o => o.ExcludingMissingMembers());
Expected actual to be a collection with 2 item(s), but {{ F1 = 1, F2 = 1, F3 = True }, { F1 = 2, F2 = 2, F3 = True }, { F1 = 3, F2 = 3, F3 = True }}"
"contains 1 item(s) more than"
"{{ F1 = 1, F2 = 1 }, { F1 = 2, F2 = 2 }}.
Obviously, I can do:
// Assert
actual
.Where(a => expected.Any(e=>e.F1 == a.F1 && e.F2 == a.F2))
.Should()
.BeEquivalentTo(expected, o => o.ExcludingMissingMembers());
but looks a bit dirty.
Another option is:
// Assert
expected
.ToList()
.ForEach(expectedItem =>
actual
.Should()
.ContainEquivalentOf(
expectation: expectedItem,
config: o => o.ExcludingMissingMembers())
);
// Or the same without Linq:
//
//foreach (var expectedItem in expected)
//{
// actual
// .Should()
// .ContainEquivalentOf(
// expectation: expectedItem,
// config: o => o.ExcludingMissingMembers());
//}
but is not readable at all.

You can use two calls of ContainEquivalentOf where you pass an item from the expected collection.
e.g.
using (new AssertionScope())
{
actual.Should().ContainEquivalentOf(expected[0]);
actual.Should().ContainEquivalentOf(expected[1]);
}
And you don't need ExcludingMissingMembers. FA will only expect the properties you defined on your expectation.

At the end, I coded it using a ClassData. I don't know if this is the best solution, I post it here with the other answers. Feel free to criticize this answer. We are here to learn.
public class CalculatorTestData : IEnumerable<object[]>
{
public IEnumerator<object[]> GetEnumerator()
{
yield return new object[] { new { F1 = 1, F2 = "1" }, "reason 1" };
yield return new object[] { new { F1 = 2, F2 = "2" }, "reason 2" };
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
[Theory]
[ClassData(typeof(CalculatorTestData))]
public void Test2(object expected, string because)
{
// Arrange
// Act
var actual = new[]
{
new {F1 = 1, F2 = "1", F3 = true },
new {F1 = 2, F2 = "2", F3 = true },
new {F1 = 3, F2 = "3", F3 = true },
};
// Assert
actual
.Should()
.ContainEquivalentOf(
expectation: expected,
because: because);
}

Related

Change big collection into unique List of collections

I have a big list of objects and in this object there is a category ID something like:
var list = new List<Example>
{
new Example {CatId = 1, Value = new { }},
new Example {CatId = 1, Value = new { }},
new Example {CatId = 1, Value = new { }},
new Example {CatId = 2, Value = new { }},
new Example {CatId = 2, Value = new { }},
new Example {CatId = 3, Value = new { }}
// and so on
};
So I am looking for making this complicated list more organized like list of lists of unique elements
something like:
var result = new List<List<Example>>
{
new List<Example>
{
new Example {CatId = 1, Value = new { }},
new Example {CatId = 2, Value = new { }},
new Example {CatId = 3, Value = new { }}
},
new List<Example>
{
new Example {CatId = 1, Value = new { }},
new Example {CatId = 2, Value = new { }}
},
new List<Example>
{
new Example {CatId = 1, Value = new { }}
}
}
Problem is I do not what to use, group by will not fix my case, so how to do this in most efficient way.
So this is about partitioning, it's the sort of thing that is easy to do in a database query, but in c# you need to create some key with a partition number that you can then use to .GroupBy.
The partitioning itself is a grouping
var projected = list.GroupBy(x => x.CatId)
.SelectMany( g => g.Select( ( x, i ) => new { Item = x, rn = i + 1 } ) );
This gives you records that look like:
{"Item":{"CatId":1,"Value":{}},"rn":1}
{"Item":{"CatId":1,"Value":{}},"rn":2}
{"Item":{"CatId":1,"Value":{}},"rn":3}
{"Item":{"CatId":2,"Value":{}},"rn":1}
{"Item":{"CatId":2,"Value":{}},"rn":2}
{"Item":{"CatId":3,"Value":{}},"rn":1}
As you can see that rn ("row number") value can be used to group by:
var result = projected.GroupBy(x => x.rn, x => x.Item);
This gives us:
[{"CatId":1,"Value":{}},{"CatId":2,"Value":{}},{"CatId":3,"Value":{}}]
[{"CatId":1,"Value":{}},{"CatId":2,"Value":{}}]
[{"CatId":1,"Value":{}}]
So, all in 1 go:
var result = list.GroupBy(x => x.CatId)
.SelectMany( g => g.Select( ( x, i ) => new { Item = x, rn = i + 1 } ) )
.GroupBy(x => x.rn, x => x.Item);
Live example: https://dotnetfiddle.net/AlTfk8

Deedle FillMissing() function bug?

I work with Deedle v.1.2.5 on C# and do get strange behavior.
When I replace missing values with FillMissing(double.NaN) function, it works as expected, but FillMissing((x) => double.NaN) code will not replace missing values.
What am I doing wrong?
//unitest
[Test]
public void Missing()
{
var sb = new SeriesBuilder<int, double>() { { 1, double.NaN }, { 2, 2 } };
var serie = sb.Series;
//replaces missing with NaN
var res = serie.FillMissing(double.NaN);
//ok
Assert.AreEqual(double.NaN, res.FirstValue());
var sb2 = new SeriesBuilder<int, double>() { { 1, double.NaN }, { 2, 2 } };
var serie2 = sb2.Series;
//NOT replaces missing with NaN
var res2 = serie2.FillMissing((x) => double.NaN);
//crashes
Assert.AreEqual(double.NaN, res2.FirstValue());
}

Convert LINQ (Entity Framework) nested Any's to take N parameters

Requirement: check if a list of successive words exists in a dataset. If it does, return a boolean to show the success.
Here is my code so far with unit tests (note this is sample code only - DataSet will be with Entity Framework not just a List):
[Test]
public void PhraseSearch()
{
var DataSet = new List<Word>
{
new Word { Text = "First", Sequence = 0 },
new Word { Text = "Second", Sequence = 1 },
new Word { Text = "Third", Sequence = 2 },
new Word { Text = "Forth", Sequence = 3 },
new Word { Text = "Five", Sequence = 4 }
};
var goodSearch = new string[]{ "First", "Second", "Third" };
var badSearch = new string[] { "First", "NOTFOUND", "Third" };
// successful test for 2 words
var result = DataSet.Any(wrd1 => wrd1.Text == goodSearch[0] && DataSet.Any(wrd2 => wrd2.Text == goodSearch[1] && wrd2.Sequence == wrd1.Sequence + 1));
Assert.That(result, Is.True);
result = DataSet.Any(wrd1 => wrd1.Text == badSearch[0] &&
DataSet.Any(wrd2 => wrd2.Text == badSearch[1] && wrd2.Sequence == wrd1.Sequence + 1));
// successful test for 2 words that don't match the data
Assert.That(result, Is.False);
// successful test for 3 words
result = DataSet.Any(wrd1 => wrd1.Text == goodSearch[0] &&
DataSet.Any(wrd2 => wrd2.Text == goodSearch[1] && wrd2.Sequence == wrd1.Sequence + 1 &&
DataSet.Any(wrd3 => wrd3.Text == goodSearch[2] && wrd3.Sequence == wrd2.Sequence + 1)));
Assert.That(result, Is.True);
// test for N words
result = .....
}
I want to expand the Linq code to do N words, but i'm not sure how to do this with Linq in Entity Framework, I'm leaning towards a hard coded method for each number of words but that seems really smelly.
You could use following extension which also takes into account that there could be multiple subsets in the longer sequence and a latter one contains the whole seb-sequence:
public static bool ContainsSequence<T>(this IEnumerable<T> seq, IEnumerable<T> subSeq, EqualityComparer<T> comparer = null)
{
if (comparer == null)
comparer = EqualityComparer<T>.Default;
IList<T> list = subSeq as IList<T> ?? new List<T>(subSeq);
IEnumerable<int> allIndexes = seq.AllIndexesOf(list.First(), comparer);
foreach (int index in allIndexes)
{
bool containsSequence = seq.Skip(index).Take(list.Count).SequenceEqual(list, comparer);
if (containsSequence)
return true;
}
return false;
}
Using this simple extension to find all indexes:
public static IEnumerable<int> AllIndexesOf<T>(this IEnumerable<T> seq, T itemToFind, EqualityComparer<T> comparer = null)
{
if (comparer == null)
comparer = EqualityComparer<T>.Default;
int index = 0;
foreach (T item in seq)
{
if (comparer.Equals(itemToFind, item))
yield return index;
index++;
}
}
Now the remaining check is simple:
bool containsSubseq = DataSet.OrderBy(x => x.Sequence).Select(x => x.Text)
.ContainsSequence(goodSearch);
However, this works for Linq-To-Objects not for database driven LINQ providers(saw too late).
I like the subset queries, but i ended up writing a recursive any statement to solve this as below:
public void PhraseSearch()
{
var DataSet = new List<Word>
{
new Word { Text = "First", Sequence = 0 },
new Word { Text = "Second", Sequence = 1 },
new Word { Text = "Third", Sequence = 2 },
new Word { Text = "Forth", Sequence = 3 },
new Word { Text = "Five", Sequence = 4 }
};
var goodSearch1 = new[] { "Second" };
var goodSearch2 = new[] { "First", "Second"};
var goodSearch3 = new[] { "Second", "Third", "Forth" };
var badSearch = new[] { "First", "NOTFOUND", "Third" };
// successful test for 1 word
int idxTosearch = 0;
var result = DataSet.Any(wrd => wrd.Text == goodSearch1[idxTosearch] && NextAny(goodSearch1,idxTosearch + 1,DataSet, wrd));
Assert.That(result, Is.True);
//reset counter
idxTosearch = 0;
result = DataSet.Any(wrd => wrd.Text == goodSearch2[0] && NextAny(goodSearch2, idxTosearch + 1, DataSet, wrd));
// successful test for 2 words
Assert.That(result, Is.True);
//reset counter
idxTosearch = 0;
// successful test for 3 words
result = DataSet.Any(wrd => wrd.Text == goodSearch3[0] && NextAny(goodSearch3, idxTosearch + 1, DataSet, wrd));
Assert.That(result, Is.True);
// test for bad words
//reset counter
idxTosearch = 0;
result = DataSet.Any(wrd => wrd.Text == badSearch[0] && NextAny(badSearch, idxTosearch + 1, DataSet, wrd));
Assert.That(result, Is.False);
}
private static bool NextAny(string[] phraseArray, int idxToSearch, List<Word> DataSet, Word previousWord)
{
if (idxToSearch == phraseArray.Length)
{
return true;
}
return allMatches.Any(wrd => wrd.Text == phraseArray[idxToSearch] && wrd.Sequence == previousWord.Sequence + 1 && NextAny(phraseArray, idxToSearch + 1, DataSet, wrd));
}
Try this to find a subset:
public void PhraseSearch()
{
var dataSet = new[] { "First", "Second", "Third", "Forth", "Five", };
var goodSearch = new[] { "First", "Second", "Third" };
var badSearch = new[] { "First", "NOTFOUND", "Third" };
Console.WriteLine(SubsequenceMatch(dataSet, new[] { "First", "Second", "Third" }));
Console.WriteLine(SubsequenceMatch(dataSet, new[] { "First", "NOTFOUND", "Third" }));
Console.WriteLine(SubsequenceMatch(dataSet, new[] { "Second", "Third", "Forth" }));
Console.WriteLine(SubsequenceMatch(dataSet, new[] { "Second", "Third", "Forth", "Five" }));
}
public bool SubsequenceMatch<T>(T[] source, T[] match)
{
return
Enumerable
.Range(0, source.Count() - match.Count() + 1)
.Any(n => source.Skip(n).Take(match.Count()).SequenceEqual(match));
}
This gives:
True
False
True
True

"select" query using "PetaPoco" ORM

I tried something like the following, but it didn't work.
var _records = new string[] {"SqlServer", "IIS" };
var result = db.Fetch<EntityRecords>(#" select * from tblRecords where RecordName IN rs", new { rs = _records });
and also i have tried another way like the following, but same problem
var _records = new string[] {"SqlServer", "IIS" };
var query = PetaPoco.Sql.Builder.Select("*").From("tblRecords").Where("RecordName IN (#rs)",new { rs = _records });
var result = db.Query<EntityRecords>(query);
The first one should be
var result = db.Fetch<EntityRecords>(#" select * from tblRecords where RecordName IN (#rs)", new { rs = _records });
or
var result = db.Fetch<EntityRecords>(#" select * from tblRecords where RecordName IN (#0)", _records);
The second one I'm not too sure about because the following tests pass
[Fact]
public void Append_GivenArrayAndValue_ShouldBeValid()
{
// Simple collection parameter expansion
_sql = Sql.Builder.Append("#0 IN (#1) #2", 20, new int[] { 1, 2, 3 }, 30);
_sql.SQL.ShouldBe("#0 IN (#1,#2,#3) #4");
_sql.Arguments.Length.ShouldBe(5);
_sql.Arguments[0].ShouldBe(20);
_sql.Arguments[1].ShouldBe(1);
_sql.Arguments[2].ShouldBe(2);
_sql.Arguments[3].ShouldBe(3);
_sql.Arguments[4].ShouldBe(30);
}
[Fact]
public void Append_GivenArrayAndNamedValue_ShouldBeValid1()
{
// Simple collection parameter expansion
_sql = Sql.Builder.Append("#p1 IN (#p2) #p3", new { p1 = 20 }, new { p2 = new int[] { 1, 2, 3 }}, new { p3 = 30 });
_sql.SQL.ShouldBe("#0 IN (#1,#2,#3) #4");
_sql.Arguments.Length.ShouldBe(5);
_sql.Arguments[0].ShouldBe(20);
_sql.Arguments[1].ShouldBe(1);
_sql.Arguments[2].ShouldBe(2);
_sql.Arguments[3].ShouldBe(3);
_sql.Arguments[4].ShouldBe(30);
}

using Linq to partition data into arrays

I have an array of elements where the element has a Flagged boolean value.
1 flagged
2 not flagged
3 not flagged
4 flagged
5 not flagged
6 not flagged
7 not flagged
8 flagged
9 not flagged
I want to break it into arrays based on the flagged indicator
output >
array 1 {1,2,3}
array 2 {4,5,6,7}
array 3 {8,9}
Linq doesn't have an operator for this, but I've written an extension method that you may be able to use (in the process of submitting it to MoreLinq, which you should also check out):
Using the operator below, you would write:
var result =
items.Segment( (item,prevItem,idx) => item.Flagged )
.Select( seq => seq.ToArray() ) // converts each sequence to an array
.ToList();
Here's the code of the extension method:
public static IEnumerable<IEnumerable<T>> Segment<T>(IEnumerable<T> sequence, Func<T, T, int, bool> newSegmentIdentifier)
{
var index = -1;
using (var iter = sequence.GetEnumerator())
{
var segment = new List<T>();
var prevItem = default(T);
// ensure that the first item is always part
// of the first segment. This is an intentional
// behavior. Segmentation always begins with
// the second element in the sequence.
if (iter.MoveNext())
{
++index;
segment.Add(iter.Current);
prevItem = iter.Current;
}
while (iter.MoveNext())
{
++index;
// check if the item represents the start of a new segment
var isNewSegment = newSegmentIdentifier(iter.Current, prevItem, index);
prevItem = iter.Current;
if (!isNewSegment)
{
// if not a new segment, append and continue
segment.Add(iter.Current);
continue;
}
yield return segment; // yield the completed segment
// start a new segment...
segment = new List<T> { iter.Current };
}
// handle the case of the sequence ending before new segment is detected
if (segment.Count > 0)
yield return segment;
}
}
I had a similar problem with this, and solved it using GroupBy and closure.
//sample data
var arrayOfElements = new[] {
new { Id = 1, Flagged = true },
new { Id = 2, Flagged = false },
new { Id = 3, Flagged = false },
new { Id = 4, Flagged = true },
new { Id = 5, Flagged = false },
new { Id = 6, Flagged = false },
new { Id = 7, Flagged = false },
new { Id = 8, Flagged = true },
new { Id = 9, Flagged = false }
};
//this is the closure which will increase each time I see a flagged
int flagCounter = 0;
var query =
arrayOfElements.GroupBy(e =>
{
if (e.Flagged)
flagCounter++;
return flagCounter;
});
What it does is grouping on an int (flagCounter), which is increased each time a Flagged element is found.
Please note this won't work with AsParallel().
Testing the results:
foreach(var group in query)
{
Console.Write("\r\nGroup: ");
foreach (var element in group)
Console.Write(element.Id);
}
Outputs:
Group: 123
Group: 4567
Group: 89
Considering:
var arrayOfElements = new[] {
new { Id = 1, Flagged = true },
new { Id = 2, Flagged = false },
new { Id = 3, Flagged = false },
new { Id = 4, Flagged = true },
new { Id = 5, Flagged = false },
new { Id = 6, Flagged = false },
new { Id = 7, Flagged = false },
new { Id = 8, Flagged = true },
new { Id = 9, Flagged = false }
};
You can write:
var grouped =
from i in arrayOfElements
where i.Flagged
select
(new[] { i.Id })
.Union(arrayOfElements.Where(i2 => i2.Id > i.Id).TakeWhile(i2 => !i2.Flagged).Select(i2 => i2.Id))
.ToArray();
This works if your elements are ordered by the Id attribute. If they don't, you'll have to inject a Sequence on your original array, that should be easy to do with linq as well, so you'll get a sequence.
Also, a better alternative should be:
// for each flagged element, slice the array,
// starting on the flagged element until the next flagged element
var grouped =
from i in arrayOfElements
where i.Flagged
select
arrayOfElements
.SkipWhile(i2 => i2 != i)
.TakeWhile(i2 => i2 == i || !i2.Flagged)
.Select(i2 => i2.Id)
.ToArray();
Note that those answers are using pure linq.
I don't think LINQ is the right tool for this task. What about this:
public static List<List<T>> PartitionData<T>(T[] arr, Func<T, bool> flagSelector){
List<List<T>> output = new List<List<T>>();
List<T> partition = null;
bool first = true;
foreach(T obj in arr){
if(flagSelector(obj) || first){
partition = new List<T>();
output.Add(partition);
first = false;
}
partition.Add(obj);
}
return output;
}
A small example, with the Data from Fábio Batistas post:
var arrayOfElements = new[] {
new { Id = 1, Flagged = true },
new { Id = 2, Flagged = false },
new { Id = 3, Flagged = false },
new { Id = 4, Flagged = true },
new { Id = 5, Flagged = false },
new { Id = 6, Flagged = false },
new { Id = 7, Flagged = false },
new { Id = 8, Flagged = true },
new { Id = 9, Flagged = false }
};
var partitioned = PartitionData(arrayOfElements, x => x.Flagged);
I don't think LINQ is suited for this very well. It could be done with Aggregate() but I think you'd be better of just looping with a foreach() building up the result.

Categories