Let's rewrite your method as follows.
def alpha(str)
sorted_chars_by_case = str.chars.sort
puts "sorted_chars = #{sorted_chars}"
sorted_chars_by_downcased = sorted_chars_by_case.sort_by(&:downcase)
puts "sorted_chars_by_downcased = #{sorted_chars_by_downcased}"
sorted_chars_by_downcased.join
end
Then:
alpha("AaaaaZazzz")
sorted_chars_by_case = ["A", "Z", "a", "a", "a", "a", "a", "z", "z", "z"]
sorted_chars_indifferent = ["A", "a", "a", "a", "a", "a", "Z", "z", "z", "z"]
#=> "AaaaaaZzzz"
As you see, the first step, after converting the string to an array of characters, is to form an array whose first elements are upper-case letters, in order, followed by lower-case letters, also ordered.1 The second step is sort sorted_chars_by_case without reference to case. That array is then joined to return the desired string, "AaaaaaZzzz".
While this gives the desired result, it is only happenstance that it does. A different sorting method could well have returned, say, "aaaaAazZzz", because "A" is treated the same as "a" in the second sort.
What you want is a two-level sort; sort by characters, case-indifferent, then when there are ties ("A" and "a", for example), sort the upper-case letter first. You can do that by sorting two-element arrays.
def alpha(str)
str.each_char.sort_by { |ele| [ele.downcase, ele] }.join
end
Then
alpha("AaaaaZazzz")
#=> "AaaaaaZzzz"
When sorting arrays the method Array#<=> is used to order two arrays. Note in particular the third paragraph of that doc.
If "A" and "z" are being ordered, for example, Ruby compares the arrays
a1 = ["a", "A"]
a2 = ["z", "z"]
As a1.first < a2.first #=> true, we see that a1 <=> a2 #=> -1, so "A" precedes "z" in the sort. Here a1.last and a2.last are not examined.
Now suppose "z" and "Z" are being ordered. Ruby compares the arrays
a1 = ["z", "z"]
a2 = ["z", "Z"]
As a1.first equals a2.first, a1.last and a2.last are compared to break the tie. Since "z" > "Z" #=> true, a1 <=> a2 #=> 1, so "Z" precedes "z" in the sort.
Note that I replace str.chars with str.each_char. It's generally a small thing, but String#chars returns an array of characters, whereas String#each_char returns an enumerator, and therefore is more space-efficient.
Sometimes you need to return an array, and therefore you must use chars. An example is str.chars.cycle, where you are chaining to the Array method cycle. On the other hand, if you are chaining to an enumerator (an instance of the class Enumerator), you must use each_char, an example being str.each_char.with_object([]) ....
Often, however, you have a choice: str.chars.sort, using Array#sort, or str.each_char.sort, using Enumerable#sort. In those situations each_char is preferred because of the reduced memory requirement. The rule, therefore, is to use chars when you are chaining to an Array method, otherwise use each_char.
1. sort_by(&:downcase) can be thought of as shorthand for sort_by { |ele| ele.downcase }.
.joinsomewhere (either inalpha, or in the call toalpha), because "alpha" returns an array of characters, however the comparison is between two strings. This is just a minor bug in your question, but isn't the answer to your question..sort?