【问题标题】:Ruby Array Sort 2 different waysRuby数组排序2种不同的方式
【发布时间】:2017-01-15 09:37:27
【问题描述】:

我有一个对象数组,我试图按多个标准进行排序。大多数比较只是对它们的哈希值进行<=>,因此使用sort_by 非常快,但其中一个比较复杂。

数组是足球队的,目前排序如下:

teams.sort_by { |item| [item.points, item.goal_dif, item.goals] }

但是,如果最后 2 个团队在这 3 个字段上具有相同的值,我希望决胜局是我创建的函数,a_beat_b(teamA, teamB)

我尝试使用 Array.sort,但与前几个 sort_by 相比,它的速度非常慢......我的实现是这样的:

teams.sort ( |a,b| [a.points, a.goals_dif, a.goals] <=> [b.points, b.goals_dif, b.goals])
与 sort_by 相比,它非常慢。 points、goals_dif 和 goal 的函数需要一些简单的查询,但如果必须进行数百次查询,就会陷入困境。

我不太擅长 Ruby,所以不知道把我的 a_beats_b 放在哪里。 (如果 A 分别击败、平局或输给 B,则返回 1、0 或 -1)

【问题讨论】:

  • a_beat_b 的实现是什么样的?当您说“无法正确获取代码”时,您是什么意思?
  • a_beat_b<=> 类似,如果 A 队在本次锦标赛中击败 B,则返回 1。如果是平局,则为 0,如果 B 赢,则为 -1。 Array.sort 的代码是这样的,teams.sort ( |a,b| [a.points, a.goals_dif, a.goals] <=> [b.points, b.goals_dif, b.goals]) 不知道把我的函数放在哪里。将编辑它。
  • Array#sort 是正确的答案。您应该检查数组是否相同,然后有条件地使用a_beat_b 来执行更昂贵的比较。没有看到您如何尝试实施Array#sort,我们无法真正帮助您。
  • 我已经编辑了,不知道怎么写排序来使用我的功能。
  • 关于速度慢:pointsgoals_difgoals 是昂贵的方法吗?

标签: arrays ruby sorting


【解决方案1】:

我尝试使用 Array.sort,但与前几个 sort_by 相比,它非常慢

这是因为sort 多次调用给定块。下面是一个展示幕后情况的示例:(按长度对"apple""pear""fig" 进行排序)

def length(str)
  puts "calculating #{str.inspect}.length"
  str.length
end

array = %w{apple pear fig}
array.sort { |a, b| length(a) <=> length(b) }
#=> ["fig", "pear", "apple"]

length 方法的输出:

calculating "apple".length
calculating "pear".length
calculating "apple".length
calculating "fig".length
calculating "pear".length
calculating "fig".length

如您所见,length 在排序过程中被多次调用。想象一下,这些是数据库查询。

另一方面,sort_by 为每个元素调用一次块,构建内部映射:

array.sort_by { |a| length(a) }
#=> ["fig", "pear", "apple"]

输出:

calculating "apple".length
calculating "pear".length
calculating "fig".length

对于昂贵的操作(如数据库查询),这要快得多。但它也不太灵活——您不能再动态比较ab

