Related
Let's say I have some user input with start and end hours:
start = 09:00
end = 01:00
How do I display all the hours between those 2? So from 09 to 23, 0, and then to 1.
There are easy cases:
start = 01:00
end = 04:00
That's just a matter of
((start_hour.to_i)..(end_hour.to_i)).select { |hour| }
This can be solved with a custom Enumerator implementation:
def hours(from, to)
Enumerator.new do |y|
while (from != to)
y << from
from += 1
from %= 24
end
y << from
end
end
That gives you something you can use like this:
hours(9, 1).each do |hour|
puts hour
end
Or if you want an Array:
hours(9,1).to_a
#=> [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 0, 1]
You could do a oneliner (0..23).to_a.rotate(start_h)[0...end_h - start_h]
def hours_between(start_h, end_h)
(0..23).to_a.rotate(start_h)[0...end_h - start_h]
end
hours_between(1, 4)
# [1, 2, 3]
hours_between(4, 4)
# []
hours_between(23, 8)
# [23, 0, 1, 2, 3, 4, 5, 6, 7]
Don't forget to sanitize the input (That they are number between 0 and 23) :)
If you want the finishing hour use .. instead of ... => [0..end_h - start_h]
If you care about performance or want something evaluated lazily you can also do the following (reading the code is really clear):
(0..23).lazy.map {|h| (h + start_h) % 24 }.take_while { |h| h != end_h }
With a simple condition:
def hours(from, to)
if from <= to
(from..to).to_a
else
(from..23).to_a + (0..to).to_a
end
end
hours(1, 9)
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
hours(9, 1)
#=> [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 0, 1]
You could also use the shorter, but more cryptic [*from..23, *0..to] notation.
https://stackoverflow.com/a/6784628/3012550 shows how to iterate over the number of hours in the distance between two times.
I would use that, and at each iteration use start + i.hours
def hours(number)
number * 60 * 60
end
((end_time - start_time) / hours(1)).round.times do |i|
print start_time + hours(i)
end
I have an array of numbers as below:
[11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
I would like to reduce this array to:
[[11,14], [19,21], [29,30], [33,33]]
Identify consequent numbers in an array and push only the start and end of its ranges.
How to achieve this?
Exactly some problem is solved to give an example for slice_before method in ruby docs:
a = [0, 2, 3, 4, 6, 7, 9]
prev = a[0]
p a.slice_before { |e|
prev, prev2 = e, prev
prev2 + 1 != e
}.map { |es|
es.length <= 2 ? es.join(",") : "#{es.first}-#{es.last}"
}.join(",")
In your case you need to tweak it a little:
a = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
prev = a[0]
p a.slice_before { |e|
prev, prev2 = e, prev
prev2 + 1 != e
}.map { |es|
[es.first, es.last]
}
Here's another way, using an enumerator with Enumerator#next and Enumerator#peek. It works for any collection that implements succ (aka next).
Code
def group_consecs(a)
enum = a.each
pairs = [[enum.next]]
loop do
if pairs.last.last.succ == enum.peek
pairs.last << enum.next
else
pairs << [enum.next]
end
end
pairs.map { |g| (g.size > 1) ? g : g*2 }
end
Note that Enumerator#peek raises a StopInteration exception if the enumerator enum is already at the end when enum.peek is invoked. That exception is handled by Kernel#loop, which breaks the loop.
Examples
a = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
group_consecs(a)
#=> [[11, 12, 13, 14], [19, 20, 21], [29, 30], [33, 33]]
a = ['a','b','c','f','g','i','l','m']
group_consecs(a)
#=> [["a", "b", "c"], ["f", "g"], ["i", "i"], ["l", "m"]]
a = ['aa','ab','ac','af','ag','ai','al','am']
group_consecs(a)
#=> [["aa", "ab", "ac"], ["af", "ag"], ["ai, ai"], ["al", "am"]]
a = [:a,:b,:c,:f,:g,:i,:l,:m]
group_consecs(a)
#=> [[:a, :b, :c], [:f, :g], [:i, :i], [:l, :m]]
Generate an array of seven date objects for an example, then group consecutive dates:
require 'date'
today = Date.today
a = 10.times.map { today = today.succ }.values_at(0,1,2,5,6,8,9)
#=> [#<Date: 2014-08-07 ((2456877j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-08 ((2456878j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-09 ((2456879j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-12 ((2456882j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-13 ((2456883j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-15 ((2456885j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-16 ((2456886j,0s,0n),+0s,2299161j)>]
group_consecs(a)
#=> [[#<Date: 2014-08-07 ((2456877j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-08 ((2456878j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-09 ((2456879j,0s,0n),+0s,2299161j)>
# ],
# [#<Date: 2014-08-12 ((2456882j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-13 ((2456883j,0s,0n),+0s,2299161j)>
# ],
# [#<Date: 2014-08-15 ((2456885j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-16 ((2456886j,0s,0n),+0s,2299161j)>
# ]]
This is some code I wrote for a project a while ago:
class Array
# [1,2,4,5,6,7,9,13].to_ranges # => [1..2, 4..7, 9..9, 13..13]
# [1,2,4,5,6,7,9,13].to_ranges(true) # => [1..2, 4..7, 9, 13]
def to_ranges(non_ranges_ok=false)
self.sort.each_with_index.chunk { |x, i| x - i }.map { |diff, pairs|
if (non_ranges_ok)
pairs.first[0] == pairs.last[0] ? pairs.first[0] : pairs.first[0] .. pairs.last[0]
else
pairs.first[0] .. pairs.last[0]
end
}
end
end
if ($0 == __FILE__)
require 'awesome_print'
ary = [1, 2, 4, 5, 6, 7, 9, 13, 12]
ary.to_ranges(false) # => [1..2, 4..7, 9..9, 12..13]
ary.to_ranges(true) # => [1..2, 4..7, 9, 12..13]
ary = [1, 2, 4, 8, 5, 6, 7, 3, 9, 11, 12, 10]
ary.to_ranges(false) # => [1..12]
ary.to_ranges(true) # => [1..12]
end
It's easy to change that to only return the start/end pairs:
class Array
def to_range_pairs(non_ranges_ok=false)
self.sort.each_with_index.chunk { |x, i| x - i }.map { |diff, pairs|
if (non_ranges_ok)
pairs.first[0] == pairs.last[0] ? [pairs.first[0]] : [pairs.first[0], pairs.last[0]]
else
[pairs.first[0], pairs.last[0]]
end
}
end
end
if ($0 == __FILE__)
require 'awesome_print'
ary = [1, 2, 4, 5, 6, 7, 9, 13, 12]
ary.to_range_pairs(false) # => [[1, 2], [4, 7], [9, 9], [12, 13]]
ary.to_range_pairs(true) # => [[1, 2], [4, 7], [9], [12, 13]]
ary = [1, 2, 4, 8, 5, 6, 7, 3, 9, 11, 12, 10]
ary.to_range_pairs(false) # => [[1, 12]]
ary.to_range_pairs(true) # => [[1, 12]]
end
Here's an elegant solution:
arr = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
output = []
# Sort array
arr.sort!
# Loop through each element in the list
arr.each do |element|
# Set defaults - for if there are no consecutive numbers in the list
start = element
endd = element
# Loop through consecutive numbers and check if they are inside the list
i = 1
while arr.include?(element+i) do
# Set element as endd
endd = element+i
# Remove element from list
arr.delete(element+i)
# Increment i
i += 1
end
# Push [start, endd] pair to output
output.push([start, endd])
end
[Edit: Ha! I misunderstood the question. In your example, for the array
a = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
you showed the desired array of pairs to be:
[[11,14], [19,21], [29,30], [33,33]]
which correspond to the following offsets in a:
[[0,3], [4,6], [7,8], [9,9]]
These pairs respective span the first 4 elements, the next 3 elements, then next 2 elements and the next element (by coincidence, evidently). I thought you wanted such pairs, each with a span one less than the previous, and the span of the first being as large as possible. If you have a quick look at my examples below, my assumption may be clearer. Looking back I don't know why I didn't understand the question correctly (I should have looked at the answers), but there you have it.
Despite my mistake, I'll leave this up as I found it an interesting problem, and had the opportunity to use the quadratic formula in the solution.
tidE]
This is how I would do it.
Code
def pull_pairs(a)
n = ((-1 + Math.sqrt(1.0 + 8*a.size))/2).to_i
cum = 0
n.downto(1).map do |i|
first = cum
cum += i
[a[first], a[cum-1]]
end
end
Examples
a = %w{a b c d e f g h i j k l}
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"]
pull_pairs(a)
#=> [["a", "d"], ["e", "g"], ["h", "i"], ["j", "j"]]
a = [*(1..25)]
#=> [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
# 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
pull_pairs(a)
#=> [[1, 6], [7, 11], [12, 15], [16, 18], [19, 20], [21, 21]]
a = [*(1..990)]
#=> [1, 2,..., 990]
pull_pairs(a)
#=> [[1, 44], [45, 87],..., [988, 989], [990, 990]]
Explanation
First, we'll compute the the number of pairs of values in the array we will produce. We are given an array (expressed algebraically):
a = [a0,a1,...a(m-1)]
where m = a.size.
Given n > 0, the array to be produced is:
[[a0,a(n-1)], [a(n),a(2n-2)],...,[a(t),a(t)]]
These elements span the first n+(n-1)+...+1 elements of a. As this is an arithmetic progession, the sum equals n(n+1)/2. Ergo,
t = n(n+1)/2 - 1
Now t <= m-1, so we maximize the number of pairs in the output array by choosing the largest n such that
n(n+1)/2 <= m
which is the float solution for n in the quadratic:
n^2+n-2m = 0
rounded down to an integer, which is
int((-1+sqrt(1^1+4(1)(2m))/2)
or
int((-1+sqrt(1+8m))/2)
Suppose
a = %w{a b c d e f g h i j k l}
Then m (=a.size) = 12, so:
n = int((-1+sqrt(97))/2) = 4
and the desired array would be:
[['a','d'],['e','g'],['h','i'],['j','j']]
Once n has been computed, constructing the array of pairs is straightforward.
I have an array in Ruby like [3,4,5] and I want to create sub-arrays by diving or multiplying. For example, I want to multiply each number in the array by 2, 3, and 4, returning [[6,9,12],[8,12,16],[10,15,20]]
After that, what's the best way to count the total number of units? In this example, it would be 9, while array.count would return 3.
Thanks
The simplest way I could think of was:
[3,4,5].map { |v|
[3,4,5].map { |w|
w * v
}
}
I'm sure there is a more elegant way.
As for the count you can use flatten to turn it into a single array containing all the elements.
[[9, 12, 15], [12, 16, 20], [15, 20, 25]].flatten
=> [9, 12, 15, 12, 16, 20, 15, 20, 25]
You might find it convenient to use matrix operations for this, particularly if it is one step among several involving matrices, vectors, and/or scalars.
Code
require 'matrix'
def doit(arr1, arr2)
(Matrix.column_vector(arr2) * Matrix.row_vector(arr1)).to_a
end
def nbr_elements(arr1, arr2) arr1.size * arr2.size end
Examples
arr1 = [3,4,5]
arr2 = [3,4,5]
doit(arr1, arr2)
#=> [[ 9, 12, 15],
# [12, 16, 20],
# [15, 20, 25]]
nbr_elements(arr1, arr2)
#=> 9
doit([1,2,3], [4,5,6,7])
#=> [[4, 8, 12],
# [5, 10, 15],
# [6, 12, 18],
# [7, 14, 21]]
nbr_elements([1,2,3], [4,5,6,7])
#=> 12
Alternative
If you don't want to use matrix operations, you could do it like this:
arr2.map { |e| [e].product(arr1).map { |e,f| e*f } }
Here's an example:
arr1 = [1,2,3]
arr2 = [4,5,6,7]
arr2.map { |e| [e].product(arr1).map { |e,f| e*f } }
#=> [[4, 8, 12],
# [5, 10, 15],
# [6, 12, 18],
# [7, 14, 21]]
If I want to interleave a set of arrays in Ruby, and each array was the same length, we could do so as:
a.zip(b).zip(c).flatten
However, how do we solve this problem if the arrays can be different sizes?
We could do something like:
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.inject(0) { |length, elem| length = [length, elem.length].max }
output = Array.new
for i in 0...max_length
args.each { |elem|
output << elem[i] if i < elem.length
}
end
return output
end
But is there a better 'Ruby' way, perhaps using zip or transpose or some such?
Here is a simpler approach. It takes advantage of the order that you pass the arrays to zip:
def interleave(a, b)
if a.length >= b.length
a.zip(b)
else
b.zip(a).map(&:reverse)
end.flatten.compact
end
interleave([21, 22], [31, 32, 33])
# => [21, 31, 22, 32, 33]
interleave([31, 32, 33], [21, 22])
# => [31, 21, 32, 22, 33]
interleave([], [21, 22])
# => [21, 22]
interleave([], [])
# => []
Be warned: this removes all nil's:
interleave([11], [41, 42, 43, 44, nil])
# => [11, 41, 42, 43, 44]
If the source arrays don't have nil in them, you only need to extend the first array with nils, zip will automatically pad the others with nil. This also means you get to use compact to clean the extra entries out which is hopefully more efficient than explicit loops
def interleave(a,*args)
max_length = args.map(&:size).max
padding = [nil]*[max_length-a.size, 0].max
(a+padding).zip(*args).flatten.compact
end
Here is a slightly more complicated version that works if the arrays do contain nil
def interleave(*args)
max_length = args.map(&:size).max
pad = Object.new()
args = args.map{|a| a.dup.fill(pad,(a.size...max_length))}
([pad]*max_length).zip(*args).flatten-[pad]
end
Your implementation looks good to me. You could achieve this using #zip by filling the arrays with some garbage value, zip them, then flatten and remove the garbage. But that's too convoluted IMO. What you have here is clean and self explanatory, it just needs to be rubyfied.
Edit: Fixed the booboo.
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.map(&:size).max
output = []
max_length.times do |i|
args.each do |elem|
output << elem[i] if i < elem.length
end
end
output
end
a = [*1..5]
# => [1, 2, 3, 4, 5]
b = [*6..15]
# => [6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
c = [*16..18]
# => [16, 17, 18]
interleave(a,b,c)
# => [1, 6, 16, 2, 7, 17, 3, 8, 18, 4, 9, 5, 10, 11, 12, 13, 14, 15]
Edit: For fun
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.map(&:size).max
# assumes no values coming in will contain nil. using dup because fill mutates
args.map{|e| e.dup.fill(nil, e.size...max_length)}.inject(:zip).flatten.compact
end
interleave(a,b,c)
# => [1, 6, 16, 2, 7, 17, 3, 8, 18, 4, 9, 5, 10, 11, 12, 13, 14, 15]
How can I substitue an element in an array?
a = [1,2,3,4,5]
I need to replace 5 with [11,22,33,44].flatten!
so that a now becomes
a = [1,2,3,4,11,22,33,44]
Not sure if you're looking to substitute a particular value or not, but this works:
a = [1, 2, 3, 4, 5]
b = [11, 22, 33, 44]
a.map! { |x| x == 5 ? b : x }.flatten!
This iterates over the values of a, and when it finds a value of 5, it replaces that value with array b, then flattens the arrays into one array.
Perhaps you mean:
a[4] = [11,22,33,44] # or a[-1] = ...
a.flatten!
A functional solution might be nicer, how about just:
a[0..-2] + [11, 22, 33, 44]
which yields...
=> [1, 2, 3, 4, 11, 22, 33, 44]
The version of bta using a.index(5) is the fastest one:
a[a.index(5)] = b if a.index(5) # 5.133327 sec/10^6
At least 10% faster than Ryan McGeary's one:
a.map!{ |x| x == 5 ? b : x } # 5.647182 sec/10^6
However, note that a.index(5) only return the first index where 5 is found.
So, given an array where 5 appears more than once, results will be different:
a = [1, 2, 3, 4, 5, 5]
b = [11,22,33,44]
a[a.index(5)] = b if a.index(5)
a.flatten # => [1, 2, 3, 4, 11, 22, 33, 44, 5]
a.map!{ |x| x == 5 ? b : x }
a.flatten # => [1, 2, 3, 4, 11, 22, 33, 44, 11, 22, 33, 44]
Array#delete will return the item or nil. You may use this to know whether or not to push your new values
a.push 11,22,33,44 if a.delete 5
You really don't have to flatten if you just concatenate. So trim the last element off the first array and concatenate them:
a = [ 1, 2, 3, 4, 5 ] #=> [1, 2, 3, 4, 5]
t = [11, 22, 33, 44] #=> [11, 22, 33, 44]
result = a[0..-2] + t #=> [1, 2, 3, 4, 11, 22, 33, 44]
a[0..-2] is a slice operation that takes all but the last element of the array.
Hope it helps!
This variant will find the 5 no matter where in the array it is.
a = [1, 2, 3, 4, 5]
a[a.index(5)]=[11, 22, 33, 44] if a.index(5)
a.flatten!
Here is another simple way to replace the value 5 in the array:
a[-1, 1] = [11, 22, 33, 44]
This uses the Array#[]= method. I'm not exactly sure why it works though.
gweg, not sure what you're trying to do here, but are you looking for something like this?
a = [1, 2, 3, 4, 5]
a.delete_at(4)
a = a.concat([11,22,33,44])
There are a number of ways of doing this -- I don't think the code above is especially nice looking. It all depends on the significance of '5' in your original array.