Converting a array to an array of ranges in Ruby - ruby

I have an array of numbers. I want to convert it to an array of ranges. Example:
input = [0,10,20,30]
output = [0..10, 10..20, 20..30, 30..Infinity]
Is there some direct way to do it in Ruby?

Yes, possible :
input = [0,10,20,30]
(input + [Float::INFINITY]).each_cons(2).map { |a,b| a..b }
# => [0..10, 10..20, 20..30, 30..Infinity]

One way:
Code
output = input.zip(input[1..-1] << 1.0/0).map { |r| Range.new(*r) }
Explanation
input = [0,10,20,30]
a = input[1..-1]
#=> [10, 20, 30]
b = a << 1.0/0
#=> [10, 20, 30, Infinity]
c = input.zip(b)
#=> [[0, 10], [10, 20], [20, 30], [30, Infinity]]
output = c.map { |r| Range.new(*r) }
#=> [0..10, 10..20, 20..30, 30..Infinity]
Possible alternative
If you instead wanted an array of arrays, you would just change the block:
output = input.zip(input[1..-1] << 1.0/0).map { |f,l| [f..l] }
#=> [[0..10], [10..20], [20..30], [30..Infinity]]

Related

How to sum values inside array of hash with same keys

I want to sum the values of same keys like
arr = [{"69120090" => [1, 2, 3]}, {"69120090" => [4, 5, 6]}]
I need to result in:
result = [{"69120090" => [5, 7, 9]}]
Reduce by Hash#merge! with a block:
arr = [{"69120090"=> [1, 2, 3] }, {"69120090"=> [4, 5, 6] }]
arr.each_with_object({}) do |h, acc|
acc.merge!(h) { |_, v1, v2| v1.zip(v2).map(&:sum) }
end
#⇒ {"69120090"=>[5, 7, 9]}
The above accepts any number of hashes with any number of keys each.
Just to have another option, given the array:
arr = [{ a: [1, 2, 3], b:[8,9,0] }, { a: [4, 5, 6], c: [1,2,3] }, { b: [0,1,2], c: [1,2,3] } ]
You could write:
tmp = Hash.new{ |k,v| k[v] = [] }
arr.each { |h| h.each { |k,v| tmp[k] << v } }
tmp.transform_values { |k| k.transpose.map(&:sum) }
Which returns
tmp #=> {:a=>[5, 7, 9], :b=>[8, 10, 2], :c=>[2, 4, 6]}
As one liner:
(arr.each_with_object(Hash.new{ |k,v| k[v] = [] }) { |h, tmp| h.each { |k,v| tmp[k] << v } }).transform_values { |k| k.transpose.map(&:sum) }

How to convert a string of values with a range to an array in Ruby

I'm trying to parse a string of numbers and ranges joined by ",", and convert it to a numerical array. I have this as input: "1,3,6-8,5", and would like to have an array like this: [1,3,5,6,7,8].
I can only do it without the range, like this:
"12,2,6".split(",").map { |s| s.to_i }.sort #=> [2, 6, 12]
With a range, I cannot do it:
a = "12,3-5,2,6"
b = a.gsub(/-/, "..") #=> "12,3..5,2,6"
c = b.split(",") #=> ["12", "3..5", "2", "6"]
d = c.sort_by(&:to_i) #=> ["2", "3..5", "6", "12"]
e = d.split(",").map { |s| s.to_i } #>> Error
How can I do this?
I was also thinking to use the splat operator in map, but splat doesn't accept strings like [*(3..5)].
"12,3-5,2,6".
gsub(/(\d+)-(\d+)/) { ($1..$2).to_a.join(',') }.
split(',').
map(&:to_i)
#⇒ [12, 3, 4, 5, 2, 6]
"1,3,6-8,5".split(',').map do |str|
if matched = str.match(/(\d+)\-(\d+)/)
(matched[1].to_i..matched[2].to_i).to_a
else
str.to_i
end
end.flatten
or
"1,3,6-8,5".split(',').each_with_object([]) do |str, output|
if matched = str.match(/(\d+)\-(\d+)/)
output.concat (matched[1].to_i..matched[2].to_i).to_a
else
output << str.to_i
end
end
or strict
RANGE_PATTERN = /\A(\d+)\-(\d+)\z/
INT_PATTERN = /\A\d+\z/
"1,3,6-8,5".split(',').each_with_object([]) do |str, output|
if matched = str.match(RANGE_PATTERN)
output.concat (matched[1].to_i..matched[2].to_i).to_a
elsif str.match(INT_PATTERN)
output << str.to_i
else
raise 'Wrong format given'
end
end
"1,3,6-8,5".split(',').flat_map do |s|
if s.include?('-')
f,l = s.split('-').map(&:to_i)
(f..l).to_a
else
s.to_i
end
end.sort
#=> [1, 3, 5, 6, 7, 8]
"1,3,6-8,5"
.scan(/(\d+)\-(\d+)|(\d+)/)
.flat_map{|low, high, num| num&.to_i || (low.to_i..high.to_i).to_a}
#=> [1, 3, 6, 7, 8, 5]

