Skip to content
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

Partition for groupings #210

Merged
merged 33 commits into from
Apr 21, 2017
Merged

Partition for groupings #210

merged 33 commits into from
Apr 21, 2017

Conversation

atifaziz
Copy link
Member

@atifaziz atifaziz commented Nov 2, 2016

Enables partitioning of groupings of Boolean keys and other any 3 cases.

Tests are pending.

Sample 1

Partition a grouping where the key is a Boolean:

var q1 =
    Enumerable.Range(0, 10)
      .GroupBy(x => x % 2 == 0)
      .Partition((evens, odds) => new { odds, evens });

Console.WriteLine("odds  = " + q1.odds.ToDelimitedString(", "));
Console.WriteLine("evens = " + q1.evens.ToDelimitedString(", "));

Output:

odds  = 1, 3, 5, 7, 9
evens = 0, 2, 4, 6, 8

Sample 2

Partition a grouping where the key is a nullable Boolean:

var q2 =
    Enumerable.Range(0, 10)
      .Select(x => (int?) x)
      .Interleave(Enumerable.Repeat((int?) null, 3))
      .GroupBy(x => x != null ? x < 5 : (bool?) null)
      .Partition((lt5, gte5, nil) => new { lt5, gte5, nil });

Console.WriteLine("lt5  = " + q2.lt5.ToDelimitedString(", "));
Console.WriteLine("gte5 = " + q2.gte5.ToDelimitedString(", "));
Console.WriteLine("nil  = " + q2.nil.Select(_ => "?").ToDelimitedString(", "));

Output:

lt5  = 0, 1, 2, 3, 4
gte5 = 5, 6, 7, 8, 9
nil  = ?, ?, ?

Sample 3

Partition grouping into multiples of 3 and the rest:

var q3 =
    Enumerable.Range(0, 10)
      .GroupBy(x => x % 3)
      .Partition(0, (m3, etc) => new { m3, etc });

Console.WriteLine("m3  = " + q3.m3.ToDelimitedString(", "));
Console.WriteLine("etc = " 
    + string.Join("; ",
        from g in q3.etc
        select new { k = g.Key, ms = g.ToDelimitedString(", ") }));

Output:

m3  = 0, 3, 6, 9
etc = { k = 1, ms = 1, 4, 7 }; { k = 2, ms = 2, 5, 8 }

Sample 4

Parition groupings by keys (remainders) matching 0, 1 & 2 (the non-matching groupings _ are ignored because they can never occur):

var q4 =
    Enumerable.Range(0, 10)
      .GroupBy(x => x % 3)
      .Partition(0, 1, 2, (ms, r1, r2, _) => new { ms, r1, r2 });

Console.WriteLine("ms  = " + q4.ms.ToDelimitedString(", "));
Console.WriteLine("r1  = " + q4.r1.ToDelimitedString(", "));
Console.WriteLine("r2  = " + q4.r2.ToDelimitedString(", "));

Output:

ms  = 0, 3, 6, 9
r1  = 1, 4, 7
r2  = 2, 5, 8

Sample 5

Partition of groupings using a key comparer:

var words =
    new[] { "foo", "bar", "FOO", "Bar", "baz", "quux", "bAz", "QuuX" };
    
var q5 =
    words
      .GroupBy(s => s, StringComparer.OrdinalIgnoreCase)
      .Partition("foo", "bar", StringComparer.OrdinalIgnoreCase,
                 (foos, bars, etc) => new { foos, bars, etc });

Console.WriteLine("foo = " + q5.foos.ToDelimitedString(", "));
Console.WriteLine("bar = " + q5.bars.ToDelimitedString(", "));
Console.WriteLine("etc = " 
    + string.Join("; ",
        from g in q5.etc
        select new { k = g.Key, ms = g.ToDelimitedString(", ") }));

Output:

