Reputation: 1155
I have an array of in Ruby. I want to:
Get a subset of the elements based on their position in the array - say every 5th element. I can do this with each_index, or extend and create a select_with_index method.
Perform some operation on the subset that depends on the entire subset - let's say subset.map{|element| subset.sum - element}
This is the bit I'm stuck on: Create a new array with the correct items replaced by the items in step 2. Eg:
So my highly convoluted example might have:
Start: [3,0,6,11,77,2,1,5,48,9,122,0,43,13,564]
Select: [3,2,122]
Map: [124,125,5]
Replace: [124,0,6,11,77,125,1,5,48,9,5,0,43,13,564]
How can I perform the replacement in an elegant fashion? Is there a way to create method that would take combine the two arrays and take a block {|i| i % 5 == 0}?
(This is motivated by an approach to writing a compact Sudoku solver in order to learn some more Ruby...)
EDIT: Have changed the example values. Hopefully this is clearer now.
Upvotes: 0
Views: 1067
Reputation: 89053
I'd probably just solve this with Enumerable#enum_for(:each_with_index)
require 'enumerator'
values = [3,0,6,11,77,2,1,5,48,9,122,0,43,13,564]
subset_with_indexes = values.enum_for(:each_with_index).select { |v,i| i % 5 == 0 }
#=> [ [3,0], [2,5], [122,10] ]
subset_sum = subset_with_indexes.inject(0) { |s,(v,i)| s+v }
#=> 127
subset_with_indexes.each do |v,i|
values[i] = subset_sum - v
end
values #=> [124, 0, 6, 11, 77, 125, 1, 5, 48, 9, 5, 0, 43, 13, 564]
Or
require 'enumerator'
values = [3,0,6,11,77,2,1,5,48,9,122,0,43,13,564]
values_with_indexes = values.enum_for(:each_with_index)
subset_sum = values_with_indexes.inject do |s,(v,i)|
i % 5 == 0 ? s + v : s
end #=> 127
new_values = values_with_indexes.map do |v,i|
i % 5 == 0 ? subset_sum - v : v
end #=> [124, 0, 6, 11, 77, 125, 1, 5, 48, 9, 5, 0, 43, 13, 564]
Upvotes: 1
Reputation: 246754
a = [3, 0, 6, 11, 77, 2, 1, 5, 48, 9, 122, 0, 43, 13, 564]
# per your requirements
def replace_indices(ary, &index_selector)
indices = ary.each_index.select(&index_selector)
sum = indices.inject(0) {|sum, i| sum += ary[i]}
indices.each {|i| ary[i] = sum - ary[i]}
ary
end
p new = replace_indices(a.dup) {|i| i % 5 == 0}
# just pass the "index step value"
def replace_each_step(ary, step)
sum = 0
ary.each_index .
select {|i| i % step == 0} .
collect {|i| sum += ary[i]; ary[i]} .
each_with_index {|e,i| ary[i*step] = sum - e}
ary
end
p new = replace_each_step(a.dup, 5)
Upvotes: 2
Reputation: 11220
Assuming the sum method is the one from Rails, this might work:
a = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
b = []
a.each_index {|i| b[i] = a[i] if i%5 == 0}
c = b.map{|p| p.nil? ? nil : b.sum{|i| i.nil? ? 0 : i} - p}
c.each_index {|i| a[i] = c[i] unless c[i].nil?}
I leave it up to you to refactor it into something useful :) Basically, the theory is to keep all the indexes in the original array even in the subset. That way it is easy to know which ones to replace later. You can also use a Hash for it if there are more complex calculations.
Here is a bit more compact version of it:
a = [3,0,6,11,77,2,1,5,48,9,122,0,43,13,564]
b = []
a.each_index {|i| b[i] = a[i] if i%5 == 0}
b.each_with_index {|obj, i|
a[i] = b.inject(0){|m,v| v.nil? ? m : v} - obj unless obj.nil?}
Upvotes: 1
Reputation: 54593
You can do it in one pass if you don't have to know the length of the subset.
a = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
p a.map {|i| i % 5 == 0 ? "foo" : i }
# => ["foo", 1, 2, 3, 4, "foo", 6, 7, 8, 9, "foo", 11, 12, 13, 14, "foo"]
Assuming you have an Array#sum
implemented elsewhere, you can do it in two passes like so:
a = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
sum = a.select {|i| i % 5 == 0 }.sum
p a.map {|i| i % 5 == 0 ? sum - i : i }
# => [30, 1, 2, 3, 4, 25, 6, 7, 8, 9, 20, 11, 12, 13, 14, 15]
Upvotes: 0