Сортировать массив массивов по порядку в другом массиве - PullRequest
0 голосов
/ 13 марта 2019

У меня есть массив массивов:

x = [
  ["ready", 5], ["shipped", 1], ["pending", 1], ["refunded", 1],
  ["delivered", 23], ["scheduled", 1], ["canceled", 51]
]

Мой массив сортировки

order_array = [
  "ready", "in_progress", "recieved", "shipped", "scheduled", "pick_up",
 "delivered", "canceled", "failed", "refunded", "refund_failed"
]

Мне нужно заказать x на основе значения первого элемента в каждом подмассиве,Требуемый отсортированный массив:

[
  ["ready", 5], ["shipped", 1], ["scheduled", 1], ["delivered", 23],
  ["canceled", 51], ["refunded", 1]
]

Использование sort_by не приводит к требуемой сортировке, оно приводит к тому же массиву.

result = x.sort_by {|u| order_array.index(u)}
# => [
#      ["ready", 5], ["shipped", 1], ["pending", 1], ["refunded", 1],
#      ["delivered", 23], ["scheduled", 1], ["canceled", 51]
# ]

Ответы [ 6 ]

5 голосов
/ 13 марта 2019
h = x.to_h
# => {"ready"=>5,
# "shipped"=>1,
# "pending"=>1,
# "refunded"=>1,
# "delivered"=>23,
# "scheduled"=>1,
# "canceled"=>51}

order_array.map{|key| [key, h[key]] if h.key?(key)}.compact
# => [["ready", 5],
# ["shipped", 1],
# ["scheduled", 1],
# ["delivered", 23],
# ["canceled", 51],
# ["refunded", 1]]

или

h = x.to_h{|k, v| [k, [k, v]]}
#=> {"ready"=>["ready", 5],
# "shipped"=>["shipped", 1],
# "pending"=>["pending", 1],
# "refunded"=>["refunded", 1],
# "delivered"=>["delivered", 23],
# "scheduled"=>["scheduled", 1],
# "canceled"=>["canceled", 51]}

order_array.map{|k| h[k]}.compact
#=> [["ready", 5],
# ["shipped", 1],
# ["scheduled", 1],
# ["delivered", 23],
# ["canceled", 51],
# ["refunded", 1]]

или

h = x.to_h{|k, v| [k, [k, v]]}
#=> {"ready"=>["ready", 5],
# "shipped"=>["shipped", 1],
# "pending"=>["pending", 1],
# "refunded"=>["refunded", 1],
# "delivered"=>["delivered", 23],
# "scheduled"=>["scheduled", 1],
# "canceled"=>["canceled", 51]}

h.values_at(*order_array).compact
#=> [["ready", 5],
# ["shipped", 1],
# ["scheduled", 1],
# ["delivered", 23],
# ["canceled", 51],
# ["refunded", 1]]
4 голосов
/ 13 марта 2019

assoc кажется полезным: "Выполняет поиск в массиве, элементы которого также являются массивами, сравнивая obj с первым элементом каждого содержащегося в нем массива, используя obj. ==."

order_array.map{|e| x.assoc(e) }.compact
4 голосов
/ 13 марта 2019

Вы почти у цели: index не работает, когда вы сравниваете весь массив, а не первый его элемент. Это будет работать:

result = x.sort_by { |u| order_array.index(u[0]) || 100 }
#=> [["ready", 5], ["shipped", 1], ["scheduled", 1], ["delivered", 23], ["canceled", 51], ["refunded", 1], ["pending", 1]]

Обратите внимание, что по умолчанию 100 находится сзади сортировки, если значение не найдено в order_array.


Редактировать

Это было первоначально принято, несмотря на то, что ["pending", 1] предполагало, что оно соответствует требованиям; однако, вот решение, позволяющее избежать нежелательной записи, которое также обрабатывает дубликаты в случае необходимости.

order_array.each_with_object([]) { |ordered_by, array| array.push(*x.select { |item| item[0] == ordered_by }) }
#=> [["ready", 5], ["shipped", 1], ["scheduled", 1], ["delivered", 23], ["canceled", 51], ["refunded", 1]]

Или, очень быстро, но с учетом повторяющихся значений для каждого заказанного элемента:

