0

How can I write an argument that captures the first non-consecutive element in an array of numbers. So far I've tried using the each_cons method, which has failed empathically as you'll see in the code below. I can't figure out what to combine it with. There doesn't seem to be much documentation on how I can approach this so if any of you have any suggestions please do share.

This is what my code currently looks like:

def first_non_consecutive(arr)
  
  arr.each_cons(6) { |a| p a } # the block is just to test the output 
 
end

Desired outcome:

So for example if I had a sequence of [1,2,3,4,6,7,8] then 1 then 2 then 3 then 4 are all consecutive but 6 is not, so that's the first non-consecutive number.

Current outcome:

Expected: 6, instead got: nil
 Log
[1, 2, 3, 4, 5, 6] # => sequential array should return null2, would you recommend an if statement for this?
[2, 3, 4, 5, 6, 7] # => 6 is the first non-consecutive element

Please note the expression should also be able to take negative integers.

5 Answers 5

2

What does it mean to be "non-consecutive"?

It means that the first number plus one is less than the second number or the difference of the two elements is not one, or …. There are many different ways to express this. So, you can simply search for the first element that satisfies that condition:

arr.each_cons(2).find {|a, b| b - a != 1 }&.last
Sign up to request clarification or add additional context in comments.

2 Comments

This is a great one liner @Jörg but I don't fully understand how it's working
In English, it is something like "for each consecutive pair of elements, find the first pair, where the second number is not 1 greater than the first number".
1

Do Pairwise Comparisons Instead of Six-at-a-Time

You have three obvious problems with your current code:

  1. The #each_cons method should probably compare two items at a time, not six.
  2. Your expectation that 6 is not consecutive seems wrong, as it comes right after 5.
  3. You haven't defined whether you need to sort the input array or not.

The following seems to do what you're asking:

def first_nonconsecutive_element array
  array.sort.each_cons(2) do |el1, el2|
    return el2 if el2 != el1.succ
  end
end

first_nonconsecutive_element [1, 2, 3, 4, 5, 6]
#=> nil

first_nonconsecutive_element [1, 3, 5]
#=> 3

1 Comment

Thanks Todd! Never seen that .succ() method
1
def first_non_consecutive(arr)
  arr.each_cons(2) { |a, b| return b if a + 1 != b }
end

p first_non_consecutive([1, 2, 3, 6])
# => 6

And if the consistency is not broken, then it will return nil

Comments

0

Here are three more solutions.

Use an enumerator

def first_non_consecutive(arr)
  enum = arr.each
  loop { return enum.next if enum.next != enum.peek-1 }
  nil
end
first_non_consecutive [1,2,4]
  #=> 4
first_non_consecutive [1,2,3]
  #=> nil
first_non_consecutive [1]
  #=> nil
first_non_consecutive []
  #=> nil

See Array#each, Kernel#loop, Enumerator#next and Enumerator#peek.

For

arr = [1,2,4]

the steps are as follows.

enum = arr.each
  #=> #<Enumerator: [1, 2, 4]:each> 
n = enum.next
  #=> 1 
p = enum.peek
  #=> 2 
n != p-1
  #=> false 
n = enum.next
  #=> 2 
p = enum.peek
  #=> 4 
n != p-1
  #=> true 
n = enum.next
  #=> 4
return 4

For

arr = [1,2,3]

the steps are as follows.

enum = arr.each
  #=> #<Enumerator: [1, 2, 3]:each> 
n = enum.next
  #=> 1 
p = enum.peek
  #=> 2 
n != p-1
  #=> false 
n = enum.next
  #=> 2 
p = enum.peek
n != p-1
  #=> false 
n = enum.next
  #=> 3 
p = enum.peek
  #=> StopIteration (iteration reached an end)

The exception is handled by Kernel#loop by breaking out of the loop, returning [1,2,3], which is disregarded, after which nil is returned. Note:

enum.rewind
loop { return enum.next if enum.next != enum.peek-1 }
  #=> [1,2,3]

See Enumerator#rewind.

return enum.next
  #=> 4

One can write enum = arr.to_enum in place of enum = arr.each. See Object#to_enum.

Determine index of desired element of array

def first_non_consecutive(arr)
  i = 1.upto(arr.size-1).find { |i| arr[i] != arr[i-1] + 1 }
  arr[i] unless i.nil?
end
first_non_consecutive [1,2,4]
  #=> 4
first_non_consecutive [1,2,3]
  #=> nil
first_non_consecutive [1]
  #=> nil
first_non_consecutive []
  #=> nil

Save last element examined

def first_non_consecutive(arr)
  arr.reduce do |last,n|
    return n if n != last+1
    n
  end
  nil
end
first_non_consecutive [1,2,4]
  #=> 4
first_non_consecutive [1,2,3]
  #=> nil
first_non_consecutive [1]
  #=> nil
first_non_consecutive []
  #=> nil

This uses the form of Enumerable#reduce (a.k.a. inject) that has no argument, resulting in the first value of the memo last being the first value of arr and the first value of n being the second element of arr (if arr has two or more elements).

Comments

0

Just in case you need to find more non consecutive elements, try Enumerable#slice_when:

ary = [1, 2, 3, 4, 6, 7, 8, 10, 11, 14, 15, 19, 20, 21, 100, 101, 102, 101, 102, 30, 31, 32, 33, 40]

ary.slice_when { |a, b| b != a + 1 }.map(&:first)[1..]
#=> [6, 10, 14, 19, 100, 101, 30, 40]

The slice_when part is returning an enumerator generator, which converted to an array looks like:

ary.slice_when { |a, b| b != a + 1 }.to_a
#=> [[1, 2, 3, 4], [6, 7, 8], [10, 11], [14, 15], [19, 20, 21], [100, 101, 102], [101, 102], [30, 31, 32, 33], [40]]

Depending on the desired result, you can change the statement:
ary.slice_when { |a, b| (b != a + 1) && (b != a - 1) }.map(&:first)[1..]
#=> [6, 10, 14, 19, 100, 30, 40]

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.