7

I want to sort an array by strings first and then numbers. How do I do this?

3
  • 2
    Your question isn't really specific enough. What are you trying to sort? Strings, or characters? And do you mean that you always want letters to sort lower than numbers? What if a string has mixed letters and numbers? Or do you want lexicographic sort in which letters are sorted before numbers? Commented Dec 23, 2009 at 22:30
  • 1
    Could you please clarify your question? Maybe give an example of the sort order you want? Commented Dec 23, 2009 at 22:32
  • objects. for example: some_array = ["object.object_price_range", "object.object_price_range" ] the attribute :object_price_range is a string which can be numbers or letters, but for this example say object1 is "1-100" and object2 is "All Prices" Commented Dec 23, 2009 at 23:12

6 Answers 6

17

A general trick for solving tricky sorts is to use #sort_by, with the block returning an array having the primary and secondary sort order (and, if you need it, tertiary, etc.)

a = ['foo', 'bar', '1', '2', '10']  
b = a.sort_by do |s|
  if s =~ /^\d+$/
    [2, $&.to_i]
  else
    [1, s]
  end
end
p b    # => ["bar", "foo", "1", "2", "10"]

This works because of the way array comparison is defined by Ruby. The comparison is defined by the Array#<=> method:

Arrays are compared in an “element-wise” manner; the first element of ary is compared with the first one of other_ary using the <=> operator, then each of the second elements, etc… As soon as the result of any such comparison is non zero (i.e. the two corresponding elements are not equal), that result is returned for the whole array comparison.

Sign up to request clarification or add additional context in comments.

1 Comment

How to handle when there is a Float objects ? It is breaking, when the collection contains Floats.. I'm basically using your code, but it is breaking for Float objects..
17

Sort an array of mixed numbers and strings by putting the numbers first, and in order, followed by the strings second, and in order.

>> a = [1, 2, "b", "a"]

>> a.partition{|x| x.is_a? String}.map(&:sort).flatten
=> ["a", "b", 1, 2]

2 Comments

I wish I went to bed. Actually, the array is only strings, but the strings may start with a "number" of a letter
Awesome use of partition...flatten combo.
8
a = ['1', '10', '100', '2', '42', 'hello', 'x1', 'x20', 'x100', '42x', '42y', '10.1.2', '10.10.2', '10.8.2']
a.map {|i| i.gsub(/\d+/) {|s| "%08d" % s.to_i } }.zip(a).sort.map{|x,y| y}
# => ["1", "2", "10", "10.1.2", "10.8.2", "10.10.2", "42", "42x", "42y", "100", "hello", "x1", "x20", "x100"] 

3 Comments

a.sort_by{|i|i.gsub(/\d+/) {|s| "%08d" % s.to_i }}
@bluexuemei ah that is pretty neat. Cool
This can be simplified with sort_by: a.sort_by { _1.gsub(/\d+/) { |s| format('%08d', s.to_i) } }
2

Normally, alphabetization is done with numbers first. If you want to alphabetize something where letters are alphabetized before numbers, you will need to alter the compare function used.

# I realize this function could be done with less if-then-else logic,
# but I thought this would be clearer for teaching purposes.
def String.mysort(other)
  length = (self.length < other.length) ? self.length : other.length
  0.upto(length-1) do |i|
    # normally we would just return the result of self[i] <=> other[i]. But
    # you need a custom sorting function.
    if self[i] == other[i]
      continue # characters the same, skip to next character.
    else
      if self[i] ~= /[0-9]/
        if other[i] ~= /[0-9]/
          return self[i] <=> other[i]  # both numeric, sort normally.
        else
          return 1  # self is numeric, other is not, so self is sorted after.
        end
      elsif other[i] ~= /[0-9]/
        return  -1  # self is not numeric, other is, so self is sorted before.
      else
        return self[i] <=> other[i]    # both non-numeric, sort normally.
      end
    end
  end

  # if we got this far, the segments were identical. However, they may
  # not be the same length. Short sorted before long.
  return self.length <=> other.length
end

['0','b','1','a'].sort{|x,y| x.mysort(y) } # => ['a', 'b', '0', '1']

Comments

1

Here is a somewhat verbose answers. Divide the array into two sub arrays: strings and numbers, sort them and concat them.

array = [1, 'b', 'a', 'c', 'd', 2, 4, 3]
strings = []
numbers = []
array.each do |element|
  if element.is_a? String
    strings << element
  else
    numbers << element
  end
end
sorted_array = strings.sort + numbers.sort
sorted_array # ['a', 'b', 'c', 'd', 1, 2, 3, 4]

Comments

0

If you are trying to sort Mixed case and numbers, only a few people on earth can do it outside of proprietary applications. It is a secret with a sucker punch. You must use a qsort which makes sorting easy until you mix cases (upper and lower case letters). Then college, books and internet leave you hanging. This hack worth its weight in gold and is the brass ring of programming for all reasons.

To sort numbers with words you must convert numbers into string. You must presort using upper case. If you have the words "Ant", "ant" and "anT" on the less they should all point to the word "ANT" to the upper case sort list. You will then create a list (array) of just these three words ["Ant", "ant" and "anT"] and use qsort as a tie breaker to sort them.

You then insert them into a final sorting array. It is fairly difficult by design. "A" is 65 on ascii and 'a' is 97 with lots of garbage characters between 'Z' and 'a'! It is no accident! It a conspiracy I tell you!

You could create a sorting table the more sanely groups the characters like :

A, a, B, b, C, c, D, d, E, e, F, f, G, g, H, h, I, i, J, j, K, k, L, l, M, m, N, n, ... 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 ...

building the table around this block starting with " "(space) ascii 32 upto 128. you will probably want to reorder the numbers in sequence the A 65 is for example only.

This makes it easier but will likely cause a performance hit being outside the macros of most programming languages. Good luck!

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.