foo = foo, FOO
bar = bar, Bar
etc = { k = baz, ms = baz, bAz }; { k = quux, ms = quux, QuuX }

Sample 6

Cascaded partition of groupings:

var q6 =
    words
      .GroupBy(s => s, StringComparer.OrdinalIgnoreCase)
      .Partition("foo", "bar", StringComparer.OrdinalIgnoreCase,
                 (foo, bar, etc) =>
                    etc.Partition("quux", "baz", StringComparer.OrdinalIgnoreCase,
                        (quux, baz, _) => new { foo, bar, baz, quux }));

Console.WriteLine("foo  = " + q6.foo.ToDelimitedString(", "));
Console.WriteLine("bar  = " + q6.bar.ToDelimitedString(", "));
Console.WriteLine("baz  = " + q6.baz.ToDelimitedString(", "));
Console.WriteLine("quux = " + q6.quux.ToDelimitedString(", "));

Output:

foo  = foo, FOO
bar  = bar, Bar
baz  = baz, bAz
quux = quux, QuuX

@leandromoh
Copy link
Collaborator

In Sample 1 we have

odds  = 1, 3, 5, 7, 9
evens = 1, 3, 5, 7, 9

Is it right?

@fsateler
Copy link
Member

fsateler commented Nov 2, 2016

I like this. I have needed methods like this one before, and ended up using ToLookup. I'll take a look at the code later.

A few overall questions though:

  • Requiring a GroupBy before partition feels a bit strange to me. Why not do the grouping inside Partition itself? A possible answer is that one might want to filter the IGrouping prior to partitioning. Not sure which case is more common though.
  • The size of the collections is (can be) known at resultSelector invocation time. I think passing a readonly IList<T> (if the order is to be guaranteed) or ICollection<T> (if not guaranteed) would be a better choice. Since streaming is out of the question for this operator, the passed IEnumerables cannot possibly be lazy.

PS: It is unfortunate that c# does not have syntax that would allow making this truly generic over the number of possible buckets...

@atifaziz
Copy link
Member Author

atifaziz commented Nov 2, 2016

@leandromoh Obviously not. :) Looks like a copy & paste error (printing odds twice). I've fixed the sample now & thanks for spotting that. 👍

@fsateler Glad to hear you like it. I've needed this countless times too & just got tired & decided it was time to give it a go (and in a generalised way over groupings). Looking forward to your review.

Requiring a GroupBy before partition feels a bit strange to me.

To be honest, I wanted to start with simpler signatures and not having to deal with keySelector and resultSelector (of which there is already another for Partition). It would have also exploded the number of overloads and it can always be added later. Since we have IGrouping<TKey, TElement> given by LINQ, I thought it'd best to build on top of that interface. A separate version for IEnumerable<T> could be added later that does the grouping if this proves useful. And as you so rightly observed, these versions allow filtering & more on the groups before partitioning.

The size of the collections is…known at resultSelector invocation time…passing a readonly IList<T>…or ICollection<T>…would be a better choice.

Unless you're in a concurrent world? And how can you assume how the grouping was created? An IGrouping<TKey, TElement> only promises to be a sequence of group members by inheriting IEnumerable<TElement> so I can't assume more. I know it's frustrating and I'd rather be indexing into a list than calling ElementAt and hoping that it is one.

@fsateler
Copy link
Member

fsateler commented Nov 3, 2016

Since we have IGrouping<TKey, TElement> given by LINQ, I thought it'd best to build on top of that interface. ... And as you so rightly observed, these versions allow filtering & more on the groups before partitioning.

I agree that using a type already given to us is good. OTOH, IGrouping is only provided as an interface, and no actual implementations, thus either forcing use of GroupBy or ToLookup to be able to consume this method.

A separate version for IEnumerable could be added later that does the grouping if this proves useful.

Ack.

The size of the collections is…known at resultSelector invocation time…passing a readonly IList…or ICollection…would be a better choice.

