Consider adding series generation/comprehension methods for programmatic generation of collections "from scratch".
Not sure this would not technically be called a LINQ operator, but it is very useful for generating data permutations more complex than a 1-step range (as with Enumerable.Range), for tests or otherwise, as well as generating ordinals to be paired with collection items using the Zip operator.
The method would have the following behavior given fairly vanilla generator and terminal functions:
Series.Expand(2, n => n * n, n > 1000) => (2, 4, 8, 16, 32, 64, 128, 256, 512)
The method would have the following behavior given a vanilla generator function and an element count:
Series.Expand(1, n => n + 3, 8) => (1, 4, 7, 10, 13, 16, 19, 22)
The method would generate new elements lazily and stream them. This would also allow an effectively "infinite" sequence to be generated, for example for the purposes of Zipping with a collection of indeterminate length.
I have attached a patch with a proposed implementation and tests. I placed the method in its own file and static class because it is not strictly an operator like the others.
- Series.patch 5.92KB
Comment #1
Posted on Feb 16, 2009 by Happy BearI like the idea of a Generate method (and I've written one before) but I don't think I'd specify either a condition or a number of elements to generate - using Take and TakeWhile do that well enough, I think.
I haven't looked at the patch yet - will try to do so tonight.
Comment #2
Posted on Feb 16, 2009 by Massive OxWe can borrow from Python's (x)range here and add a Range operator:
Range(int stop) Range(int start, int stop) Range(int start, int stop, int step)
I can also imagine a default, zero-arguments, overload of Range that produces the full series of integers/longs. With this in place, one can use Select and other existing operators to the same effect. For example:
Series.Expand(2, n => n * n, n > 1000) => (2, 4, 8, 16, 32, 64, 128, 256, 512)
becomes:
Range().Select(n => n + 2) .Select(n => n * n) .TakeWhile(n => n < 1000)
That can be a bit mouthful and verbose so I can understand CAmmerman's point to have something that simplifies a very common use case. After all, many base LINQ operators also have overloads for projection (see Max, for example) when there is already Select. However, I would stard with Range because that can serve as a basis for Expand.
Comment #3
Posted on Feb 16, 2009 by Massive OxOK, I'm silly and it had momentarily slipped my mind that Range already exists: http://msdn.microsoft.com/en-us/library/system.linq.enumerable.range.aspx
So we are now talking about:
Range(2, int.MaxValue).Select(n => n * n).TakeWhile(n => n < 1000)
Comment #4
Posted on Feb 16, 2009 by Grumpy Birdbut I don't think I'd specify either a condition or a number of elements to generate
I was thinking along the lines of what azizatif was saying, about simplifying the common use cases. I'm not married to it however. And we could certainly have an overload without the terminator condition or element count. On a related note, the terminal check might do better if it worked like TakeWhile, rather than "take until". The code itself is older, and I don't remember why I chose to do it this way originally.
There might be a better name for this method than Expand, as well. It was just the simplest thing that applied, in my mind at the time.
Comment #5
Posted on Feb 16, 2009 by Grumpy BirdMy example (which has since been repeated by Aziz) was not quite right.... The n * n should be n * 2, to generate the sequence I specified. n * n would be repeated squaring, which would overflow quite rapidly. =)
Series.Expand(2, n => n * 2, n > 1000) => (2, 4, 8, 16, 32, 64, 128, 256, 512)
The patch I submitted has the same typo in the comments.
Comment #6
Posted on Feb 17, 2009 by Happy BearIf we're looking at Range, I have a much more competent Range class in MiscUtil. I'm not sure whether I want to bring that over to MoreLinq though - it's useful outside LINQ too, and I'd rather keep to operators.
@Comment 5: That example still isn't right - it should be:
Series.Expand(2, n => n * 2, n => n > 1000)
which I'd still prefer to see as:
Series.Expand(2, n => n * 2).TakeWhile(n => n <= 1000)
Generating a sequence and terminating it are somewhat orthogonal concepts, and the latter already exists in a well-known form. I think I'd find the latter easier to read, aside from anything else.
Comment #7
Posted on Feb 17, 2009 by Massive Ox@CAmmerman: You should commit your patch. I agree with Jon, though, to keep the terminating condition out for now. It can always be added later in version 2. Meanwhile it does not prevent anyone from having their own overload that combines Expand with TakeWhile or Expand with Take to get your original three-part Expand version. At least, they'll be getting a tested base to build on.
Comment #8
Posted on Feb 18, 2009 by Massive OxImplemented in r38.
Status: Fixed
Labels:
Type-Enhancement
Priority-Medium
Milestone-Release1.0