Amanda Lilley
Amanda Lilley

Reputation: 181

Ruby select by index

I am trying to select elements out of an array:

arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n']

whose index is a Fibonacci number. I want the result:

['a', 'b', 'c', 'd', 'f', 'i', 'n']

My code returns both the element and the index.

def is_fibonacci?(i, x = 1, y = 0)
  return true if i == x || i == 0
  return false if x > i
  is_fibonacci?(i, x + y, x)
end

arr.each_with_index.select do |val, index|
  is_fibonacci?(index)
end

This code returns:

[["a", 0], ["b", 1], ["c", 2], ["d", 3], ["f", 5], ["i", 8], ["n", 13]]

Please help me understand how I can still iterate through the array and evaluate the index but only return the element.

Upvotes: 18

Views: 24514

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110755

Here's another way to do it.

index_gen = Enumerator.new do |y|
  i = 0
  j = 1
  loop do
    y.yield i unless i==j
    i, j = j, i + j
  end
end
  #=> #<Enumerator: #<Enumerator::Generator:0x007fa3eb979028>:each> 

arr.values_at(*index_gen.take_while { |n| n < arr.size })
  #=> ["a", "b", "c", "d", "f", "i", "n"]

or

index_gen.take_while { |n| n < arr.size }.map { |n| arr[n] }
  #=> ["a", "b", "c", "d", "f", "i", "n"]

Note:

  • I have assumed Fibonacci numbers begin at zero (rather than one), which is the modern definition.
  • The Fibonacci sequence begins 0, 1, 1, 2,.... The construction of the enumerator index_gen skips the second 1.
  • index_gen.take_while { |n| n < arr.size } #=> [0, 1, 2, 3, 5, 8, 13]

Upvotes: 1

Frederick Cheung
Frederick Cheung

Reputation: 84182

You can change the last bit of your code to

arr.select.with_index do |val, index|
  is_fibonacci?(index)
end

This works because if you call a method such as select without a block, you get an Enumerator object, on which you can then chain more Enumerable methods.

In this case I've used with_index, which is very similar to calling each_with_index on the original array. However because this happens after the select instead of before, select returns items from the original array, without the indices appended

Upvotes: 35

user12341234
user12341234

Reputation: 7223

Your code seems great so far, I wouldn't change it. You can go over your results after the fact and change the [element, index] pairs to only contain the element by mapping over each pair and only taking the first:

>> results = [["a", 0], ["b", 1], ["c", 2], ["d", 3], ["f", 5], ["i", 8], ["n", 13]]
>> results.map(&:first)
=> ["a", "b", "c", "d", "f", "i", "n"]

Upvotes: 3

Related Questions