Unless you're in a concurrent world? And how can you assume how the grouping was created? An IGrouping<TKey, TElement> only promises to be a sequence of group members by inheriting IEnumerable so I can't assume more. I know it's frustrating and I'd rather be indexing into a list than calling ElementAt and hoping that it is one.

That is the problem of relying on IGrouping 😉 . More seriously, though, if the IGrouping overload is preserved, there is no choice but to use IEnumerable<T> instead of a collection interface.

@atifaziz
Copy link
Member Author

atifaziz commented Nov 3, 2016

if the IGrouping overload is preserved, there is no choice but to use IEnumerable<T> instead of a collection interface.

Sorry, not sure if I'm following you here. Care to rephrase?

Copy link
Member

@fsateler fsateler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests are missing. Looking forward to having this in MoreLINQ!

/// </summary>

public static TResult Partition<T, TResult>(this IEnumerable<IGrouping<bool, T>> source,
Func<IEnumerable<T>, IEnumerable<T>, TResult> resultSelector) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the first use of expression-bodied members in MoreLINQ. Is this intended?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, parameters indented at same level as body looks consfusing to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All done in the interest of speed to get this published for early feedback.

I use expression-bodied members whenever possible though I'm willing to revert in the interest of consistency with the rest of the code base. Once 2.0 is golden, it'll be time to make a sweeping change & upgrade the code base to C# 6.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use expression-bodied members whenever possible though I'm willing to revert in the interest of consistency with the rest of the code base.

Yes, consistency is good. Also, I thought there was interest in being able to compile with C# < 6. I'm not saying it is necessary to preserve (I don't use C#<6), but I wasn't sure if this was a requirement.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once 2.0 is golden

BTW, maybe it is a good idea to establish a milestone, to define what needs to be done before releasing 2.0. Then people can contribute towards that end, and maybe even decide which PRs to look at before or after release.

/// <summary>
/// Partitions a grouping by Boolean keys into a projection of true
/// elements and false elements, respectively.
/// </summary>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameter and return documentation is missing, on all overloads.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, for the particular boolean overloads, it should be documented which of the resultSelector parameters corresponds to true and false.

Copy link
Member Author

@atifaziz atifaziz Nov 3, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I documented just the summaries for now, which are the most important bits. The rest will follow as time permits. Meanwhile, this could be released earlier, and dare I say even without tests, if it's put into the edge namespace as being discussed in #209.

for the particular boolean overloads, it should be documented which of the resultSelector parameters corresponds to true and false.

This is already done: a projection of true elements and false elements, respectively. Perhaps it's not obvious but “respectively” means in the aforementioned order.

On that note, this is different from F# where, for example, List.partition returns a couple of false (first) & true (second) parts. Should we align? It does mean though that in the case of bool? keys, the parts become: null, false, true. Or should we go opinionated, that true then false reads better in practice because it's easier to express & think in terms of passing than failing conditions?

Copy link
Member

@fsateler fsateler Nov 4, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the particular boolean overloads, it should be documented which of the resultSelector parameters corresponds to true and false.

This is already done: a projection of true elements and false elements, respectively. Perhaps it's not obvious but “respectively” means in the aforementioned order.

Right, sorry. I meant that this needs to be in the <param /> entry.

On that note, this is different from F# where, for example, List.partition returns a couple of false (first) & true (second) parts. Should we align? It does mean though that in the case of bool? keys, the parts become: null, false, true. Or should we go opinionated, that true then false reads better in practice because it's easier to express & think in terms of passing than failing conditions?

Hmm. OTOH, those from a C-like background may find it more natural in the F# order, given that false is 0 and true is (usually) 1, and then parameters are in order. I value consistency, but I'm not sure how many people use both F# and C#. I'm undecided.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I documented just the summaries for now, which are the most important bits. The rest will follow as time permits. Meanwhile, this could be released earlier, and dare I say even without tests, if it's put into the edge namespace as being discussed in #209.

