Python: find out whether a list of integers is coherent
python, python-itertools, sequence
Solution
how bout
sorted_list = sorted(my_list)
return sorted_list == range(sorted_list[0],sorted_list[-1]+1)
or if its only coherent if it is already sorted
return my_list == range(my_list[0],my_list[-1]+1)
if you are using python 3 you will need `list(range(...))`
Problem
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 found 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. Test: ``` >>> l1 = [1, 2, 3, 4, 5, 6] >>> l2 = [1, 2, 3, 4, 5, 7] >>> l3 = [1, 2, 3, 4, 5, 5] >>> l4 = [1, 2, 3, 4, 5, 4] >>> l5 = [6, 5, 4, 3, 2, 1] >>> def is_coherent(seq): ... return len(list(g for _, g in itertools.groupby(enumerate(seq), lambda (i,e): i-e))) == 1 ... >>> is_coherent(l1) True >>> is_coherent(l2) False >>> is_coherent(l3) False >>> is_coherent(l4) False >>> is_coherent(l5) False ``` 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? Edit: summary of answers From the answers given below, the solution ``` def is_coherent(seq): return seq == range(seq[0], seq[-1]+1) ``` clearly wins. For small lists (10^3 elements), it is on the order of 10 times faster than the `groupby` approach and (on my machine) still four times faster than the next best approach (using `izip_longest`). 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 `izip_longest`-based solution. Relevant timing information obtained with `timeit`: ``` 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 ``` Testing 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) ``` Test machine: - Linux 3.2.0 (Ubuntu 12.04) - Python 2.7.3 (gcc 4.1.2) - numpy 1.6.2 built with Intel compiler - CPU: E5-2650 @ 2.00GHz - 24 GB of memory