Any instance of key anywhere in a nested hash

Given a hash like so:
h = {
"actual_amount" => 20,
"otherkey" => "value",
"otherkey2" => [{"actual_amount" => 30, "random_amount" => 45}]
}
where there are any number of layers of nesting, is there a simple way to pluck all the key-value pairs (or just the values) of the keys that are actual_amount?
I've assumed the values of keys are literals or arrays of hashes.
This question clearly calls for a recursive solution.
def amounts(h)
h.each_with_object([]) do |(k,v),a|
case v
when Array
v.each { |g| a.concat amounts(g) }
else
a << v if k == "actual_amount"
end
end
end
Suppose
h = {
"actual_amount"=>20,
1=>2,
2=>[
{ "actual_amount"=>30,
3=>[
{ "actual_amount" => 40 },
{ 4=>5 }
]
},
{ 5=>6 }
]
}
then
amounts(h)
#=> [20, 30, 40]
Using the hash, provided by Cary, as an input:
▶ flatten = ->(inp) do
▷ [*(inp.respond_to?(:map) ? inp.map(&:flatten) : inp)]
▷ end
▶ res = flatten(h).first
▶ res.select.with_index do |_, i|
▷ i > 0 && res[i - 1] == 'actual_amount'
▷ end
#⇒ [20, 30, 40]

Ruby: reuse value in a block without assigning it to variable (write object method on the fly)

There are several situations where I'd like to apply a block to a certain value and use the value inside this block, to use the enumerator coding style to every element.
If such method would be called decompose, it would look like:
result = [3, 4, 7, 8].decompose{ |array| array[2] + array[3] } # result = 15
# OR
result = {:key1 => 'value', :key2 => true}.decompose{ |hash| hash[:key1] if hash[:key2] } # result = 'value'
# OR
[min, max] = [3, 4, 7, 8].decompose{ |array| [array.min, array.max] } # [min, max] = [3, 8]
# OR
result = 100.decompose{ |int| (int - 1) * (int + 1) / (int * int) } # result = 1
# OR
result = 'Paris'.decompose{ |str| str.replace('a', '') + str[0] } # result = 'PrisP'
The method simply yields self to the block, returning the block's result. I don't think it exists, but you can implement it yourself:
class Object
def decompose
yield self
end
end
[3, 4, 7, 8].decompose{ |array| array[2] + array[3] }
#=> 15
{:key1 => 'value', :key2 => true}.decompose{ |hash| hash[:key1] if hash[:key2] }
#=> "value"
[3, 4, 7, 8].decompose{ |array| [array.min, array.max] }
#=> [3, 8]
It actually exists (I could not believe it didn't).
It is called BasicObject#instance_eval. Here's the doc: http://apidock.com/ruby/BasicObject/instance_eval
Available since Ruby 1.9 as this post explains: What's the difference between Object and BasicObject in Ruby?

How can I convert this array so that each element represents the cumulative value of the previous elements? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Cumulative array sum in Ruby
I have an array of integers like this
[20, 25, 40, 60]
How can I turn it into an array with each element representing the cumulative value of the elements before it, including itself?
[20, 45, 85, 145]
I'm using Rails 3.2.0 & ruby 1.9.3
s = 0
[20, 25, 40, 60].map{|e| s += e}
[20, 25, 40, 60].reduce([]) do |arr, v|
arr << (arr.last || 0) + v
end
Or an ugly one liner.
[20, 25, 40, 60].reduce([0]){ |a, v| a << a[-1] + v }[1..-1]
array = [20, 25, 40, 60]
(array.size - 1).times { |i| array[i + 1] += array[i] }
puts array
# => [20, 45, 85, 145]
arr = [20, 25, 40, 60]
first = []
sum = 0
arr.each do |e|
sum += e
first << sum
end
puts first
arr.each_with_index.map{|x, i| x + (i==0 ? 0 : arr[0..i-1].inject(:+))}
=> [20, 45, 85, 145]
Matlab:
B = cumsum(A)
Ruby:
class Array
def ruby_cumsum!
(1..size-1).each {|i| self[i] += self[i-1] }
self
end
end

Resources