Yes exactly. The Crystal code I provided is also lazy which is why it doesn’t use much memory at all.
A common convention in Crystal is that you can get an iterator by invoking a method that normally takes a block, but without passing one. I.e. the version of each_combination in your link is using a block so it’s not the iterator version of it. While calling it as I did w/o the block returns an iterator.
The difference compared to Ruby is that for this snippet, all I want is the number of different combinations, and not their values, but to get that Crystal stores the individual values, and Ruby doesn’t. I tried to see if I could get around having to store arrays of combinations but haven’t seen how to do that with existing methods.
It looks like the issue here is simply that the iterator implementation (Indexable::CombinationIterator) does not have a dedicated #size method which could give you the result right away. Instead, it falls back to Enumerable#size which iterates through all items (count { true }).
With a direct implementation of #size this works really well and fast:
p! (1..60).to_a.each_combination(53).size # => 386206920
module Indexable
class CombinationIterator
def size
binomial_coefficient(@n, @size)
end
def binomial_coefficient(n, k)
return 0 if k > n
return 1 if k == 0 || k == n
return binomial_coefficient(n - 1, k - 1) + binomial_coefficient(n - 1, k)
end
end
end
Losely related issues (why this implementation is not enforced):
The binomial_coefficient algorithm is just a very crude and inefficient recursive formula. Using the Rosetta code gives a result almost instantaneously. But even that is still far from highly optimized.
Just as an example, this implementation is an order of magnitude faster:
def binomial_coefficient(n, k)
k = Math.min(k, n - k)
(1..k).product(1.to_big_i) {|i| (n + 1 - i)} // (1..k).product(1.to_big_i)
end
I don’t follow what you mean by 2. Indexable#combinations materializes an array with all combinations. It can’t be fast because it really needs to iterate and collect all combinations. #each_combination is the lazy variant and faster if you only want the size.