Hmmm. Maybe the guidelines for acceptance should also be discussed in #209. But I figure at least the trivial tests (null arguments, etc) should be there before merging.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that false is 0 and true is (usually) 1

I think true is usually some non-zero value. For example, in VBA, True is -1 when converting to a signed integer. Does that mean true comes before false? 😉 Joking aside, where would you put the null grouping?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Joking aside, where would you put the null grouping?

I like putting it last. It feels a lot more natural, because "real" values are first.

if (i < 0)
(etc ?? (etc = new List<IGrouping<TKey, TElement>>())).Add(e);
else
groups[i] = e;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the source enumerable is not unique by keys, then the last group seen will be the one used. This should be documented.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point but isn't it better to turn it into an error? Although doing so for non-matching groups would be unnecessarily expensive. I'm inclined to document this as undefined behaviour even if someone observes what you say. I guess the best test is what you'd expect Partition to do if the came from GroupAdjacent instead of GroupBy. Have to think about this one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point but isn't it better to turn it into an error? Although doing so for non-matching groups would be unnecessarily expensive.

Maybe just defining the error for the known keys is sufficient.

I'm inclined to document this as undefined behaviour even if someone observes what you say.

Uhm. I'm not particularly fond of undefined behavior. However, undefined behavior might be too strong. Maybe just say "if the keys are not unique, a single group will be passed to resultSelector. It is not specified which one."

I have to say I prefer the error.

I guess the best test is what you'd expect Partition to do if the came from GroupAdjacent instead of GroupBy. Have to think about this one.

GroupAdjacent has 2 main uses in my mind: partial grouping on infinite sequences, and grouping optimization when the source is known to be ordered. The first use case is not relevant for Partition as it can't deal with an infinite sequence anyway. For the second use case, I think I'd prefer if Partition would tell me the keys weren't unique.

: -1;

if (i < 0)
(etc ?? (etc = new List<IGrouping<TKey, TElement>>())).Add(e);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An assignment within an lvalue looks weird, I think it is more readable to expand this:

etc = etc ?? new List<IGrouping<TKey, TElement>>();
etc.Add(e);

var i = count > 0 && comparer.Equals(e.Key, k1) ? 0
: count > 1 && comparer.Equals(e.Key, k2) ? 1
: count > 2 && comparer.Equals(e.Key, k3) ? 2
: -1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find nested ?: operators very difficult to follow. What is the precedence of ? and : ? Again, I would expand this:

int i;
if (count > 0 && comparer.Equals(e.Key, k1))
    i = 0;
else if (count > 1 && comparer.Equals(e.Key, k2))
    i = 1;
else if (count > 2 && comparer.Equals(e.Key, k3))
    i = 2;
else
    i = -1;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is meant to be read here like a switch/case where case can be an expression and the whole switch is an expression evaluating to a single value. I'd like to keep this as-is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is meant to be read here like a switch/case where case can be an expression and the whole switch is an expression evaluating to a single value. I'd like to keep this as-is.

OK. I still disagree, but there is nothing else to claim other than style preferences 😉


/// <summary>
/// Partitions a grouping into a projection of elements matching two
/// sets of keys and those groups that do not.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/sets of // ? Or instead "elements matching a set of two keys". Same for the 3 key overloads

