Suppose you have a sequence of numbers:
var nums = Enumerable.Range(1, 5); // 1,2,3,4,5
Then nums.Min()
computes their minimum, and nums.Max()
computes their maximum.
However, if we want want both the mininum and the maximum we end up running through nums
twice.
We could, of course, write our own function MinMax()
which keeps track of both
the minimum and the maximum. But let's assume what we want to compute two, more complicated,
functions of nums
and that we don't have the source code for the function.
Suppose, also, that the sequence we are working with is expensive to produce (so
we don't want to run through it more than once) and large (so we can't store all the items
in memory). Is there a way to still compute what we wanted?
This repo shows how one can go about doing this.
This repo was inspired by the following question on SO: Consuming an IEnumerable multiple times in one pass.
Let's call an object with type IEnumerable<S>
an enumerable, and an object
with type Func<IEnumerable<S>, T>
a coenumerable (since it is
dual to an enumerable).
Each time we call GetEnumerator()
on enumerable we say that we are enumerating it.
If we call MoveNext()
on the resulting IEnumerator<S>
until it returns false
then
we say we enumerated fully, otherwise we enumerated partially.
We can now state precisely what we described informally in the Introduction. We want a procedure to evaluate two coenumerables on a given enumerable so that
- the enumerable is only enumerated once,
- we do not store the items of the enumerable simultaneously in memory,
- we do not require access to the source code of the coenumerables, and
- if both coenumerables enumerate the enumerable partially, then so too does the procedure.
The Combine
extension method in the CoEnumerable
project in this repo does exactly this.
It turns out to be fairly simple to implement in terms of a
Barrier
(not to be confused with a
MemoryBarrier).
- It is not very fast (as
CoEnumerable.Demo
illustrates) - In theory a coenumerable could do either of two things which will cause problems for this library:
- A single evaluation of the coenumerable on an enumerable might enumerate it multiple times
- The coenumerable could fail to
Dispose()
the enumerator it uses to enumerate the enumerable
- Could this be implemented using ReactiveX? It is certainly possible. In fact there is at least one SO answer that proposes this as a solution to a similar question: Reading an IEnumerable multiple times. However, I am not familiar enough with ReactiveX to be certain whether it is, or is not possible. In particular, I don't know whether it is possible to satisfy the last requirement of the four listed above (if you drop that last requirement I think it is possible).