【问题标题】:Dynamically sorting an array of hashes in Ruby在 Ruby 中动态排序哈希数组
【发布时间】:2018-08-15 07:43:02
【问题描述】:

我想按几个动态标准对哈希数组进行排序。假设我有这个数组

persons = [
  {
    id: 1,
    first_name: "Bill",
    last_name: "Zamora",
    age: 37
  },
  {
    id: 2,
    first_name: "Alexia",
    last_name: "Reyes",
    age: 70
  },
  {
    id: 3,
    first_name: "Anthony",
    last_name: "Nelson",
    age: 25
  }
]

我知道您可以使用以下代码轻松按多个条件对数组进行排序

persons.sort_by!{ |p| [p[:age], p[:first_name]] }

但是,在此示例中,数组排序所依据的字段的数量和顺序是硬编码的。就我而言,这是在运行时动态确定的。所以我不知道数组是按多少个字段排序的,也不知道哪些字段是按什么顺序排序的。

我正在寻找一种优雅的解决方案来使用我以前不知道的配置对象对我的数组进行排序。这样的配置可能如下所示:

sort_settings = [
  {
    field: "first_name",
    order: "asc"
  },
  {
    field: "age",
    order: "desc"
  }
]

非常感谢您对此的任何帮助!

【问题讨论】:

  • desc 部分可能没有你想象的那么简单。仅针对该功能,代码可能非常复杂。
  • 最好将 sort_settings 中的 field 值作为符号而不是字符串,因为原始哈希中的键是符号。
  • 对于订单,您始终可以按一个方向排序,然后使用#reverse 得到相反的结果。更新:实际上,这只会在按具有不同顺序的多个字段进行排序时有所帮助。

标签: arrays ruby sorting


【解决方案1】:

使用sort_by按desc顺序对字符串进行排序非常具有挑战性,最好使用“低级”sort方法,该方法使用<=>运算符按指定的比较器排序。对此的快速解决方案如下所示:

persons.sort do |a, b|
  comparator = 0

  sort_settings.each do |s|
    a_field = a[s[:field].to_sym]
    b_field = b[s[:field].to_sym]

    comparator = a_field <=> b_field

    comparator = -comparator if s[:order] == "desc"

    break unless comparator == 0
  end

  comparator
end

块必须实现a和b之间的比较,当a跟在b之后返回-1,当a和b相等时返回0,或者如果b跟在a之后+1。

因此,我们遍历sort_settings 并使用&lt;=&gt; 比较指定的字段,这将返回10-1。如果指定的顺序是desc,我们反转值。如果比较器返回不为零的值,我们不需要继续迭代。

【讨论】:

    【解决方案2】:

    忽略asc/desc 功能并假设排序键以符号形式给出并且采用以下格式:

    sort_settings = [
      :first_name,
      :age,
    ]
    

    你可以这样做:

    persons.sort_by{|p| p.values_at(sort_settings)}
    

    【讨论】:

      【解决方案3】:

      代码

      def sort_by_settings(persons, sort_settings)
        sort_mult_by_field = sort_settings.each_with_object({}) do |g,h|
          h[g[:field]] = g[:order] == "asc" ? 1 : -1
        end
      
        longest_string_by_key = persons.each_with_object(Hash.new(0)) do |g,h|
          g.each { |k,v| h[k] = [h[k], g[k].size].max if sort_mult_by_field.key?(k) &&
            v.is_a?(String) }
        end
      
        sort_by_arr = persons.each_with_object({}) do |g,h|    
          h[g] = sort_mult_by_field.each_with_object([]) do |(f,m),a|
            gv = g[f]
            a <<
            case gv
            when Integer
              m * gv
            when String
              gv.chars.map { |c| m * c.ord }.concat([m * -256]*(longest_string_by_key[f]-gv.size))
            else # rescue...
            end
          end
        end
      
        persons.sort_by { |g| sort_by_arr[g] }
      end
      

      示例

      persons 与问题中的定义相同。

      sort_settings = [{field: :first_name, order: "asc"}, {field: :age, order: "desc"}]
      
      sort_by_settings(persons, sort_settings)
        #=> [{:id=>2, :first_name=>"Alexia",  :last_name=>"Reyes",  :age=>70},
        #    {:id=>3, :first_name=>"Anthony", :last_name=>"Nelson", :age=>25},
        #    {:id=>1, :first_name=>"Bill",    :last_name=>"Zamora", :age=>37}]
      
      persons1 = persons + [{ id: 4, first_name: "Alexia", last_name: "Whoosit", age: 71 }]
      sort_by_settings(persons1, sort_settings)
        #=> [{:id=>4, :first_name=>"Alexia",  :last_name=>"Whoosit", :age=>71},
        #    {:id=>2, :first_name=>"Alexia",  :last_name=>"Reyes",   :age=>70},
        #    {:id=>3, :first_name=>"Anthony", :last_name=>"Nelson",  :age=>25},
        #    {:id=>1, :first_name=>"Bill",    :last_name=>"Zamora",  :age=>37}]
      
      sort_settings1 = [{field: :first_name, order: "desc"}, {field: :age, order: "asc"}]
      sort_by_settings(persons1, sort_settings1)
        #=> [{:id=>1, :first_name=>"Bill",    :last_name=>"Zamora",  :age=>37},
        #    {:id=>3, :first_name=>"Anthony", :last_name=>"Nelson",  :age=>25},
        #    {:id=>2, :first_name=>"Alexia",  :last_name=>"Reyes",   :age=>70}, 
        #    {:id=>4, :first_name=>"Alexia",  :last_name=>"Whoosit", :age=>71}]
      

      说明

      在第一个示例的计算中,计算了以下中间值。

      sort_mult_by_field
        #=> {:first_name=>1, :age=>-1}
      
      longest_string_by_key
        #=> {:first_name=>7}
      
      sort_by_arr
        #=> {{:id=>1, :first_name=>"Bill",    :last_name=>"Zamora", :age=>37}=>
        #      [[66, 105, 108, 108, -256, -256, -256], -37],
        #    {:id=>2, :first_name=>"Alexia",  :last_name=>"Reyes",  :age=>70}=>
        #      [[65, 108, 101, 120,  105,   97, -256], -70],
        #    {:id=>3, :first_name=>"Anthony", :last_name=>"Nelson", :age=>25}=>
        #      [[65, 110, 116, 104,  111,  110,  121], -25]}
      

      【讨论】:

      • 干得好!唯一与"asc""desc" 一起正常工作
      【解决方案4】:

      您将需要一种将给定项目转换为排序键的方法,使用配置作为指导:

      def build_sort_key_for(item, configuration)
        configuration.map { |entry|
          value = item[entry[:field].to_sym]
          value = -value if entry[:order] == "desc" # this will only work on numeric values
          value
        }
      end
      

      然后你只需在你的 sort_by 中调用它:

      persons.sort_by!{ |p| build_sort_key_for(p, configuration) }
      

      "desc" 为字符串工作本身就是一个挑战,因此留给读者(或一个单独的问题)。

      【讨论】:

        猜你喜欢
        • 2018-11-15
        • 2011-10-27
        • 1970-01-01
        • 2014-03-05
        • 2011-05-19
        • 2011-07-25
        • 1970-01-01
        • 2014-08-01
        相关资源
        最近更新 更多