Эффективный способ использовать max_by с несколькими условиями - PullRequest
0 голосов
/ 02 ноября 2019

Я пытался выяснить, как использовать методы max_by и max с несколькими условиями. В то время как я думал, что сделал, после обширного тестирования оказалось, что это не так.

Например, у меня есть хэш, значения которого являются хешами:

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

Я хочу найти одинхэш, который имеет максимальную громкость и главу. Так что в этом случае я ожидаю получить ту с томом 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
  }

Который неожиданно начал возвращать тот, что с томом 1 и главой 2! Я до сих пор не уверен, почему это происходит ...

Поэтому я решил попробовать 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 }

, которое работает по очевидным причинам, но чувствует себя довольно неэффективно. Должен быть способ использовать один max или max_by. Хотелось бы услышать некоторые идеи по этому поводу

Ответы [ 2 ]

1 голос
/ 02 ноября 2019

Вы можете сделать следующее.

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#map и Array#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, {}]

0 голосов
/ 02 ноября 2019

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}]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...