Ruby: adding second element in array where first element is the same - ruby

[[1, 20],[2,30],[1,5],[4,5]]
In ruby how does one go through this array and add the second element if the first is the same with and output such as:
[[1, 25],[2,30],[4,5]]

If order isn't important, inserting the pairs into a hash, adding if the key already exists, and then flattening the hash back into an array is a neat way. In irb:
>> a = [[1, 20],[2,30],[1,5],[4,5]]
=> [[1, 20], [2, 30], [1, 5], [4, 5]]
>> a.inject(Hash.new(0)) { |h, p| h[p[0]] += p[1]; h }.to_a
=> [[1, 25], [2, 30], [4, 5]]

A solution in Ruby:
L = [[1, 20],[2,30],[1,5],[4,5]]
d = {}
L.each{|k,v| d[k] = (d[k]||0) + v}
A solution in Python:
L = [[1, 20],[2,30],[1,5],[4,5]]
d = {}
for k,v in L:
d[k] = d.get(k,0) + v

You can do this (warning, the output is a bit garbled, you may want to convert to array):
myArray = [[1, 20],[2,30],[1,5],[4,5]]
outputHash = {}
myArray.each do |values|
outputHash[values[0]] = outputHash[values[0]].to_i + values[1]
end
puts outputHash

Related

Another efficient way to write a nested "for" in ruby?

I have an exercise from HackerRank, that can easy resolve as the follow:
def divisibleSumPairs(n, k, ar)
validPairs = 0
for i in 0..ar.size-1
for j in i+1..ar.size-1
validPairs += 1 if (ar[i]+ar[j]) % k == 0
end
end
validPairs
end
n, k = gets.strip.split(' ')
n = n.to_i
k = k.to_i
ar = gets.strip
ar = ar.split(' ').map(&:to_i)
result = divisibleSumPairs(n, k, ar)
puts result;
But that nested for is bothering me. Is there any other way to do that in Ruby?
Yes. You can use combination method from Array class:
a = [1,2,3,4,5]
a.combination(2).to_a #=> [[1, 2], [1, 3], [1, 4], [1, 5], [2, 3], [2, 4], [2, 5], [3, 4], [3, 5], [4, 5]]
Then you can iterate on those pairs, so the code should look like that (haven't run it, though):
def divisibleSumPairs(n, k, ar)
validPairs = 0
ar.combination(2).each do |pair|
validPairs += 1 if (pair[0]+pair[1]) % k == 0
end
validPairs
end

How do I find the location of an integer in an array of arrays in ruby?

Given:
a = [[1,2,3,4],
[1,2,3,7],
[1,2,3,4]]
What do I need to do to output the location of the 7 as (1,3)?
I've tried using .index to no avail.
require 'matrix'
a = [[1, 2, 3, 4],
[1, 2, 3, 7],
[1, 2, 3, 4]]
Matrix[*a].index(7)
=> [1, 3]
If your sub-arrays are all the same width, you can flatten it into a single array and think of the position as row_num * row_width + col_num:
idx = a.flatten.index(7)
row_width = a[0].length
row = idx / row_width
col = idx - (row * row_width)
puts [row, col] # => [1, 3]
Or you could just iterate it to find all matches:
def find_indices_for(array, value)
array.with_object([]).with_index do |(row, matches), row_index|
matches << [row_index, row.index(value)] if row.index(value)
end
end
find_indices_for(a, 7) # => [[1, 3]]
find_indices_for(a, 2) # => [[0, 1], [1, 1], [2, 1]]
each_with_index works pretty well here:
def locator(array, number)
locations = Array.new
array.each_with_index do |mini_array, index|
mini_array.each_with_index do |element, sub_index|
locations << [index, sub_index] if element == number
end
end
locations
end
Now, locator(array, number) will return an array of containing all the locations of number in array.
def locs2D(a,e)
a.size.times.with_object([]) do |row,arr|
row.size.times { |col| arr << [row,col] if a[row][col] == e }
end
end
locs2D(a,7) #=> [[1, 3]]
locs2D(a,3) #=> [[0, 2], [1, 2], [2, 2]]

Returning all maximum or minimum values that can be multiple

Enumerable#max_by and Enumerable#min_by return one of the relevant elements (presumably the first one) when there are multiple max/min elements in the receiver. For example, the following:
[1, 2, 3, 5].max_by{|e| e % 3}
returns only 2 (or only 5).
Instead, I want to return all max/min elements and in an array. In the example above, it would be [2, 5] (or [5, 2]). What is the best way to get this?
arr = [1, 2, 3, 5]
arr.group_by{|a| a % 3} # => {1=>[1], 2=>[2, 5], 0=>[3]}
arr.group_by{|a| a % 3}.max.last # => [2, 5]
arr=[1, 2, 3, 5, 7, 8]
mods=arr.map{|e| e%3}
find max
max=mods.max
indices = []
mods.each.with_index{|m, i| indices << i if m.eql?(max)}
arr.select.with_index{|a,i| indices.include?(i)}
find min
min = mods.min
indices = []
mods.each.with_index{|m, i| indices << i if m.eql?(min)}
arr.select.with_index{|a,i| indices.include?(i)}
Sorry for clumsy code, will try to make it short.
Answer by #Sergio Tulentsev is the best and efficient answer, found things to learn there. +1
This is the hash equivalent of #Serio's use of group_by.
arr = [1, 2, 3, 5]
arr.each_with_object(Hash.new { |h,k| h[k] = [] }) { |e,h| h[e%3] << e }.max.last
#=> [2, 5]
The steps:
h = arr.each_with_object(Hash.new { |h,k| h[k] = [] }) { |e,h| h[e%3] << e }
#=> {1=>[1], 2=>[2, 5], 0=>[3]}
a = h.max
#=> [2, [2, 5]]
a.last
#=> [2, 5]

How do I add a cumulative sum to an array for only one value?

I have an array of arrays with x and y values:
[[some_date1, 1], [some_date2, 3], [some_date3, 5], [some_date4, 7]]
The result should only sum the y values (1, 3, 5, 7) so that the result is like this:
[[some_date1, 1], [some_date2, 4], [some_date3, 9], [some_date4, 16]]
How is this possible in Ruby?
Yes, this is possible in Ruby. You can use [map][1] and do something like this:
sum = 0
array.map {|x,y| [x, (sum+=y)]}
This is how it works. For the given the input:
array = ["one", 1], ["two", 2]
It will iterate through each of the elements in the array e.g.) the first element would be ["one", 1].
It will then take that element (which is an array itself) and assign the variable x to the first element in that array e.g.) "one" and y to the second e.g.) 1.
Finally, it will return an array with the result like this:
=> ["one", 1], ["two", 3]
You can use map:
a = [[:some_date1, 1], [:some_date2, 3], [:some_date3, 5], [:some_date4, 7]]
sum = 0
a.map { |f, v| [f, (sum = sum + v)]}
=> [[:some_date1, 1], [:some_date2, 4], [:some_date3, 9], [:some_date4, 16]]
Since sum will be nil in the first iteration it is necessary to call to_i on it.
a = [['some_date1', 1], ['some_date2', 3], ['some_date3', 5], ['some_date4', 7]]
a.each_cons(2){|a1, a2| a2[1] += a1[1]}
last = 0
arr.map do |a, b|
last = last + b
[a, last]
end
I'd use:
ary = [['some_date1', 1], ['some_date2', 3], ['some_date3', 5], ['some_date4', 7]]
ary.inject(0) { |m, a|
m += a[-1]
a[-1] = m
}
After running, ary is:
[["some_date1", 1], ["some_date2", 4], ["some_date3", 9], ["some_date4", 16]]
The reason I prefer this is it doesn't require the addition of an accumulator variable. inject returns a value but it gets thrown away without an assignment.

