Reputation: 1621
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?
Upvotes: 1
Views: 1916
Reputation: 554
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
Upvotes: 0
Reputation: 110665
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.
Upvotes: 1
Reputation: 51151
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.
Upvotes: 4
Reputation: 40516
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)
Upvotes: 2
Reputation: 37409
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"
Upvotes: 1