2015-04-24 7 views
0

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

array = ["{109}{08} OK", 
     "{98} Thx", 
     "{108}{0.8}{908} aa", 
     "{8}{51} lorem ipsum"] 

Я хотел бы отсортировать этот процесс сканирование массива «данные внутри» : здесь целые числа в фигурной скобке. Итак, окончательный массив должен быть таким:

array.custom_sort! => ["{8}{51} lorem ipsum", 
         "{98} Thx", 
         "{108}{0.8}{908} aa", 
         "{109}{08} OK"] 

Есть ли хорошее решение для этого в Ruby? Или я должен воссоздать новый массив, который вставляет каждый анализируемый элемент?

EDIT:

я не упомянул о СНП приоритеты: Во-первых, сортировка на основе числа в фигурных скобках, до 3-х групп, но не может отсутствовать.

["{5}something", 
"{61}{64}could", 
"{}be",     #raise an error or ignore it 
"{54}{31.24}{0.2}write", 
"{11}{21}{87}{65}here", #raise an error or ignore it 
"[]or",     #raise an error or ignore it 
"{31}not"] 

Если первые числа равны, то следует сравнить другие. Некоторые примеры:

"{15}" < "{151}" < "{151}{32}" < "{152}" 
"{1}" < "{012}" < "{12}{-1}{0}" < "{12.0}{0.2}" 
"{5}" < "{5}{0}" < "{5}{0}{1}" 

Но если каждые числа равны, то строка сравнивается. Единственный символ, который создает проблему, - это пространство, которое должно быть после каждого другого «видимого» символа. Примеры:

"{1}a" < "{1}aa" < "{1} a" < "{1} a" 
"{1}" < "{1}a " < "{1}a " < "{1}a a" 
"{1}a" < "{1}ba" < "{1}b " 

Я могу сделать это делать somethign, как это в пользовательском классе:

class CustomArray 
    attr_accessor :one 
    attr_accessor :two 
    attr_accessor :three 
    attr_accessor :text 

    def <=>(other) 
    if self.one.to_f < other.one.to_f 
     return -1 
    elsif self.one.to_f > other.one.to_f 
     return 1 
    elsif self.two.nil? 
     if other.two.nil? 
     min = [self.text, other.text].min 
     i = 0 
     until i == min 
      if self.text[i].chr == ' ' #.chr is for compatibility with Ruby 1.8.x 
      if other.text[i].chr != ' ' 
       return 1 
      end 
      else 
      if other.text[i].chr == ' ' 
       return -1 

      #... 

    self.text <=> other.text 
    end 
end 

Он отлично работает, но я очень разочарован кодирования в Рубине, как я кода в проекте C++. Вот почему я хотел бы знать, как использовать «настраиваемый сортировку в методе foreach» с более сложным способом сортировки (требуется разбор, сканирование, регулярное выражение), чем наивный, основанный на атрибуте содержимого.

ответ

1

