1

I wrote a method to get the next element after a given element inside an array. If I provide the method with c, I want it to return e; if e, I want it to return a, etc.:

array = %w[a f c e]

def find_element_after(element, array)
  index = array.find_index(element) + 1
  array.at(index)
end

array.find_element_after("c", array)

If I pass in the last element I will get nil. But I want to return the first element instead.

I can solve this with if and else. But I want to know if Ruby has better way?

3
  • 1
    It's not necessary to strike-out changed text. We can see the edit history if something doesn't make sense. Commented Nov 27, 2014 at 15:21
  • 1
    One thing to consider is how this will slow down as array grows in size because you are using a linear search. Instead I'd probably use some sort of linked-list or a hash inside a custom class that manages/maintains that hash. The key is what you search for and the value is the next element. That would be a very consistent lookup speed with a little slower insertion time. Commented Nov 27, 2014 at 15:38
  • @theTinMan thanks for pointing those two things out. In this case I'll have probably less than 100 items, which should be trivial. It's good to know, though. Commented Nov 27, 2014 at 16:41

5 Answers 5

4

You could modify your method, taking array size into account, like this:

array = %w[a f c e]

def find_element_after(element, array)
  index = array.find_index(element) + 1
  array.at(index % array.size) # divide calculated index modulo array size
end

find_element_after('e', array)
# => "a"

If you want to make your method proof to passing argument that isn't member of array, you could do:

def find_element_after(element, array)
  index = array.find_index(element)
  array.at((index + 1) % array.size) if index
end
find_element_after('u', array)
# => nil

or:

def find_element_after(element, array)
  return nil unless array.include?(element)
  index = array.find_index(element)
  array.at(index % array.size)
end

as you see, there's many possible solutions. Feel free to experiment.

Sign up to request clarification or add additional context in comments.

Comments

2

If you pass in the last element, it actually works. The index gets evaluated to the last index, and retrieving the element at lastindex + 1 from the array returns nil.

The problem is when you provide an element that is not present in the array. It's this scenario that will result in the index being nil and then throwing the NoMethodError when you call + 1 on it.

To fix this case, define your method like this:

def find_element_after(element, array)
  index = array.find_index(element)
  array.at(index + 1) if index
end

Here's a demo showing how it works now (run online):

array = %w[a f c e]

def find_element_after(element, array)
  index = array.find_index(element)
  array.at(index + 1) if index
end

p find_element_after("c", array) # element in the middle - prints "e"
p find_element_after("e", array) # last element - prints "nil"
p find_element_after("z", array) # element not present in the array - prints "nil" (no error)

1 Comment

Thanks for pointing out the error. The issue is I never want to return nil when the last element is passed. I want to return the first element instead, going in a circle essentially. But that's easy enough I now understand the error.
1

You can use each_cons to iterate the array using pairs:

def find_element_after(element, array)
  cons = array.each_cons(2).find { |i1, i2| i1 == element }
  cons.nil? ? array.first : cons.last
end

find_element_after('c', array)
# => "e"
find_element_after('e', array)
# => "a"

Comments

1

Here are some other ways to do that.

#1 Use the method Array#rotate

def nxt(arr, e)
  arr.rotate(arr.index(e)+1).first
end

arr = %w[a f c e]

nxt(arr, 'a') #=> "f"
nxt(arr, 'f') #=> "c"
nxt(arr, 'c') #=> "e"
nxt(arr, 'e') #=> "a"

I think this reads well, but it has the disadvantage of creating a temporary array the size of arr.

#2 Use Array#rotate to construct a hash

h = Hash[arr.zip(arr.rotate(1))]
  #=> {"a"=>"f", "f"=>"c", "c"=>"e", "e"=>"a"}

h['a'] #=> "f"
h['f'] #=> "c"
h['c'] #=> "e"
h['e'] #=> "a"

#3 Use Array#cycle to create an enumerator

enum = arr.cycle

def nxt(enum, v)
  until enum.next == v do
  end
  enum.next
end

nxt(enum, 'a') #=> "f"
nxt(enum, 'f') #=> "c"
nxt(enum, 'c') #=> "e"
nxt(enum, 'e') #=> "a"

The latter two approaches should be relatively efficient if you had several values to map.

Comments

0

If need a cycle so to get the first element if the last passed

def find_after(element, array)
  idx = array.index(element)
  array[idx - array.length + 1]
end

Comments

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.