根据另一个数组中的顺序对数组数组进行排序

时间:2019-03-13 11:30:25

标签: arrays ruby sorting

我有一个数组数组:

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 个答案:

答案 0 :(得分:5)

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]]

答案 1 :(得分:4)

您几乎可以做到这一点: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]] }

基准

这是此场景的基准,其中包含更大的数据集:https://repl.it/repls/SentimentalAdequateClick。看起来Sawa的方法是带路的,尽管如果将来有重复的值,我的最后努力也会很轻松。另外,我的第二次努力很糟糕(这让我有些惊讶):)

答案 2 :(得分:4)

assoc似乎很有帮助:“搜索一个数组,该数组的元素也是使用obj。==将obj与每个包含的数组的第一个元素进行比较的数组。”

    {
        path: '/app/userRole1/Dashboard',
        component: DashboardUserRole1,
        name: 'dashboardRole1',
        meta: {
            title: 'Dashboard Role1',
            userRole: 1
        },
    },
    {
        path: '/app/userRole2/Dashboard',
        component: DashboardUserRole2,
        name: 'dashboardRole2',
        meta: {
            title: 'Dashboard Role2',
            userRole: 2
        },
    },
    {
        path: '/app/Admin/Dashboard',
        component: DashboardAdmin,
        name: 'dashboardAdmin',
        meta: {
            title: 'Dashboard Admin',
            userRole: 7
        },
    },

答案 3 :(得分:2)

我建议

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 }

答案 4 :(得分:1)

您可以尝试下面的代码来有效地找到输出,

order_array.map { |p| x.detect { |y| y[0] == p } }.compact
# => [["ready", 5], ["shipped", 1], ["scheduled", 1], ["delivered", 23], ["canceled", 51], ["refunded", 1]]

答案 5 :(得分:0)

我假设:

  • x的每个元素的第一个元素不一定是唯一的;
  • 第一个元素相同且第一个元素是x成员的order_array的所有元素按照在{{ 1}};
  • 第一个元素不是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进行排序。这是在Ruby 1.9中进行有争议的更改的价值的一个示例,该更改保证了哈希键将保持按键插入顺序进行。