I'm going out on a limb by suggesting an approach even though I know nothing of Rails. If what I propose makes no sense, please tell me and I will remove my answer and slink away.
Suppose you interrogated the database merely to create an array that indicates whether the apartment is available on each date in a range of consecutive dates. Suppose, for example, it looked like this:
A = true
U = nil
avail = [A,A,A,U,U,A,A,U,U,A,A,A,A,A,U,A]
For a given number of consecutive days, n
, the date offsets that begin runs of at least n
available days would be given by the following method.
Code
def runs(avail, n)
avail.each_with_index.each_cons(n).map do |run|
av, off = run.transpose
(av == av.compact) ? off.first : nil
end.compact
end
Examples
runs(avail,1) #=> [0, 1, 2, 5, 6, 9, 10, 11, 12, 13, 15]
runs(avail,2) #=> [0, 1, 5, 9, 10, 11, 12]
runs(avail,3) #=> [0, 9, 10, 11]
runs(avail,4) #=> [9, 10]
runs(avail,5) #=> [9]
runs(avail,6) #=> []
Explanation
Consider the n=3
case above.
n = 3
enum0 = avail.each_with_index
#=> #<Enumerator: [true, true, true, nil, nil, true, true, nil, nil,
# true, true, true, true, true, nil, true]:each_with_index>
enum0.to_a
#=> [[true, 0], [true, 1], [true, 2], [nil, 3], [nil, 4], [true, 5],
# [true, 6], [nil, 7], [nil, 8], [true, 9], [true, 10], [true, 11],
# [true, 12], [true, 13], [nil, 14], [true, 15]]
enum1 = enum0.each_cons(n)
#=> #<Enumerator: #<Enumerator: [true, true, true, nil,...
# ..., true]:each_with_index>:each_cons(40)>
enum1.to_a
#=> [[[true, 0], [true, 1], [true, 2]],
# [[true, 1], [true, 2], [nil, 3]],
# ...
# [[true, 13], [nil, 14], [true, 15]]]
enum2 = enum1.map
#=> #<Enumerator: #<Enumerator: #<Enumerator: [true, true, true, nil,...
# ...true]:each_with_index>:each_cons(3)>:map>
enum2.to_a
#=> [[[true, 0], [true, 1], [true, 2]],
# [[true, 1], [true, 2], [nil, 3]],
# ...
# [[true, 13], [nil, 14], [true, 15]]]
a = enum2.each do |run|
av, off = run.transpose
(av == av.compact) ? off.first : nil
end
#=> [0, nil, nil, nil, nil, nil, nil, nil, nil, 9, 10, 11, nil, nil]
a.compact
#=> [0, 9, 10, 11]
Consider the calculation of the array a
above. The first element of enum2
that each
passes into the block, and assigns to the block variable run
is:
run => [[true, 0], [true, 1], [true, 2]]
Then
av, off = run.transpose #=> [[true, true, true], [0, 1, 2]]
av #=> [true, true, true]
off #=> [0, 1, 2]
([true, true, true] == [true, true, true].compact) ? 0 : nil
#=> ([true, true, true] == [true, true, true]) ? 0 : nil
#=> 0
so the first value of enum2
is mapped into 0
, meaning that there is a run of at least 3
days beginning on day offset 0
.
Next, [[true, 1], [true, 2], [nil, 3]]
is passed into the block and assigned to the variable run
. Then:
av, off = run.transpose #=> [[true, true, nil], [1, 2, 3]]
av #=> [true, true, nil]
off #=> [1, 2, 3]
([true, true, nil] == [true, true, nil].compact) ? 1 : nil
# ([true, true, nil] == [true, true]) ? 1 : nil
#=> nil
so the second value of enum2
is mapped into nil
, meaning that there is not a run of at least 3
days beginning on day offset 1
. And so on...
Notes
- The values in
avail
that indicate an apartment is available on a given date offest (the constant A
above) can be any "truthy" value (i.e., anything other than false
or nil
); however, unavailable dates (represented by U
above) must be represented by nil
, as I use Array#compact to remove them from arrays.
- It may be helpful to think of
enum1
and enum2
as "compound" enumerators.
enum0.to_a
, enum1.to_a
and enum2.to_a
are provided to show what each enumerator would pass into its block, if it had one. (enum2
does have a block, of course).