Search code examples
rubysortingnatural-sort

Is there a way to sort so that "Vitamin B12" is not in front of "Vitamin B6"?


In Ruby on Rails, the default sort order will be

Vitamin A
Vitamin B12
Vitamin B6

Is there a mechanism or quick way so that it will sort by a natural language way so that B6 shows before B12?


Solution

  • Try something like:

    class Array
      def smart_sort
        sort_by{|s| (' '+s).scan(/(\d+)|(\D+)/).map{|d, s| s || d.to_i}}
      end
    end
    
    a = ['Vitamin A', 'Vitamin B12', 'Vitamin B6']
    p a.smart_sort
    
    # => ["Vitamin A", "Vitamin B6", "Vitamin B12"]
    

    It sorts alternatively by digits and by non-digits.

    b = ['3c17d', '1a34be',  '3x1a', '1b01c', '1a34bb']
    p b.smart_sort
    
    # => ["1a34bb", "1a34be", "1b01c", "3c17d", "3x1a"]
    

    This is probably similar to what is done in Windows when sorting the file names within a directory.


    Update: A newer version. Doesn't need ' '+, as it is automatically supplied by split. It also removes the redundancy of specifying the complementary \d and \D in the regex.

    class Array
      def smart_sort
        sort_by{|s| s.split(/(\d+)/).each_slice(2).flat_map{|s, d| [s, d.to_i]}}
      end
    end