但是,您可以存储(昂贵的)操作的结果,例如使用哈希:(这称为memoization

hash = Hash.new { |h, k| h[k] = length(k) }

并使用sort内的哈希:

array.sort { |a, b| hash[a] <=> hash[b] }
# calculating "apple".length
# calculating "pear".length
# calculating "fig".length
#=> ["fig", "pear", "apple"]

排序后,我们的哈希是这样的:

hash #=> {"apple"=>5, "pear"=>4, "fig"=>3}

应用于您的代码,这样的事情应该可以工作:

hash = Hash.new { |h, k| h[k] = [k.points, k.goal_dif, k.goals] }
teams.sort { |a, b| hash[a] == hash[b] ? a_beats_b(a, b) : hash[a] <=> hash[b] }

【讨论】:

  • 哇,我真的要研究一下你在那里做了什么。我已经知道排序函数之间的区别,但是这里有很多很棒的东西。不管怎样,这就是答案。谢谢!
【解决方案2】:

包括a_beats_bsort 的实现:

teams.sort do |a, b|
  result = [a.points, a.goals_dif, a.goals] <=> [b.points, b.goals_dif, b.goals]
  result.zero? ? a_beats_b(a, b) : result
end

【讨论】:

  • 那个result.zero?东西看起来很棒!这段代码确实有效,但是那种排序仍然比 sort_by 慢得多,与大约 1 相比,它需要 5 秒以上。如果这是唯一可能的方法,我会接受答案。
【解决方案3】:

这是另一种方法,虽然有些复杂,但专为提高效率而设计。该方法使用以下步骤。

  • 将每个Team 实例转换为一个数组,该数组包含实例和要对其进行廉价排序的三元素数组。
  • 使用Enumerable#sort_by 按三元素数组对数组进行排序。
  • 使用Enumerable#chunk 对具有相等三元素数组的二元素数组进行分组。
  • 将每个分块数组元素映射到二元素数组中的Team 实例。
  • 在使用a_beat_b(a, b) 方法排序后,使用Enumerable#flat_map 映射Team 实例的每个分块组(当然,除非该组只包含一个团队)。

代码

def sort_em(teams)
  teams.map { |t| [t, [t.points, t.goal_dif, t.goals]] }.
        sort_by(&:last).
        chunk(&:last).
        map { |_,tied_teams| tied_teams.map(&:first) }.
        flat_map { |tied_teams| (tied_teams.size == 1) ?
          tied_teams.first : tied_teams.sort { |a,b| a_beat_b(a, b) } }
end

示例

class Team
  attr_reader :name, :points, :goal_dif, :goals
  def initialize(name, points, goal_dif, goals)
    @name, @points, @goal_dif, @goals = name, points, goal_dif, goals
  end
end

teams = [Team.new("bluebirds", 233, 25, 130),
         Team.new("eagles",    233, 18, 105),
         Team.new("jays",      233, 25, 130),
         Team.new("owls",      160, 12, 105),
         Team.new("sparrows",  233, 18, 105)
        ]
  #=> [#<Team:0x007ff2f900e5a8 @name="bluebirds", @points=233, @goal_dif=25, @goals=130>,
  #    #<Team:0x007ff2f900e530 @name="eagles", @points=233, @goal_dif=18, @goals=105>,
  #    #<Team:0x007ff2f900e4b8 @name="jays", @points=233, @goal_dif=25, @goals=130>,
  #    #<Team:0x007ff2f900e440 @name="owls", @points=160, @goal_dif=12, @goals=105>,
  #    #<Team:0x007ff2f900e3c8 @name="sparrows", @points=233, @goal_dif=18, @goals=105>]

def a_beat_b(a, b)
  a.name.size <=> b.name.size
end

sort_em(teams)
  #=> [#<Team:0x007ff2fa845630 @name="owls", @points=160, @goal_dif=12, @goals=105>,
  #    #<Team:0x007ff2fa845720 @name="eagles", @points=233, @goal_dif=18, @goals=105>,
  #    #<Team:0x007ff2fa8455b8 @name="sparrows", @points=233, @goal_dif=18, @goals=105>,
  #    #<Team:0x007ff2fa8456a8 @name="jays", @points=233, @goal_dif=25, @goals=130>,
  #    #<Team:0x007ff2fa8457e8 @name="bluebirds", @points=233, @goal_dif=25, @goals=130>]

说明

步骤如下。

a = teams.map { |t| [t, [t.points, t.goal_dif, t.goals]] }
  #=> [[#<Team:0x007ff2fa8457e8 @name="bluebirds", @points=233, @goal_dif=25, @goals=130>,
  #     [233, 25, 130]],
  #    [#<Team:0x007ff2fa845720 @name="eagles", @points=233, @goal_dif=18, @goals=105>,
  #     [233, 18, 105]],
  #    [#<Team:0x007ff2fa8456a8 @name="jays", @points=233, @goal_dif=25, @goals=130>,
  #     [233, 25, 130]],
  #    [#<Team:0x007ff2fa845630 @name="owls", @points=160, @goal_dif=12, @goals=105>,
  #     [160, 12, 105]],
  #    [#<Team:0x007ff2fa8455b8 @name="sparrows", @points=233, @goal_dif=18, @goals=105>,
  #     [233, 18, 105]]] 
b = a.sort_by(&:last)
  #=> [[#<Team:0x007ff2fa845630 @name="owls", @points=160, @goal_dif=12, @goals=105>,
  #    [160, 12, 105]],
  #   [#<Team:0x007ff2fa845720 @name="eagles", @points=233, @goal_dif=18, @goals=105>,
  #    [233, 18, 105]],
  #   [#<Team:0x007ff2fa8455b8 @name="sparrows", @points=233, @goal_dif=18, @goals=105>,
  #    [233, 18, 105]],
  #   [#<Team:0x007ff2fa8457e8 @name="bluebirds", @points=233, @goal_dif=25, @goals=130>,
  #    [233, 25, 130]],
  #   [#<Team:0x007ff2fa8456a8 @name="jays", @points=233, @goal_dif=25, @goals=130>,
  #    [233, 25, 130]]
  #   ] 
c = b.chunk(&:last)
  #=> #<Enumerator: #<Enumerator::Generator:0x007ff2fa81dc20>:each> 

要查看枚举器c 生成了哪些值,我们可以将其转换为数组。

c.to_a 
  #=> [[[160, 12, 105],
  #     [[#<Team:0x007ff2fa845630 @name="owls",@points=160,@goal_dif=12,@goals=105>,
  #       [160, 12, 105]
  #      ]
  #     ]
  #    ],
  #    [[233, 18, 105],
  #     [[#<Team:0x007ff2fa845720 @name="eagles",@points=233,@goal_dif=18,@goals=105>,
  #       [233, 18, 105]
  #      ],
  #     [#<Team:0x007ff2fa8455b8 @name="sparrows",@points=233,@goal_dif=18,@goals=105>,
  #       [233, 18, 105]
  #     ]
  #    ],
  #    [[233, 25, 130],
  #     [[#<Team:0x007ff2fa8457e8 @name="bluebirds",@points=233,@goal_dif=25,@goals=130>,
  #       [233, 25, 130]
  #      ],
  #      [#<Team:0x007ff2fa8456a8 @name="jays", @points=233,@goal_dif=25,@goals=130>,
  #       [233, 25, 130]
  #      ]
  #     ]
  #    ]
  #   ]

d = c.map { |_,tied_teams| tied_teams.map(&:first) }
  #=> [[#<Team:0x007ff2fa845630 @name="owls", @points=160, @goal_dif=12, @goals=105>],
  #    [#<Team:0x007ff2fa845720 @name="eagles", @points=233, @goal_dif=18, @goals=105>,
  #     #<Team:0x007ff2fa8455b8 @name="sparrows", @points=233, @goal_dif=18, @goals=105>
  #    ],
  #    [#<Team:0x007ff2fa8457e8 @name="bluebirds", @points=233, @goal_dif=25, @goals=130>,
  #     #<Team:0x007ff2fa8456a8 @name="jays", @points=233, @goal_dif=25, @goals=130>
  #    ]
  #   ] 
d.flat_map { |tied_teams| (tied_teams.size == 1) ?
  tied_teams.first : tied_teams.sort { |a,b| a_beat_b(a, b) } }
  #=> [#<Team:0x007ff2fa845630 @name="owls", @points=160, @goal_dif=12, @goals=105>,
  #    #<Team:0x007ff2fa845720 @name="eagles", @points=233, @goal_dif=18, @goals=105>,
  #    #<Team:0x007ff2fa8455b8 @name="sparrows", @points=233, @goal_dif=18, @goals=105>,
  #    #<Team:0x007ff2fa8456a8 @name="jays", @points=233, @goal_dif=25, @goals=130>,
  #    #<Team:0x007ff2fa8457e8 @name="bluebirds", @points=233, @goal_dif=25, @goals=130>
  #   ] 

【讨论】:

    【解决方案4】:

    根据排序函数的大小和常量,这可能是一种方法:

    # First group the teams by standard sort:
    groups = teams.group_by{|a| [a.points, a.goals_dif, a.goals] }
    
    # For each group that has ties. Run the slow sorter on them:
    groups.each{ |_,val| val.sort!{|teamA,teamB| a_beat_b(teamA, teamB)} if val.size > 1 }
    
    # Finally run sort on the keys of the group by:
    groups.sort.flat_map(&:last)
    

    【讨论】:

      猜你喜欢
      • 2012-07-27
      • 1970-01-01
      • 2019-06-24
      • 2019-03-05
      • 2017-02-07
      • 1970-01-01
      • 2015-05-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多