查找离数组平均值最近的元素

4

以下是“Ruby”方式执行以下操作的方法;我仍然以更加命令式的编程风格思考,而没有真正适应以Ruby思考。我想要做的是找到大小最接近数组平均值的元素,例如,考虑以下数组。

[1,2,3] 

平均值为2.0。我想编写的方法将从上下两个方向返回最接近平均值的元素,即1和3。

下面的另一个例子会更好地说明这一点:

[10,20,50,33,22] avg is 27.0 method would return 22 and 33.

什么?[1,2,3]的平均值不是6,而是2总和6。那么13如何接近于62呢?请使用准确的术语和细节进行编辑。如果数组恰好包含平均值怎么办?三个值等于平均值? - Phrogz
1
另外,未来请注意在您的问题中包含您已尝试解决问题的代码。这个网站旨在帮助人们获得正确的答案,而不是为他们解决问题。尽管我刚刚做到了这一点。 :) - Phrogz
1
这两个数组的平均值都为3,你希望返回哪些值:[1,2,3,4,5][1,2,3,3,4,5] - Cary Swoveland
+1,考虑用 ruby 的方式来实现 :) - sameera207
2个回答

7

这不是最有效的方法,但(在我谦虚的看法中)它非常符合Ruby编程风格。

class Array
  # Return the single element in the array closest to the average value
  def closest_to_average
    avg = inject(0.0,:+) / length
    min_by{ |v| (v-avg).abs }
  end
end

[1,2,3].closest_to_average
#=> 2 

[10,20,50,33,22].closest_to_average
#=> 22 

如果您真的想要最接近的n个项目,则:

class Array
  # Return a number of elements in the array closest to the average value
  def closest_to_average(results=1)
    avg = inject(0.0,:+) / length
    sort_by{ |v| (v-avg).abs }[0,results]
  end
end

[10,20,50,33,22].closest_to_average     #=> [22] 
[10,20,50,33,22].closest_to_average(2)  #=> [22, 33] 
[10,20,50,33,22].closest_to_average(3)  #=> [22, 33, 20] 

工作原理

avg = inject(0.0,:+) / length
这是一种简写方式:
avg = self.inject(0.0){ |sum,n| sum+n } / self.length
我从一个值0.0开始,而不是0,以确保总和将是浮点数,这样除以长度不会给我一个整数舍入的值。

sort_by{ |v| (v-avg).abs }
根据数字与平均值之间的差异(从低到高)对数组进行排序,然后:
[0,results]
从该数组中选择前results个条目。


1
inject(:+) 就是一个更简洁的方式来获得数组中所有元素的总和。 - SparkyRobinson
1
@SparkyRobinson 但如果数组为空,它将返回nil而不是0.0(导致在进行除法时出错)。这种边缘情况是我使用该形式的唯一原因。感谢您指出这一点,加1。 - Phrogz
1
请注意,@Sparky提醒我们,在使用inject时,将:+转换为proc并非必要。我认为在讨论初始值时,这一点可能被忽略了。 - Cary Swoveland
1
@CarySwoveland FWIW,我没有漏掉这个问题,但我没有意识到它可以用于两个参数的调用,即inject(0.0,:+);感谢你的推动,让我去检查并发现真相。这个Core方法有这种便利性确实让我感到不安,因为其他很多方法都没有。然而,它稍微更易读和方便,所以我已经编辑了回复来使用它(即使它只是Ruby语法中另一个记忆魔法的例子)。 - Phrogz
或许最好省略 inject 的初始值,这样如果没有浮点数,就可以使用整数算术进行求和,然后再除以 length.to_f。能够向 inject 传递一个符号是一个很棒的特性,经常被忽视。我喜欢的一个例子是:[{a: 1},{b: 2},{c: 3}].inject(:merge) => {:a=>1, :b=>2, :c=>3} - Cary Swoveland

2
我假设所需的是一个数组中比平均数小的最大元素和比平均数大的最小值。仅当该数组至少有两个元素且它们不全相同时,才存在这样的值。 假设这个条件适用,我们只需要将其从文字转换为符号:
avg = a.reduce(:+)/a.size.to_f
[ a.select { |e| e < avg }.max, a.select { |e| e > avg }.min ]

另一种方式,效率略低:
avg = a.reduce(:+)/a.size.to_f
b = (a + [avg]).uniq.sort
i = b.index(avg)
[ b[i-1], b[i+1] ]

也许 b=a+[avg] 可以避免污染原始数组。同时注意,原始数组中只需要两个元素即可。对于猜测需求和实现加1分。 - Phrogz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接