hash = x.each_with_object(Hash.new { |h,k| h[k] = [] }) { |item, h| h[item[0]] << item[1] }
order_array.flat_map { |key| [key, hash[key]] }

Benchmark

Вот пример для этого сценария с большим набором данных: https://repl.it/repls/SentimentalAdequateClick. Похоже, что методы Савы лидируют, хотя мое последнее усилие работает легко, если в будущем появятся повторяющиеся значения. Кроме того, мое второе усилие - отстой (что меня немного удивило):)

2 голосов
/ 13 марта 2019

Я бы предложил

x.keep_if { |e| order_array.include? e[0] }.sort_by { |e| order_array.index(e[0]) }

Так как некоторые значения не являются элементами order_array, например "pending".

#=> [["ready", 5], ["shipped", 1], ["scheduled", 1], ["delivered", 23], ["canceled", 51], ["refunded", 1]]


Бенчмарк ответы до сих пор 500.times:
#        user       system     total       real
# sawa   0.006698   0.000132   0.006830 (  0.006996) # on the first method
# ray    0.005543   0.000123   0.005666 (  0.005770)
# igian  0.001923   0.000003   0.001926 (  0.001927)
# srack  0.005270   0.000168   0.005438 (  0.005540) # on the last method


Просто ради интереса я попытался найти более быстрый метод для Ruby 2.5:
xx = x.to_h # less than Ruby 2.6
order_array.each.with_object([]) { |k, res| res << [k, xx[k]] if xx.has_key? k }
1 голос
/ 13 марта 2019

Вы можете попробовать приведенный ниже код для эффективного поиска,

order_array.map { |p| x.detect { |y| y[0] == p } }.compact
# => [["ready", 5], ["shipped", 1], ["scheduled", 1], ["delivered", 23], ["canceled", 51], ["refunded", 1]]
0 голосов
/ 14 марта 2019

Я предположил:

  • первый элемент каждого элемента x не обязательно уникален;
  • все элементы x, первый элемент которых одинаков, а первый элемент является членом order_array, последовательно появляются в возвращенном (отсортированном) массиве в том порядке, в котором эти элементы отображаются в x;
  • любые элементы x, первый элемент которых не является членом order_array, появляются в возвращенном (отсортированном) массиве после всех элементов, первый элемент которых находится в sorted_array, и все такие элементы появляются в возвращенном массиве ( в конце) в порядке, в котором они встречаются в x; и
  • эффективность имеет первостепенное значение.

x = [
  ["ready", 5], ["shipped", 1], ["pending", 1], ["refunded", 1], ["originated", 3],
  ["delivered", 23], ["scheduled", 1], ["ready", 8], ["canceled", 51]
]

order_array = [
  "ready", "in_progress", "received", "shipped", "scheduled", "pick_up",
  "delivered", "canceled", "failed", "refunded", "refund_failed"
]

order_pos = order_array.each_with_object({}) { |word,h| h[word] = [] }
  #=> {"ready"=>[], "in_progress"=>[], "received"=>[], "shipped"=>[],
  #    "scheduled"=>[], "pick_up"=>[], "delivered"=>[], "canceled"=>[],
  #    "failed"=>[], "refunded"=>[], "refund_failed"=>[]} 
back = x.each_with_index.with_object([]) { |((word,v),i),back|
  order_pos.key?(word) ? (order_pos[word] << i) : back << [word,v] }
  #=> [["pending", 1], ["originated", 3]] 
order_pos.flat_map { |word,offsets| offsets.map { |i| x[i] } }.concat(back)
  #=> [["ready", 5], ["ready", 8], ["shipped", 1], ["scheduled", 1],
  #    ["delivered", 23], ["canceled", 51], ["refunded", 1], ["pending", 1],
  #    ["originated", 3]] 

Примечание:

order_pos
  #=> {"ready"=>[0, 7], "in_progress"=>[], "received"=>[], "shipped"=>[1],
  #    "scheduled"=>[6], "pick_up"==>[], "delivered"=>[5], "canceled"=>[8],
  #    "failed"=>[], "refunded"=>[3], "refund_failed"=>[]} 

Необходимо инициализировать order_pos, чтобы его ключи были заказаны по order_arr. Это пример значительного изменения, внесенного в Ruby 1.9, который гарантировал, что хеш-ключи останутся в порядке вставки ключей.

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