title: Python: find out whether a list of integers is coherent
body: <p>I am trying to find out whether a list of integers is coherent or 'at one stretch', meaning that the difference between two neighboring elements must be exactly one and that the numbers must be increasing monotonically. I <a href="https://stackoverflow.com/a/3149493/145400">found</a> a neat approach where we can group by the number in the list minus the position of the element in the list -- this difference changes when the numbers are not coherent. Obviously, there should be exactly one group when the sequence does not contain gaps or repetitions.</p>
  
  <p>Test:</p>
  
  <pre><code>&gt;&gt;&gt; l1 = [1, 2, 3, 4, 5, 6]
  &gt;&gt;&gt; l2 = [1, 2, 3, 4, 5, 7]
  &gt;&gt;&gt; l3 = [1, 2, 3, 4, 5, 5]
  &gt;&gt;&gt; l4 = [1, 2, 3, 4, 5, 4]
  &gt;&gt;&gt; l5 = [6, 5, 4, 3, 2, 1]
  &gt;&gt;&gt; def is_coherent(seq):
  ...     return len(list(g for _, g in itertools.groupby(enumerate(seq), lambda (i,e): i-e))) == 1
  ... 
  &gt;&gt;&gt; is_coherent(l1)
  True
  &gt;&gt;&gt; is_coherent(l2)
  False
  &gt;&gt;&gt; is_coherent(l3)
  False
  &gt;&gt;&gt; is_coherent(l4)
  False
  &gt;&gt;&gt; is_coherent(l5)
  False
  </code></pre>
  
  <p>It works well, but I personally find that this solution is a bit too convoluted in view of the simplicity of the problem. Can you come up with a clearer way to achieve the same without significantly increasing the code length?</p>
  
  <h1>Edit: summary of answers</h1>
  
  <p>From the answers given below, the solution</p>
  
  <pre><code>def is_coherent(seq):
      return seq == range(seq[0], seq[-1]+1)
  </code></pre>
  
  <p>clearly wins. For small lists (10^3 elements), it is on the order of 10 times faster than the <code>groupby</code> approach and (on my machine) still four times faster than the next best approach (using <code>izip_longest</code>). It has the worst scaling behavior, but even for a large list with 10^8 elements it is still two times faster than the next best approach, which again is the <code>izip_longest</code>-based solution.</p>
  
  <p>Relevant timing information obtained with <code>timeit</code>:</p>
  
  <pre><code>Testing is_coherent_groupby...
     small/large/larger/verylarge duration: 8.27 s, 20.23 s, 20.22 s, 20.76 s
     largest/smallest = 2.51
  Testing is_coherent_npdiff...
     small/large/larger/verylarge duration: 7.05 s, 15.81 s, 16.16 s, 15.94 s
     largest/smallest = 2.26
  Testing is_coherent_zip...
     small/large/larger/verylarge duration: 5.74 s, 20.54 s, 21.69 s, 24.62 s
     largest/smallest = 4.28
  Testing is_coherent_izip_longest...
     small/large/larger/verylarge duration: 4.20 s, 10.81 s, 10.76 s, 10.81 s
     largest/smallest = 2.58
  Testing is_coherent_all_xrange...
     small/large/larger/verylarge duration: 6.52 s, 17.06 s, 17.44 s, 17.30 s
     largest/smallest = 2.65
  Testing is_coherent_range...
     small/large/larger/verylarge duration: 0.96 s, 4.14 s, 4.48 s, 4.48 s
     largest/smallest = 4.66
  </code></pre>
  
  <p>Testing code:</p>
  
  <pre><code>import itertools
  import numpy as np
  import timeit
  
  
  setup = """
  import numpy as np
  def is_coherent_groupby(seq):
      return len(list(g for _, g in itertools.groupby(enumerate(seq), lambda (i,e): i-e))) == 1
  
  def is_coherent_npdiff(x):
      return all(np.diff(x) == 1)
  
  def is_coherent_zip(seq):
      return all(x==y+1 for x, y in zip(seq[1:], seq))
  
  def is_coherent_izip_longest(l):
      return all(a==b for a, b in itertools.izip_longest(l, xrange(l[0], l[-1]+1)))
  
  def is_coherent_all_xrange(l):
      return all(l[i] + 1 == l[i+1] for i in xrange(len(l)-1))
  
  def is_coherent_range(seq):
      return seq == range(seq[0], seq[-1]+1)
  
  
  small_list = range(10**3)
  large_list = range(10**6)
  larger_list = range(10**7)
  very_large_list = range(10**8)
  """
  
  
  fs = [
      'is_coherent_groupby',
      'is_coherent_npdiff',
      'is_coherent_zip',
      'is_coherent_izip_longest',
      'is_coherent_all_xrange',
      'is_coherent_range'
      ]
  
  
  for n in fs:
      print "Testing %s..." % n
      t1 = timeit.timeit(
          '%s(small_list)' % n, 
          setup,
          number=40000
          )      
      t2 = timeit.timeit(
          '%s(large_list)' % n, 
          setup,
          number=100
          )     
      t3 = timeit.timeit(
          '%s(larger_list)' % n, 
          setup,
          number=10
          )
      t4 =  timeit.timeit(
          '%s(very_large_list)' % n, 
          setup,
          number=1
          )
      print "   small/large/larger/verylarge duration: %.2f s, %.2f s, %.2f s, %.2f s" % (t1, t2, t3, t4)
      print "   largest/smallest = %.2f" % (t4/t1)
  </code></pre>
  
  <p>Test machine:</p>
  
  <ul>
  <li>Linux 3.2.0 (Ubuntu 12.04)</li>
  <li>Python 2.7.3 (gcc 4.1.2)</li>
  <li>numpy 1.6.2 built with Intel compiler</li>
  <li>CPU: E5-2650 @ 2.00GHz</li>
  <li>24 GB of memory</li>
  </ul>