Internet man
Internet man

Reputation: 1155

Ruby array manipulation based on index properties

I have an array of in Ruby. I want to:

  1. 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.

  2. Perform some operation on the subset that depends on the entire subset - let's say subset.map{|element| subset.sum - element}

  3. 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

Answers (4)

rampion
rampion

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

glenn jackman
glenn jackman

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

Jimmy Stenke
Jimmy Stenke

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

August Lilleaas
August Lilleaas

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

Related Questions