Reputation: 1890
I'm getting an error when attempting to use the spaceship operator with non alpha-numeric characters in the sort function.
word = "out-classed"
letters = word.downcase.split('')
letters.sort! do |x, y|
if y < 'a'
next
else
value = x <=> y
end
end
I'm getting ArgumentError: comparison of String with String failed
, and I'm almost positive this happens with the spaceship operator and not the < comparison.
The interesting part is that when I do this same comparison in irb outside of the context of a sort block, the comparison works. It also works when the word variable only consists of letters.
Can anybody help me to understand why this doesn't work in this specific context alone?
Upvotes: 3
Views: 282
Reputation: 110695
If you attempt to sort a collection, x<=>y
must return 0, 1 or -1 for every pair of elements of the collection. If <=>
is defined artificially for some pairs (e.g., 'a'<=>'-' #=> 0
and '-'<=>'a' #=> 0
), your sort may return erroneous results.
This is because sort algorithms do not necessarily evaluate all pairs of elements in the collection. If, for example, it finds that:
'a' <=> 'b' #=> 0
and
'b' <=> 'c' #=> 0
it will conclude that:
`a` <=> `c` #=> 0
because the collection being sorted must satisfy transitivity: x <== z
if x <= y
and y <= z
.
For example, if the collection is the array ['z', '-', 'a']
and it finds that 'z' <= '-'
and '-' <= 'a'
, it will conclude that 'z' <= 'a'
(and not evaluate 'z' <=> 'a'
).
That's why:
['z', '-', 'a'].sort { |x,y| p [x,y]; (y < 'a') ? 0 : x<=>y }
#-> ["z", "-"]
#-> ["-", "a"]
#=> ["z", "-", "a"]
doesn't work. You have two choices:
Remove the offending elements before sorting:
['z', '-', 'a'].select { |c| ('a'..'z').cover?(c) }.
sort { |x,y| (y < 'a') ? 0 : x<=>y }
#=> ["a", "z"]
or sort all elements of the collection:
['z', '-', 'a'].sort
#=> ["-", "a", "z"]
If the collection contains non-comparable elements (e.g., [1,2,'cat']
), you only choice is to remove elements from the array until all remaining elements are comparable.
Upvotes: 3
Reputation: 19948
Instead of next
you need to return 0, 1 or -1. Try this:
word = "out-classed"
letters = word.downcase.split('')
letters.sort! do |x, y|
if y < 'a'
0
else
value = x <=> y
end
end
Upvotes: 2
Reputation: 2390
Your problem lies here
if y < 'a'
next
else
...
sort
method expects you to return a comparison value between every pair, so when you call next
without returning anything, it says that comparison failed.
Try e.g. this:
if y < 'a'
1
else
value = x <=> y
end
Upvotes: 1