Сортировать массив в Ruby, игнорируя артикли (the, a, an)

В моем приложении мне нужно показать список песен. Прямо сейчас я делаю это:

Song.all.sort {|x,y| x.artist.name <=> y.artist.name }

К сожалению, это означает, что «Печально известный БОЛЬШОЙ» будет сортировать по Т, а я хочу, чтобы он сортировал по Н (т. е. я хочу игнорировать артикли — «the», «a» и «an» — для цели сортировки.

Первой мыслью было сделать так:

Song.all.sort {|x,y| x.artist.name.gsub(/^(the|a|an) /i, '') <=> y.artist.name.gsub(/^(the|a|an) /i, '') }

Но, похоже, это не работает. Мысли?


person Tom Lehman    schedule 10.09.2009    source источник
comment
Я поддерживаю добавление дополнительной таблицы-столбца, но если вы хотите сделать это таким образом, вам обязательно нужно использовать .sort_by, а не .sort. Нет причин использовать сортировку, если ваше поведение не должно иметь доступ к обоим элементам в паре, что здесь не так...   -  person glenn mcdonald    schedule 10.09.2009


Ответы (4)


Мой любимый подход к такого рода проблемам состоит в том, чтобы сохранить в базе данных дополнительный столбец sort_order.

Таким образом, когда у вас есть 10000 песен, которые вы хотели бы просмотреть, вы можете сделать это в SQL и избежать необходимости возвращать их все назад.

Просто добавьте фильтр before_save, чтобы синхронизировать этот столбец.

Чистое решение без изменений схемы:

class Artist
  def sortable_name
    self.name.sub(/^(the|a|an)\s+/i, '')
  end
end

class Song
  def sortable_name
    # note the - is there so [Radio] [head on] and [Radiohead] [karma police] 
    #   are not mixed in the ordering
    "#{artist.sortable_name} - #{name}" 
  end
end

# breaks ties as well 
Song.all.sort_by { |song| song.sortable_name }
person Sam Saffron    schedule 10.09.2009
comment
Сохранение дополнительного столбца позволяет вам делать некоторые другие приятные вещи, такие как .downcase, замена ä на ae и т.п. (если применимо), перестановка имен и фамилий и т. д., и все это без пересчета всего во время каждого запроса... - person glenn mcdonald; 10.09.2009
comment
вы также можете быть более кратким с symbol_to_proc, но лично мне этот трюк не нравится ... - person Sam Saffron; 10.09.2009
comment
Выглядит неплохо, хотя что вы имеете в виду под словами «the the»…? - person Tom Lehman; 10.09.2009
comment
Плохой пример: я имею в виду, что если у вас нет какого-то разделителя между исполнителем и названием песни, вы можете перепутать порядок в некоторых странных случаях. - person Sam Saffron; 10.09.2009
comment
yerp Я думаю, что это было бы хорошо - person Sam Saffron; 11.09.2009

Возможно, вам лучше сделать это в SQL,

SELECT Title,
CASE WHEN SUBSTRING_INDEX(Title, ' ', 1)
        IN ('a', 'an', 'the')
    THEN CONCAT(
        SUBSTRING(Title, INSTR(Title, ' ') + 1),
        ', ',
        SUBSTRING_INDEX(Title, ' ', 1)
    )
    ELSE Title
END AS TitleSort
FROM music
ORDER BY TitleSort

Существует статья, описывающая это более подробно.

Большое преимущество по сравнению с изложенным вами подходом заключается в том, что вы сначала извлекаете ВСЕ записи, что тонко испортит вас как с точки зрения производительности, так и с точки зрения пользовательского интерфейса (я думаю о таких ситуациях, как попытка просмотреть большой набор песен).

person Mike Buckbee    schedule 10.09.2009

Ваш ответ кажется правильным, но, вероятно, вы можете изменить его на:

Song.all.sort {|x, y| x.artist.name.sub(/^(the|a|an)\s/i, '') <=> y.artist.name.sub(/^(the|a|an)\s/i, '') }

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

вы можете перейти на http://www.rubyxp.com/, чтобы протестировать регулярное выражение

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

надеюсь поможет =)

person Staelen    schedule 10.09.2009

Разбить его!

class Artist < ActiveRecord::Base

  def <=>(other)
    name.gsub(/^(the|a|an) /i, '') <=> other.name.gsub(/^(the|a|an) /i, '')
  end

end

songs = Song.all(:include => :artist)
songs.sort_by { |s| s.artist }
person hgmnz    schedule 10.09.2009
comment
По какой-то причине форматирование кода поглощает полные строки кода выше. Пожалуйста, смотрите пасти здесь: pastie.org/612206 - person hgmnz; 10.09.2009