tags:

views:

238

answers:

4

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.

A: 

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]
August Lilleaas
My example may be confusing as the elements match the index. Let me change it and it might be clearer. Apologies.
Rock and or Roll
samuil
+1  A: 

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?}
Jimmy Stenke
Yes, that looks like it could. The sum method was just made up really, could use something like inject(0){|s,i| s+= i unless i.nil?}...
Rock and or Roll
I don't think inject would like it that you return nil. inject(0){|m,i| i.nil? ? m : m + i} would be better :)And, yeah. That is basically the way Rails do it, except for the nil check though.
Jimmy Stenke
Agreed - hadn't tested the code.
Rock and or Roll
+2  A: 
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)
glenn jackman
+1  A: 

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]
rampion