New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Flatten (Issue #356) #357
Flatten (Issue #356) #357
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please fix broken build/test. Thanks!
MoreLinq.Test/FlattenTest.cs
Outdated
|
||
var result = source.Flatten(); | ||
|
||
Assert.AreEqual(13, result.Count()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change assertions to use the more readable constraints approach:
Assert.That(result.Count(), Is.EqualTo(13));
MoreLinq.Test/FlattenTest.cs
Outdated
[Test] | ||
public void FlattenOfType() | ||
{ | ||
var source = new List<object>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use an array instead of List<>
here and in other similar cases. A list isn't needed, distracting & verbose.
MoreLinq.Test/FlattenTest.cs
Outdated
public class FlattenTest | ||
{ | ||
[Test] | ||
public void FlattenOfType() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This name doesn't mean anything.
MoreLinq/Flatten.cs
Outdated
/// </returns> | ||
/// <exception cref="ArgumentNullException"><paramref name="source"/> is null.</exception> | ||
/// <exception cref="ArgumentNullException"><paramref name="isAtom"/> is null.</exception> | ||
public static IEnumerable<object> Flatten(this IEnumerable source, Func<object, bool> isAtom) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isAtom
should be renamed to predicate
and the condition inverted such that returning true
will flatten the inner sequence in question. I would also change the signature to Func<IEnumerable, bool>
.
We can also look at this as a selector/predicate combined? The function signature would become:
public static IEnumerable<object> Flatten(this IEnumerable source, Func<object, IEnumerable> selector)
If the selector function returns null
then it is skipped. The benefit of this version is that it would allow flattening via a property of an object when the object itself isn't iterable. The downside is that it overloads the selector function to also serve as a predicate and which won't be obvious from the signature (since we don't have an optional type in .NET).
MoreLinq/Flatten.cs
Outdated
{ | ||
if (e is IEnumerable inner && !isAtom(inner)) | ||
{ | ||
foreach (var i in _(inner)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about coding an explicit stack here instead of relying on recursion?
MoreLinq.Test/NullArgumentTest.cs
Outdated
|
||
return typeInfo.IsGenericType | ||
? CreateGenericInterfaceInstance(typeInfo) | ||
: CreateNonGenericInterfaceInstance(typeInfo); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the purpose of CreateNonGenericInterfaceInstance
. Why not just use new NonGenericArgs.Enumerable()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to follow the pattern, but really it is not necessary here
MoreLinq.Test/FlattenTest.cs
Outdated
|
||
Assert.AreEqual(13, result.Count()); | ||
|
||
result.OfType<int>().AssertSequenceEqual(Enumerable.Range(1, 10)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These assertions are not very clear and seem like a shortcut. They don't assert the order in which elements are returned in result
. It's much clearer to write the full expected result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made some improvements directly to speed up things. There are just some tests for disposals missing now and then I believe we'll be done.
Thanks!
|
||
if (!next) | ||
{ | ||
(e as IDisposable)?.Dispose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test to exercise this disposal is missing.
MoreLinq/Flatten.cs
Outdated
{ | ||
stack.Prepend(e) | ||
.OfType<IDisposable>() | ||
.ForEach(x => x.Dispose()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test to exercise these disposals in the event of an exception is missing.
Thanks for the help! Surely it speed up. Tests for dispose were delivery. |
@atifaziz I finished the development. I added the overload for map an object to a property. IMHO It is only missing the parameter documentation for "selector" |
Singular of "series" is "series", not "serie".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the overload for map an object to a property.
I don't think that was the idea. What I said in my earlier suggestion was:
We can also look at this as a selector/predicate combined? The function signature would become:
public static IEnumerable<object> Flatten(this IEnumerable source, Func<object, IEnumerable> selector)
If the selector function returns
null
then it is skipped. The benefit of this version is that it would allow flattening via a property of an object when the object itself isn't iterable. The downside is that it overloads the selector function to also serve as a predicate and which is not obvious from the signature (since we don't have an optional type in .NET).
Your signature for the selector function is Func<object, object>
and then for the predicate function it's Func<IEnumerable, bool>
. Looking at just those two, it's not obvious what's going on, especially with the selector (projecting an object to yet another object?). At the very least, the selector should be Func<object, IEnumerable>
because it's not surprising to see a projection going from an object (item of a parent sequence) to a (sub-)sequence in the context of a flattening operation. Since an IEnumerable
is a reference and we'd have to deal with them being returned as null
, I followed that we could use that as a predicate too, for example, to skip some iterables like strings. However, that was me just making a suggestion and was hoping @fsateler and/or you would chime in and we'd reach a consensus on the final design before updating the PR. As it stands right now, I feel the overload worsens things.
MoreLinq.Test/FlattenTest.cs
Outdated
|
||
using (var test = source.AsTestingSequence()) | ||
{ | ||
Assert.That(test.Flatten(), Is.EquivalentTo(expectations)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use AssertSequenceEqual
to be consistent as in other tests? That is:
test.Flatten().AssertSequenceEqual(expectations);
MoreLinq.Test/FlattenTest.cs
Outdated
Assert.That(test.Flatten(), Is.EquivalentTo(expectations)); | ||
} | ||
|
||
new object[] { inner1, inner2, inner3 }.Cast<IDisposable>().ForEach(seq => seq.Dispose()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two things. First, why not type the array elements as IDisposable
in the first place instead of casting later?
new IDisposable[] { inner1, inner2, inner3 }.ForEach(seq => seq.Dispose())
Second, why not just use using
like below?
[Test]
public void FlattenFullIteratedDisposesInnerSequences()
{
using (var inner1 = TestingSequence.Of(4, 5))
using (var inner2 = TestingSequence.Of(true, false))
using (var inner3 = TestingSequence.Of<object>(6, inner2, 7))
{
var source = new object[]
{
inner1,
inner3,
};
var expectations = new object[]
{
4,
5,
6,
true,
false,
7,
};
using (var test = source.AsTestingSequence())
test.Flatten().AssertSequenceEqual(expectations);
}
}
@atifaziz sorry, I thought had undertood your idea but I just did it now. I dont like too much about the idea of return How about maintain |
@atifaziz what about the above question? |
Let's shoot for this. Doing an MVP is a good thing anyway. |
@atifaziz The PR is only with the MVP now. ready for merge. can we publish a new release before december finished? |
Sorry but was AFK, celebrating. I'll try and look at it this week. |
Any news? |
Did you rebase and remove commits because that's confused me a bit and we may have lost some work. The history of this PR seems to have diverged at d0297a5. I think it would have been simpler to |
I checkout to the last commit without selector overload. But before that, created a new branch with all work (this one for the possíble new PR)
Sorry about the commit messages, i will be more descritive next time. |
Okay, that explains it and so the branch was rewound. As I said, a revert would have been the right call here.
Cheers! 👍 |
This revert is a combination of the following: - Revert "Use arrays instead of lists" This reverts commit c8f59eb. - Revert "Fix grammar" This reverts commit 78fd850. - Revert "Delete trailing whitespace" This reverts commit c38da12. - Revert "fix" This reverts commit 0db744d. - Revert "improved" This reverts commit 68b7a9f. - Revert "fix" This reverts commit 977193a. - Revert "add selector overload" This reverts commit 9480d35.
@leandromoh I've put your changes on top of a revert (c6f59f6) just to maintain the “selector overload” as part of this PR's history and discussion, in case we need to pick it up again. |
Ok. Thanks. What about the code itself? Are we ready to merge? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The finally
block needs a final touch/revision so almost there! 🏁
MoreLinq/Flatten.cs
Outdated
} | ||
finally | ||
{ | ||
stack.Prepend(e) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The finally
block should be simple rather than use any fancy LINQ to avoid causing allocations under low memory conditions (the reason for which the finally
code could be running in the first place). You can re-write this in the same lines of code:
(e as IDisposable)?.Dispose();
foreach (var se in stack)
(se as IDisposable)?.Dispose();
Note that since Stack<>
's enumerator is a struct
, there's no cost to iterating over it using foreach
.
@atifaziz code refactored! also I added flatten to readme and release notes. |
Implementation for #356.