{
Debug.Assert(count > 0 && count <= 3);

if (source == null) throw new ArgumentNullException(nameof(source));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First instance of nameof in MoreLINQ, same question as for expression-bodied members.


public static TResult Partition<T, TResult>(this IEnumerable<IGrouping<bool?, T>> source,
Func<IEnumerable<T>, IEnumerable<T>, IEnumerable<T>, TResult> resultSelector) =>
source.Partition(true, false, null, (t, f, n, _) => resultSelector(t, f, n));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you use _ to mark an unused parameter, I think you should do this in all overloads.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do that where it's limited to one unused parameter as that's all C# can afford us. Do you suggest to use 2 underscores for the second unused? As in:

PartitionImpl(source, 1, key, default(TKey), default(TKey), comparer, (a, _, __, rest) => resultSelector(a, rest));

int count, TKey k1, TKey k2, TKey k3, IEqualityComparer<TKey> comparer,
Func<IEnumerable<TElement>, IEnumerable<TElement>, IEnumerable<TElement>, IEnumerable<IGrouping<TKey, TElement>>, TResult> resultSelector)
{
Debug.Assert(count > 0 && count <= 3);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be changed to a regular check+throw, so that unit tests have a chance of catching this.

@@ -0,0 +1,144 @@
#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2009 Atif Aziz. All rights reserved.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2009 => 2016

@fsateler
Copy link
Member

fsateler commented Nov 3, 2016

Sorry, not sure if I'm following you here. Care to rephrase?

I mean that as long as the implementation is based on top of IGrouping, it is somewhat restricted to having to return IEnumerables, because there is no guarantee the IGrouping is a collection. The alternative would be calling ToList or similar before invoking the resultSelector, but that is potentially costly.

@fsateler
Copy link
Member

fsateler commented Nov 3, 2016

as long as the implementation is based on top of IGrouping

Actually, now that I think of this, why not pass the IGrouping directly instead of an IEnumerable ?

public static TResult Partition<T, TResult>(this IEnumerable<IGrouping<bool, T>> source,
            Func<IGrouping<bool, T>, IGrouping<bool, T>, TResult> resultSelector)
// ...

@atifaziz
Copy link
Member Author

atifaziz commented Nov 3, 2016

why not pass the IGrouping directly instead of an IEnumerable ?

With Partition, you're destructuring groupings via lambda arguments. Why would you want to keep the keys as it's going to be used for a small set of known keys? For non-matching cases, you get the groupings because you might still need the key (see sample 6 for how that's used for nested partitioning). For the case where keys are Boolean values (the signature you quoted), it's especially useless. It may have some value if you want to capture the original keys when using a custom comparer (e.g. the original case of a string key using a case-insensitive comparer). In the rare event that you really want to keep the keys for the destructured cases, you can attach it back.

Do you have a concrete example to demonstrate what the current design/signature would prevent?

@fsateler
Copy link
Member

fsateler commented Nov 4, 2016

why not pass the IGrouping directly instead of an IEnumerable ?

Do you have a concrete example to demonstrate what the current design/signature would prevent?

No. That was mostly thinking out loud, and your explanation makes a lot of sense. There is little value in preserving the IGrouping. Thanks for the explanation!

@fsateler
Copy link
Member

fsateler commented Dec 29, 2016

Alternative names to avoid collision with the already existing Partition (btw, the currently existing partition is not currently appearing on the api docs online):

  1. Pivot: As in pivoting over the possible keys in the IGrouping collection
  2. Deconstruct: as in tuple deconstruction.
  3. Match: as in pattern matching (a very limited pattern, though).

@atifaziz
Copy link
Member Author

Thanks for those names suggestions, @fsateler. I'll give them some thought.

Partition (btw, the currently existing partition is not currently appearing on the api docs online)

If you look at the changes between the last beta and up to the release of 2.0, Partition was removed in 7a2c72b for the reason given in the commit message.

@atifaziz
Copy link
Member Author

We don't have an issue for this PR so let's pair it with #264.

@atifaziz
Copy link
Member Author

@fsateler Let me know if I missed anything from your review/feedback.

Failed : MoreLinq.Test.NullArgumentTest.Partition: 'predicate' (1);
TResult Partition[T,TResult](System.Collections.Generic.IEnumerable`1[T], System.Func`2[T,System.Boolean], System.Func`3[System.Collections.Generic.IEnumerable`1[T],System.Collections.Generic.IEnumerable`1[T],TResult])
  Expected string length 9 but was 11. Strings differ at index 0.
  Expected: "predicate"
  But was:  "keySelector"
  -----------^
Copy link
Member

@fsateler fsateler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very nice except for a few details.

BTW, the GroupAdjacentTest.cs file renaming seems spurious?

/// <typeparam name="TElement">Type of elements in source groupings.</typeparam>
/// <typeparam name="TResult">Type of the result.</typeparam>
/// <param name="source">The source sequence.</param>
/// <param name="key">The key to partition.</param>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be my non-native English, but I fail to parse this description. I think it would be clearer to say: The key of the matching partition. Mutatis mutandis, the same applies to the overloads with more key arguments.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's my thinking…the method is called Partition so it's partitioning or extracting the group with that key. Would the “the key to partition on“ read clearer?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the “the key to partition on“ read clearer?

That would be a bit better. At least I can parse that :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTY now with 97ed654?

{
var (evens, odds) =
Enumerable.Range(0, 10)
.Partition(x => x % 2 == 0, ValueTuple.Create);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very nice example of this overload. Maybe it should be included in the xmldoc?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 7ef8442 but just for the simplest overload.


var r2 = r.Read();
Assert.That(r2.Key, Is.EqualTo(2));
Assert.That(r2, Is.EquivalentTo(new[] { 2, 5, 8 }));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test (and all the tests that have more than one grouping in etc) assert that the result is ordered by the order in which keys are seen, but that is not documented. So either the documentation should be updated to include this guarantee, or change these tests to relax that requirement.

Assert.That(r1, Is.EquivalentTo(new[] { 1, 4, 7 }));
Assert.That(r2, Is.EquivalentTo(new[] { 2, 5, 8 }));
using (var r = etc.Read())
r.ReadEnd();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would find it clearer to Assert.That(etc, Is.Empty), any particular reason to use the SequenceReader in this context?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any particular reason to use the SequenceReader in this context?

Besides me being sloppy, no. 😕

@atifaziz
Copy link
Member Author

Wonder if this is also not a good time to add an overload without any projection function that simply returns a tuple:

public static (IEnumerable<T> True, IEnumerable<T> False)
    Partition<T>(this IEnumerable<T> source, Func<T, bool> predicate) =>
    source.Partition(predicate, ValueTuple.Create);

The usage would then be:

var (evens, odds) = Enumerable.Range(0, 10).Partition(x => x % 2 == 0);

Here it is in action in a C# Interactive session:

Microsoft (R) Visual C# Interactive Compiler version 2.1.0.61520
Copyright (C) Microsoft Corporation. All rights reserved.

Type "#help" for more information.
> #r "MoreLinq.dll"
> using MoreLinq;
> var res = Enumerable.Range(0, 10).Partition(x => x % 2 == 0);
> res.True
Lookup<bool, int>.Grouping { 0, 2, 4, 6, 8 }
> res.False
Lookup<bool, int>.Grouping { 1, 3, 5, 7, 9 }

Note tuple element names of True & False.

@fsateler
Copy link
Member

Wonder if this is also not a good time to add an overload without any projection function that simply returns a tuple

Oh, that sounds very useful.

@atifaziz
Copy link
Member Author

Oh, that sounds very useful.

Added in 2a9fa55.

@atifaziz
Copy link
Member Author

@fsateler We good to merge this? Did I miss anything from the changes you requested in your review?

@fsateler
Copy link
Member

@fsateler We good to merge this? Did I miss anything from the changes you requested in your review?

You did not comment on my comment on the key description. If you disagree the description is unclear (or you think the new description is worse than the original), then I'm fine to merge.

Copy link
Member

@fsateler fsateler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (modulo the merge conflicts)

Thanks! 👍

@atifaziz atifaziz merged commit 89dcf08 into master Apr 21, 2017
@atifaziz atifaziz deleted the group-partition branch April 21, 2017 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants