tags:

views:

760

answers:

5

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

+8  A: 

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]
Peter
Right on, thanks for the answer,
Sam
I wish I went to bed. Actually, the array is only strings, but the strings may start with a "number" of a letter
Sam
Awesome use of `partition...flatten` combo.
Adrian
+2  A: 

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']
Myrddin Emrys
A: 

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!

Paul
+1  A: 

A general trick for solving tricky sorts is to have sort_by return an array with 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"]
Wayne Conrad
A: 

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]