[Edit: Моего первым решением, которое следует за этим редактирование, не работает с пересмотренным заявлением о вопрос. Однако я оставлю его, поскольку это может быть интересно независимо.

Ниже приведен способ выполнения сортировки по пересмотренным правилам, как я их понимаю. Если я неверно истолковал правила, я ожидаю, что исправление будет незначительным.

Regex использовать

Давайте начнем с регулярного выражения я буду использовать:

R =/
    \{  # match char 
    (  # begin capture group 
    \d+  # match one or more digits 
    (?:  # begin non-capture group 
    \.  # match decimal 
    \d+  # match one or more digits 
    )  # end non-capture group 
    |  # or 
    \d*  # match zero or more digits 
    )  # match end capture group 
    \}  # match char 
    /x 

Примеры:

a = ["{5}something", "{61}{64}could", "{}be", "{54}{31.24}{0.2}write", 
    "{11}{21}{87}{65}here", "[]or", "{31}not", "{31} cat"] 
a.each_with_object({}) { |s,h| h[s] = s.scan(R).flatten } 
    # => {"{5}something"  =>["5"], 
    # "{61}{64}could"  =>["61", "64"], 
    # "{}be"     =>[""], 
    # "{54}{31.24}{0.2}write"=>["54", "31.24", "0.2"], 
    # "{11}{21}{87}{65}here" =>["11", "21", "87", "65"], 
    # "[]or"     =>[], 
    # "{31}not"    =>["31"] 
    # "{31} cat"    =>["31"]} 

метод custom_sort

Мы можем написать метод custom_sort следующим образом (изменить sort_by к sort_by! для custom_sort!):

class Array 
    def custom_sort 
    sort_by do |s| 
     a = s.scan(R).flatten 
     raise SyntaxError, 
     "'#{s}' contains empty braces" if a.any?(&:empty?) 
     raise SyntaxError, 
     "'#{s}' contains zero or > 3 pair of braces" if a.size.zero?||a.size > 3 
     a.map(&:to_f) << s[a.join.size+2*a.size..-1].tr(' ', 255.chr) 
    end 
    end 
end 

Примеры

Давайте попробуем:

a.custom_sort 
    #=> SyntaxError: '{}be' contains empty braces 

Удалить "{}be" из a:

a = ["{5}something", "{61}{64}could", "{54}{31.24}{0.2}write", 
    "{11}{21}{87}{65}here", "[]or", "{31}not", "{31} cat"] 
a.custom_sort 
    #SyntaxError: '{11}{21}{87}{65}here' contains > 3 pair of braces 

Удалить "{11}{21}{87}{65}here":

a = ["{5}something", "{61}{64}could", "{54}{31.24}{0.2}write", 
    "[]or", "{31}not", "{31} cat"] 
a.custom_sort 
    #=> SyntaxError: '[]or' contains zero or > 3 pair of braces 

Удалить "[]or":

a = ["{5}something", "{61}{64}could", "{54}{31.24}{0.2}write", 
    "{31}not", "{31} cat"] 
a.custom_sort 
    #=> ["{5}something", 
    # "{31}not", 
    # "{31} cat", 
    # "{54}{31.24}{0.2}write", "{61}{64}could"] 

Объяснение

Пусть одна из строк, подлежащих сортировке был:

s = "{54}{31.24}{0.2}write a letter" 

Тогда в sort_by блоке, мы бы вычислить:

a = s.scan(R).flatten 
    #=> ["54", "31.24", "0.2"] 
raise SyntaxError, "..." if a.any?(&:empty?) 
    #=> raise SyntaxError, "..." if false 
raise SyntaxError, "..." if a.size.zero?||a.size > 3 
    #=> SyntaxError, "..." if false || false 
b = a.map(&:to_f) 
    #=> [54.0, 31.24, 0.2] 
t = a.join 
    #=> "5431.240.2" 
n = t.size + 2*a.size 
    #=> 16 
u = s[n..-1] 
    #=> "wr i te" 
v = u.tr(' ', 255.chr) 
    #=> "wr\xFFi\xFFte" 
b << v 
    #=> [54.0, 31.24, 0.2, "wr\xFFi\xFFte"] 

Обратите внимание, что использование String#tr (или вы можете использовать String#gsub) ставит пробелы в конце порядок сортировки ASCII символов:

255.times.all? { |i| i.chr < 255.chr } 
    #=> true 

Tide]

Я предположил, что, в сортировке, пары строк должны быть сравнены в манере analo до Array#<=>. Первое сравнение рассматривает строки цифр в первой паре фигурных скобок в каждой строке (после преобразования в поплавок). Связи разбиваются, сравнивая строки цифр во вторых парах фигурных скобок (преобразуются в поплавки). Если есть еще галстук, то сравниваются третьи пары, заключенные в фигурные скобки, и т. Д. Если одна строка содержит n пар фигурных скобок, а другая имеет пары m > n, а значения в фигурных скобках одинаковы для первых пар n, я предполагаю первая строка должна предшествовать второй в сортировке.

