0

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.

0

4 Answers 4

2
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)
Sign up to request clarification or add additional context in comments.

Comments

1

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?}

2 Comments

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

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]

Comments

0

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]

2 Comments

My example may be confusing as the elements match the index. Let me change it and it might be clearer. Apologies.
sum is almost built-in if you use accumulator: arr.inject(&:+)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.