2015-06-29 3 views
3

Я получаю сообщение об ошибке при попытке использовать оператор космического корабля с не альфа-цифровыми символами в функции сортировки.Ruby - оператор Spaceship не работает в блоке для .sort

word = "out-classed" 
letters = word.downcase.split('') 
letters.sort! do |x, y| 
    if y < 'a' 
    next 
    else 
    value = x <=> y 
    end 
end 

Я получаю ArgumentError: comparison of String with String failed, и я почти уверен, что это происходит с оператором космического корабля, а не сравнение <.

Интересная часть состоит в том, что, когда я делаю такое же сравнение в irb вне контекста блока сортировки, сравнение работает. Он также работает, когда переменная слова состоит только из букв.

Может кто-нибудь помочь мне понять, почему это не работает в этом конкретном контексте?

+0

Это хороший вопрос, потому что важно, чтобы вы и другие понимали, почему вы не можете делать то, что вы хотели бы сделать. –

ответ

2

Вместо next вам нужно возвратить 0, 1 или -1. Попробуйте это:

word = "out-classed" 
letters = word.downcase.split('') 
letters.sort! do |x, y| 
    if y < 'a' 
    0 
    else 
    value = x <=> y 
    end 
end 
+0

Это работает на все, кроме периодов закрытия ('.'). Так, например, предложение «Я собираюсь сделать это». выходит как «я ggino ot do .hist» Возможно, это выходит за рамки оригинального вопроса, но знаете ли вы, почему? –

+0

Здесь не полезно, но на всякий случай: 'next 0' также будет работать. – steenslag

+0

Редактировать: nvm, это выходит за рамки исходного вопроса. –

1

Ваша проблема заключается здесь

if y < 'a' 
    next 
else 
    ... 

sort метод ожидает, что вы возвращать значение сравнения между каждой парой, так что, когда вы звоните next без возвращения ничего, он говорит, что сравнение не удалось.

Попробуйте, например. это:

if y < 'a' 
    1 
else 
    value = x <=> y 
end 
+0

Этот пример - случай сортировки по умолчанию !, так что вы действительно можете просто сказать 'letters.sort!' И иметь те же результаты –

+1

Похоже, что установка значения 1 приводит к перемещению символов пунктуации перед текстом. Каковы ваши рассуждения в использовании 1, а не 0? –

+0

Нет никаких оснований, просто заявив, что возвращаемое значение должно быть '1',' 0' или '-1', в зависимости от того, как вы хотите отсортировать это :) –

3

При попытке отсортировать коллекцию, x<=>y должна возвращать 0, 1 или -1 для каждой пары элементов коллекции. Если <=> определен искусственно для некоторых пар (например, 'a'<=>'-' #=> 0 и '-'<=>'a' #=> 0), ваш вид может возвращать ошибочные результаты.

Это потому, что алгоритмы сортировки не обязательно оценивают все пары элементов в коллекции. Если, например, он считает, что:

'a' <=> 'b' #=> 0 

и

'b' <=> 'c' #=> 0 

будет заключить, что:

`a` <=> `c` #=> 0 

потому что коллекция сортируется должны удовлетворять транзитивность: x <== z если x <= y и y <= z.

Например, если набор является массив ['z', '-', 'a'] и он находит, что 'z' <= '-' и '-' <= 'a', он будет заключить, что 'z' <= 'a' (а не оценивать 'z' <=> 'a').

Вот почему:

['z', '-', 'a'].sort { |x,y| p [x,y]; (y < 'a') ? 0 : x<=>y } 
    #-> ["z", "-"] 
    #-> ["-", "a"] 
    #=> ["z", "-", "a"] 

не работает.У вас есть два варианта:

Удалите элементы обижая перед сортировкой:

['z', '-', 'a'].select { |c| ('a'..'z').cover?(c) }. 
       sort { |x,y| (y < 'a') ? 0 : x<=>y } 
    #=> ["a", "z"] 

или отсортировать все элементы коллекции:

['z', '-', 'a'].sort 
    #=> ["-", "a", "z"] 

Если коллекция содержит несопоставимые элементы (например, [1,2,'cat']), вам остается только удалить элементы из массива, пока все остальные элементы не будут сопоставимы.

+0

Я оставляю выбранный ответ как есть, потому что он очень непосредственно ответил на вопрос. Однако воздержитесь от этого, потому что это очень полное обсуждение темы. Кстати, это помогло мне решить более широкий вопрос, над которым я работал. Спасибо!! –

+0

Продвиньтесь от меня тоже, это подробности, о которых я не знал. – Kris