在多个条件下使用max_by的有效方法

时间:2019-11-02 15:16:27

标签: ruby-on-rails ruby sorting

我一直在尝试找出如何在多个条件下使用max_bymax方法。虽然我以为是,但经过大量测试,事实并非如此。

例如,我有一个散列,其值是散列:

chapters = { "686050": {
        "volume": '1',
        "chapter": '1',
        "title": 'Chapter Title',
        "lang_code": 'gb',
        "timestamp": 1_565_434_815
      },
      "686049": {
        "volume": '2',
        "chapter": '1',
        "title": 'Chapter Title',
        "lang_code": 'gb',
        "timestamp": 1_565_300_815
      },
      "686048": {
        "volume": '2',
        "chapter": '2',
        "title": 'Chapter Title',
        "lang_code": 'gb',
        "timestamp": 1_565_300_815
      }
    }

我想找到一个散列最大的AND散列。因此,在这种情况下,我希望获得第2卷第2章的内容。

我的第一个实现是使用max

chapters.max do |a, b|
  a[1][:volume].to_d <=> b[1][:volume].to_d &&
    a[1][:chapter].to_d <=> b[1][:chapter].to_d
end

查看此内容时,我的想法是,它将首先按数量进行比较,然后按章节进行比较。这段代码一直有效...直到我将哈希更改为:

  "686050": {
    "volume": '1',
    "chapter": '1',
    "title": 'Chapter Title',
    "lang_code": 'gb',
    "timestamp": 1_565_434_815
  },
  "686049": {
    "volume": '2',
    "chapter": '1',
    "title": 'Chapter Title',
    "lang_code": 'gb',
    "timestamp": 1_565_300_815
  }

哪个突然开始归还第一卷和第二章!我仍然不确定为什么会这样...

因此,我决定在相同的假设下,尝试使用max_by来使用chapters.max_by { |_, v| v[:volume].to_d && v[:chapter].to_d }。当每卷只有一个章节时,也会失败。

我的解决方案最终是:

chapters
  .group_by { |_, v| v[:volume].to_d }.max.last.to_h
  .max_by { |_, v| v[:chapter].to_d }

这是有明显原因的,但感觉效率很低。必须有一种使用单个maxmax_by的方法。很想听听关于此的一些想法

2 个答案:

答案 0 :(得分:1)

您可以执行以下操作。

def max_val(h,key)
  h.map { |_,g| g[key] }.max
end

max_volume = max_val(h, :volume)    #=> "2"
max_chapter = max_val(h, :chapter)  #=> "2"

chapters.find { |_,g| g[:volume] == max_volume && g[:chapter] == max_chapter } 
  #=> [:"686048", {:volume=>"2", :chapter=>"2", :title=>"Chapter Title",
  #                :lang_code=>"gb", :timestamp=>1565300815}] 
如果没有值包含两个键的最大值,则返回

nil。如果要返回具有所需属性的所有键值对(即,如果有联系),请用find替换select。如果没有值包含两个键的最大值,则该数组将为空。

这需要三遍散列。可以一次完成该操作,但很麻烦,我希望它会更耗时,主要是因为Enumerable#mapArray#max都是用C实现的。

可以通过以下步骤一次获得所需的结果。

max_volume  = 0.chr
max_chapter = 0.chr
candidate = [nil, {}]
chapters.each do |k,g|
  old_max_volume = max_volume
  max_volume = [max_volume, g[:volume]].max
  old_max_chapter = max_chapter 
  max_chapter = [max_chapter, g[:chapter]].max
  if max_volume > old_max_volume || max_chapter > old_max_chapter
    candidate = (max_volume == g[:volume] && max_chapter == g[:chapter]) ? 
     [k,g] : [nil, {}]
  end
end
candidate
  #=> [:"686048", {:volume=>"2", :chapter=>"2", :title=>"Chapter Title",
  #    :lang_code=>"gb", :timestamp=>1565300815}] 

现在设置:

chapters[:"686048"][:volume] = '1'

上面的第一个方法现在返回nil,第二个(单次通过)返回[nil, {}]

答案 1 :(得分:0)

Enumerable#max_by

# example 1
a = %w[albatross dog horse]
a.max_by(2) {|x| x.length } #=> ["albatross", "horse"]

# example 2 - Arrays are compared in an “element-wise” manner
arrays = [['1', '1'], ['2', '1'], ['2', '2']]
arrays.max_by(2) { |arr| arr } #=> ["2","2"], ["2", "1"]

所以

chapters.max_by { |_, elem| [elem[:volume], elem[:chapter]] }

让我们测试

book1 = { 
  "686050": {
    "volume": '1',
    "chapter": '1',
    "title": 'Chapter Title',
    "lang_code": 'gb',
    "timestamp": 1_565_434_815
  },
  "686049": {
    "volume": '2',
    "chapter": '1',
    "title": 'Chapter Title',
    "lang_code": 'gb',
    "timestamp": 1_565_300_815
  },
  "686048": {
    "volume": '2',
    "chapter": '2',
    "title": 'Chapter Title',
    "lang_code": 'gb',
    "timestamp": 1_565_300_815
  }
}

book2 = {
  "686050": {
    "volume": '1',
    "chapter": '1',
    "title": 'Chapter Title',
    "lang_code": 'gb',
    "timestamp": 1_565_434_815
  },
  "686049": {
    "volume": '2',
    "chapter": '1',
    "title": 'Chapter Title',
    "lang_code": 'gb',
    "timestamp": 1_565_300_815
  }  
}

pp book1.max_by(2) { |_, elem| [elem[:volume], elem[:chapter]] }
pp book2.max_by { |_, elem| [elem[:volume], elem[:chapter]] }

输出

# 1st test
[[:"686048",
  {:volume=>"2",
   :chapter=>"2",
   :title=>"Chapter Title",
   :lang_code=>"gb",
   :timestamp=>1565300815}],
 [:"686049",
  {:volume=>"2",
   :chapter=>"1",
   :title=>"Chapter Title",
   :lang_code=>"gb",
   :timestamp=>1565300815}]]

# 2nd test
[:"686049",
 {:volume=>"2",
  :chapter=>"1",
  :title=>"Chapter Title",
  :lang_code=>"gb",
  :timestamp=>1565300815}]