код

R =/
    \{ # match char 
    (\d+) # capture digits 
    \} # match char 
    +  # capture one or more times 
    /x 

class Array 
    def custom_sort! 
    sort_by! { |s| s.scan(R).map { |e| e.first.to_f } } 
    end 
end 

Пример

array = ["{109}{08} OK", 
     "{109}{07} OK", 
     "{98} Thx", 
     "{108}{0.8}{908} aa", 
     "{108}{0.8}{907} aa", 
     "{8}{51} lorem ipsum"] 

a = array.custom_sort! 
    #=> ["{8}{51} lorem ipsum", 
    # "{98} Thx", 
    # "{108}{0.8}{907} aa", 
    # "{108}{0.8}{908} aa", 
    # "{109}{07} OK", 
    # "{109}{08} OK"] 

array == a 
    #=> true 

Объяснение

Давайте теперь вычислить значение в блоке Array#sort_by! «s для первого элемента array

s = "{109}{08} OK" 

a = s.scan(R) 
    #=> [["109"], ["08"]] 
b = a.map { |e| e.first.to_f } 
    #=> [109.0, 8.0] 

Давайте теперь делать то же самое для остальных строк и поместить результаты в массив:

c = array.map { |s| [s, s.scan(R).map { |e| e.first.to_f }] } 
    #=> [["{8}{51} lorem ipsum", [8.0, 51.0]], 
    # ["{98} Thx",   [98.0]], 
    # ["{108}{0.8}{907} aa", [108.0, 907.0]], 
    # ["{108}{0.8}{908} aa", [108.0, 908.0]], 
    # ["{109}{07} OK",  [109.0, 7.0]], 
    # ["{109}{08} OK",  [109.0, 8.0]]] 

sort_by в custom_sort! является равнозначно:

c.sort_by(&:last).map(&:first) 
    #=> ["{8}{51} lorem ipsum", 
    # "{98} Thx", 
    # "{108}{0.8}{907} aa", 
    # "{108}{0.8}{908} aa", 
    # "{109}{07} OK", 
    # "{109}{08} OK"] 
+0

Тщательное, как всегда , Хорошая работа. – DiegoSalazar

+0

Это очень полезное объяснение, спасибо большое! Но для сравнения строк я забыл что-то сказать. Скажем: ["{42} foo", "{42} bar", "{42} world", "{42} hello"]. Трюк заключается в сравнении строки, символ пробела 'должен быть * после * alphanum, но в таблице ASCII это не так. Я видел в другом потоке (http://stackoverflow.com/questions/29808574/ruby-custom-string-sort), что я мог бы установить свой собственный «порядок символов». Возможно ли интегрировать эту функцию в элегантное решение? – Rikouchi

+0

Я посмотрю на это позже сегодня. Тем временем я предлагаю вам отредактировать свой вопрос, чтобы добавить информацию в свой комментарий (а также исправить порядок '108' против' 109', если это необходимо). Поскольку дополнительная информация изменяет вопрос, вы должны сделать это ясно в редактировании. Часто это делается путем написания «Правка: я не упоминал ...», возможно, в конце вопроса. –

-2

Вы можете передать Array#sort блок, определяющий порядок его заказа.

1

Это следует сделать это:

array.sort_by do |s| 
    # regex match the digits within the first pair of curly braces 
    s.match(/^\{(\d+)\}/)[1].to_i # convert to an int in order to sort 
end 

# => ["{8}{51} lorem ipsum", "{98} Thx", "{108}{0.8}{908} aa", "{109}{08} OK"] 
1
array.sort_by { |v| (v =~ /(\d+)/) && $1.to_i } 

попеременно

array.sort_by { |v| /(\d+)/.match(v)[1].to_i } 
Смежные вопросы