Result of inject is nil

I'm trying to accumulate some values if they match a condition.
Why is this snippet returning nil, when I would expect it to return 2?
[[1, 2], [2, 3], [3, 8], [4, 2]].inject(0) { |s, e| s + e[1] if e[0] <= 1}
Isn't inject the right method for this?
You should return s;
[[1, 2], [2, 3], [3, 8], [4, 2]].inject(0) { |s, e| s += e[1] if e[0] <= 1; s}
Little cleaner
[[1, 2], [2, 3], [3, 8], [4, 2]].inject(0){|s,(k,v)| s += (k<2 ? v : 0)}
You could do it in multiple steps:
>> a = [[1, 2], [2, 3], [3, 8], [4, 2]]
>> a.select { |e| e.first <= 1 }.inject(0) { |s, e| s += e.last }
=> 2
>> a.select { |e| e.first <= 1 }.map(&:last).inject(0, :+)
=> 2
Doing it all with a single inject should be more efficient but breaking it into pieces might be cleaner and the speed difference won't be noticeable unless you have really large arrays.
If you don't mind emulating a pointer with a Hash or Array, you can do it with each_with_object:
>> a.each_with_object({ :sum => 0 }) { |(k,v), m| m[:sum] += v if k <= 1 }[:sum]
=> 2
>> a.each_with_object([0]) { |(k,v), m| m[0] += v if k <= 1 }.first
=> 2
The result of your inject block is used as the value of s on the next call to your block. On the second iterator, this:
s + e[1] if e[0] <= 1
will have a value of nil because e[0] will be 2. Subsequent iterations also return nil from your block because every e[0] is larger than 1 except the first one. This is why you need to return s from your block. If you had an array like this:
[[1, 2], [2, 3], [3, 8], [4, 2], [1, 11]]
then you wouldn't even get nil out of your inject, you'd just can an exception:
NoMethodError: undefined method `+' for nil:NilClass
when your block tried to add 11 to nil.
Yet another way:
xs.map { |k, v| v if k <= 1 }.compact.inject(0, :+)
Note how Ruby suffers a bit from the lack of list-comprehensions and we have to (somewhat inefficiently) emulate it with map + compact. In a language with LC it'd look better: sum(v for (k, v) in xs if k <= 1).

Resources