3

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?

1
  • It's a good question because it's important for you and others to understand why you cannot do what you'd like to to do. Commented Jun 30, 2015 at 1:27

3 Answers 3

3

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.

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

2 Comments

I'm leaving the selected answer as is because it did very directly answer the question. Upvote on this, however, because it's a very complete discussion of the subject. Incidentally, it helped me to solve the broader issue I was working on. Thank you!!
Upvote from me too, this is detail I was not aware of.
2

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

9 Comments

This is working for everything except for closing periods ('.'). So, for example, the sentence "I'm going to do this." comes out as "i'm ggino ot do .hist" Maybe this is outside of the scope of the original question, but do you know why?
@steenslag You're not supposed to use next in these sorts of functions. You're expected to return -1, 0 or 1 only.
The value = part is completely useless here. In fact the whole thing could be collapsed to: y < 'a' ? 0 : x <=> y
There is no reason to set value to the result of the <=> comparison, unless you want only the result of the very last loop, which won't tell you much that is useful. You'd either want all comparisons, or you shouldn't worry about it.
Since you have not defined value prior to sort's block, value's scope is confined to the block. If you reference value outside the block an "undefined local variable or method" exception will be raised.
|
1

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

5 Comments

This example is the default case of sort!, so you could really just say letters.sort! and have the same results
It looks like setting the value to 1 leads to punctuation characters being moved to before the text. What's your reasoning in using 1 as opposed to 0?
No reasoning at all, simply stating that the value returned should be 1, 0 or -1, depending on how you want to sort this :)
ahh, I see. For practical purposes 0 is what I was looking for, but good thinking on making it widely useful.
['z', '-', '-', 'a'].sort { |x,y| p [x,y]; (y < 'a') ? 0 : x<=>y } #=> ["z", "-", "-", "a"] after printing ["z", "-"], ["-", "a"] and ["-", "-"].

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.