arraysrubyrange

How to compress arrays of integers into an array of ranges and integers in Ruby


I have a sorted array of integers, and want all subsets of consecutive integers to be replaced with a range, so that:

[1,2,3,4,5,6,7,8,9,10,42]

is replaced with:

[1..10,42]

It's a list of rows from a Google spreadsheet that the user did not enter properly, and that could not be imported into another system's database. There are a lot of rows, and often dozens in sequence. It's much easier for a user to see that rows 3201-3379 are all bad, rather than having to read every single row number individually.

I'm not asking for "Array of integers into array of ranges".

I know how to write the code, and I'm happy to do so, but not if someone has already done it. My current code is not elegant and I'm not going to bother refactoring it if there's something well tested and optimized already out there.


Solution

  • I expect this question has been asked at SO, but the answer in not in my Rolodesk. It's easy enough to do, however.

    arr = [1, 2, 3, 5, 6, 7, 8, 10, 12, 13, 14, 17, 18, 20]
    
    arr.slice_when { |x,y| y > x+1 }.
        map { |a| a.size == 1 ? a.first : a.first..a.last }
      #=> [1..3, 5..8, 10, 12..14, 17..18, 20]
    

    See Enumerable#slice_when.

    We first compute the enumerator

    enum = arr.slice_when { |x,y| y > x+1 }
      #=> #<Enumerator: #<Enumerator::Generator:0x...0000...fe0>:each>
    

    By converting enum to an array we can see the elements that will be generated by the enumerator and fed to map:

    enum.to_a
      #=> [[1, 2, 3], [5, 6, 7, 8], [10], [12, 13, 14], [17, 18], [20]]