代码之家  ›  专栏  ›  技术社区  ›  lumos

基于多个条件对哈希数组进行排序和重新排列

  •  1
  • lumos  · 技术社区  · 7 年前

    我试图根据3个不同的标准对数组进行排序。假设我有这样一个哈希数组:

    a = [
        { "name" => "X", "year" => "2013-08"},
        { "name" => "A", "year" => "2017-01"},
        { "name" => "X", "year" => "2000-08"},
        { "name" => "B", "year" => "2018-05"},
        { "name" => "D", "year" => "2016-04"},
        { "name" => "C", "year" => "2016-04"}
    ]
    

    {"name"=>"X", "year"=>"2013-08"}
    {"name"=>"X", "year"=>"2000-08"}
    {"name"=>"B", "year"=>"2018-05"}
    {"name"=>"A", "year"=>"2017-01"}
    {"name"=>"C", "year"=>"2016-04"}
    {"name"=>"D", "year"=>"2016-04"}
    

    所以一切都是按“年”的降序排列,然后按“名”的升序排列,然后所有“名”==“X”的散列移到顶部,仍然按“年”排序。

    a.sort { |a,b| [b["year"], a["name"]] <=> [a["year"], b["name"]] }        
    

    但这只处理我需要的前两个标准。后来我试过这样的方法:

    top = []
    a.each { |x| top << x if x["name"] == "X" }
    a.delete_if { |x| x["name"] == "X"}
    a.unshift(top)
    

    它确实产生了所需的输出,但显然是笨重的,似乎不是最好的方式做事情。有没有一个更快,更有效的方法来做我想做的事?

    (仅供参考,年份值是字符串,我无法将其转换为整数。我简化了这里的值,但是我从中提取的数据实际上在每个值的末尾附加了一系列其他字符和符号。)

    4 回复  |  直到 7 年前
        1
  •  1
  •   engineersmnky    7 年前

    a = [
      { "name" => "X", "year" => "2013-08"},
      { "name" => "A", "year" => "2017-01"},
      { "name" => "X", "year" => "2000-08"},
      { "name" => "B", "year" => "2018-05"},
      { "name" => "D", "year" => "2016-04"},
      { "name" => "C", "year" => "2016-04"}
    ]
    
    
    a.sort do  |a,b| 
      a_ord, b_ord = [a,b].map {|e| e["name"] == "X" ? 0 : 1 }
      [a_ord,b["year"],a["name"] ] <=> [b_ord, a["year"],b["name"]]
    end
    

    a.sort do  |a,b| 
      [a,b].map {|e| e["name"] == "X" ? 0 : 1 }.zip(
        [b["year"],a["year"]],[a["name"],b["name"]]
      ).reduce(:<=>)
    end
    
        2
  •  3
  •   tadman    7 年前

    sort 如果您有一致的排序条件,则不是要使用的对象。更快的方法是 sort_by :

    a.sort_by { |e| [ e["year"], e["name"] ] }
    

    a.sort_by { |e| [ e["year"], e["name"] ] }.reverse
    

    实际上,它根据块中表示的转换形式对数组中的每个元素进行排序,然后根据这些元素进行排序。这种转换是一次又一次地进行的,这比 方法,每次进行比较时必须执行该转换。

    现在,如果您想将“X”项排序到顶部,您可以轻松地将其添加为附加条件:

    a.sort_by { |e| [ e["name"] == "X" ? 1 : 0, e["year"], e["name"] ] }.reverse
    

    这样你就能到达你想去的地方。

    通常可以将非常复杂的排序逻辑表示为数组中的一系列元素。只要每一个元素都是可比较的,就都能解决问题。

        3
  •  1
  •   Cary Swoveland    7 年前
    arr = [
      {"name"=>"X", "year"=>"2013-08"},
      {"name"=>"X", "year"=>"2000-08"},
      {"name"=>"B", "year"=>"2018-05"},
      {"name"=>"A", "year"=>"2017-01"},
      {"name"=>"C", "year"=>"2016-04"},
      {"name"=>"D", "year"=>"2016-04"},
    ]
    

    当一个数组的各个部分的排序方式不同于数组的其他部分时,我发现将数组划分为相关的部分,分别对每个部分进行排序,然后合并这些排序的结果是有益的。这种方法不仅通常易于读者理解,而且简化了测试,并且至少与执行单个更复杂的排序一样高效。在这里,我们将数组分为两部分。

    x, non_x = arr.partition { |h| h["name"] == 'X' }
      #=> [[{"name"=>"X", "year"=>"2013-08"}, {"name"=>"X", "year"=>"2000-08"}],
      #    [{"name"=>"B", "year"=>"2018-05"}, {"name"=>"A", "year"=>"2017-01"},
      #     {"name"=>"C", "year"=>"2016-04"}, {"name"=>"D", "year"=>"2016-04"}]]
    

    排序数组 x 这很简单。

    sorted_x = x.sort_by { |h| h["year"] }.reverse
      #=> [{"name"=>"X", "year"=>"2013-08"}, {"name"=>"X", "year"=>"2000-08"}]
    

    排序 non_x 是更复杂的,因为它是按 递减 值的顺序 "year" ,用价值观打破束缚 "name" 增加的 命令。在这种情况下,我们可以随时使用 Array#sort .

    non_x.sort do |g,h|
      case g["year"] <=> h["year"]
      when -1
        1
      when 1
        -1
      when 0
        (g["name"] < h["name"]) ? -1 : 1
      end
    end
      #=> [{"name"=>"B", "year"=>"2018-05"}, {"name"=>"A", "year"=>"2017-01"},
      #    {"name"=>"C", "year"=>"2016-04"}, {"name"=>"D", "year"=>"2016-04"}]
    

    只要稍加努力,我们就可以 Enumerable#sort_by . 给一个杂凑 h ,我们需要对其中任何一个进行排序

    [h["year"], f(h["name"])].reverse
    

    f 是导致 h["name"] 按降序排序,或 .reverse

    [f(h["year"]), h["name"]]
    

    哪里 f 是导致 h["year"] 按降序排列。后者更容易实现。我们可以使用以下方法。

    def year_str_to_int(year_str)
      yr, mon = year_str.split('-').map(&:to_i)
      12 * yr + mon
    end
    

    这样我们就可以分类了 根据需要:

    sorted_non_x = non_x.sort_by { |h| [-year_str_to_int(h["year"]), h["name"]] }
      #=> [{"name"=>"B", "year"=>"2018-05"}, {"name"=>"A", "year"=>"2017-01"},
      #    {"name"=>"C", "year"=>"2016-04"}, {"name"=>"D", "year"=>"2016-04"}]
    

    我们现在只需将这两个已排序的分区组合起来。

    sorted_x.concat(sorted_non_x)
      #=> [{"name"=>"X", "year"=>"2013-08"}, {"name"=>"X", "year"=>"2000-08"},
      #    {"name"=>"B", "year"=>"2018-05"}, {"name"=>"A", "year"=>"2017-01"}, 
      #    {"name"=>"C", "year"=>"2016-04"}, {"name"=>"D", "year"=>"2016-04"}]
    
        4
  •  1
  •   Surya    7 年前

    require 'pp'
    
    a = [
        { "name" => "X", "year" => "2013-08"},
        { "name" => "A", "year" => "2017-01"},
        { "name" => "X", "year" => "2000-08"},
        { "name" => "B", "year" => "2018-05"},
        { "name" => "D", "year" => "2016-04"},
        { "name" => "C", "year" => "2016-04"}
    ]
    
    class NameYearSorter
      attr_reader :value
      def initialize(value)
        @value = value
      end
    
      def name
        value['name']
      end
    
      def year
        value['year']
      end
    
      def <=>(other)
        if self.name != 'X' && other.name != 'X'
          if self.year == other.year
            self.name <=> other.name
          else
            self.year > other.year ? -1 : 0
          end
        elsif self.name == 'X' && other.name != 'X'
          -1
        elsif other.name == 'X' && self.name != 'X'
          0   
        elsif self.name == other.name
          other.year > self.year ? 0 : -1
        end
      end
    end
    
    sortable = a.map{ |v| NameYearSorter.new(v) }
    pp sortable.sort.map(&:value)
    
    # Output:
    #=> [{"name"=>"X", "year"=>"2013-08"},
    #=>  {"name"=>"X", "year"=>"2000-08"},
    #=>  {"name"=>"B", "year"=>"2018-05"},
    #=>  {"name"=>"A", "year"=>"2017-01"},
    #=>  {"name"=>"C", "year"=>"2016-04"},
    #=>  {"name"=>"D", "year"=>"2016-04"}]
    
    推荐文章