Calculating Median in Ruby
Asked Answered
O

12

58

How do I calculate the median of an array of numbers using Ruby?

I am a beginner and am struggling with handling the cases of the array being of odd and even length.

Overlong answered 13/2, 2013 at 17:15 Comment(1)
You might want to use a Gem, e.g. github.com/ankane/active_medianMultiplicity
C
115

Here is a solution that works on both even and odd length array and won't alter the array:

def median(array)
  return nil if array.empty?
  sorted = array.sort
  len = sorted.length
  (sorted[(len - 1) / 2] + sorted[len / 2]) / 2.0
end
Concavoconcave answered 13/2, 2013 at 17:37 Comment(2)
return 0 if array.empty?Seniority
@ArtemKalinchuk why 0? Median is undefined for an empty array. I agree this method should be improved to handle this case either by returning something like Float::NAN or raising a custom, more explicit error.Concavoconcave
S
8

Similar to nbarraille's, but I find it a bit easier to keep track of why this one works:

class Array
  def median
    sorted = self.sort
    half_len = (sorted.length / 2.0).ceil
    (sorted[half_len-1] + sorted[-half_len]) / 2.0
  end
end

half_len = number of elements up to and including (for array with odd number of items) middle of array.

Even simpler:

class Array
  def median
    sorted = self.sort
    mid = (sorted.length - 1) / 2.0
    (sorted[mid.floor] + sorted[mid.ceil]) / 2.0
  end
end
Saadi answered 19/12, 2013 at 9:26 Comment(0)
I
4

If by calculating Median you mean this

Then

a = [12,3,4,5,123,4,5,6,66]
a.sort!
elements = a.count
center =  elements/2
elements.even? ? (a[center] + a[center+1])/2 : a[center]  
Imparadise answered 13/2, 2013 at 17:30 Comment(4)
yeah, the even number one makes it an if ... else procedure (in what I've learned so far)Overlong
Doesn't have to have a if/else, see my answerConcavoconcave
Needs 2.0 or a to_f to make results like 4.5 possible.Suchlike
Additionally the final line should be elements.even? ? (a[center] + a[center-1])/2.0 : a[center] (note the minus in a[center-1] and the 2.0) otherwise the median value of an even-length array is offset by 1 index position in the array. Try the original code with a 2-value array and you will get an error. This just bit me...Synchronous
M
3
  def median(array)                          #Define your method accepting an array as an argument. 
      array = array.sort                     #sort the array from least to greatest
      if array.length.odd?                   #is the length of the array odd?
        array[(array.length - 1) / 2] #find value at this index
      else array.length.even?                #is the length of the array even?
       (array[array.length/2] + array[array.length/2 - 1])/2.to_f
                                             #average the values found at these two indexes and convert to float
      end
    end
Monorail answered 3/4, 2015 at 0:57 Comment(3)
There are many ways to do this, but your answer is the most elegant. Very clear what is going on.Buxton
If the array.length isn't odd, then is even, or could it be something else?, is it necessary the expression after else?, are also the return needed?Diagnostic
I don't think the even evaluation is needed as well.Dictaphone
A
3

I like to use Refinements, which is a safe way to Monkey Patch the ruby classes without collateral effects over the system.

The usage become much more cleaner than a new method.

With the Refinements you can monkey patch the Array class, implement the Array#median and this method will only be available inside the scope of the class that is using the refinement! :)

Refinements

module ArrayRefinements
  refine Array do
    def median
      return nil if empty?
      sorted = sort
      (sorted[(length - 1) / 2] + sorted[length / 2]) / 2.0
    end
  end
end

class MyClass
  using ArrayRefinements
  # You can use the Array#median as you wish here

  def test(array)
    array.median
  end
end

MyClass.new.test([1, 2, 2, 2, 3])
=> 2.0
Astigmia answered 17/1, 2021 at 22:50 Comment(0)
O
1

More correct solution with handling edge cases:

class Array
  def median
    sorted = self.sort
    size = sorted.size
    center = size / 2

    if size == 0
      nil
    elsif size.even?
      (sorted[center - 1] + sorted[center]) / 2.0
    else
      sorted[center]
    end
  end
end

There is a specs to prove:

describe Array do
  describe '#median' do
    subject { arr.median }

    context 'on empty array' do
      let(:arr) { [] }

      it { is_expected.to eq nil }
    end

    context 'on 1-element array' do
      let(:arr) { [5] }

      it { is_expected.to eq 5 }
    end

    context 'on 2-elements array' do
      let(:arr) { [1, 2] }

      it { is_expected.to eq 1.5 }
    end

    context 'on odd-size array' do
      let(:arr) { [100, 5, 2, 12, 1] }

      it { is_expected.to eq 5 }
    end

    context 'on even-size array' do
      let(:arr) { [7, 100, 5, 2, 12, 1] }

      it { is_expected.to eq 6 }
    end
  end
end
Ohara answered 2/4, 2018 at 12:54 Comment(0)
S
0
def median(array)
  half = array.sort!.length / 2
  array.length.odd? ? array[half] : (array[half] + array[half - 1]) / 2 
end

*If the length is even, you must add the middle point plus the middle point - 1 to account for the index starting at 0

Soda answered 22/2, 2015 at 23:52 Comment(1)
using array.sort! is not a good idea since it will change the parameterGodship
B
0
def median(arr)
    sorted = arr.sort 
    if sorted == []
       return nil
    end  

    if sorted.length % 2 != 0
       result = sorted.length / 2 # 7/2 = 3.5 (rounded to 3)
       return sorted[result] # 6 
    end

    if sorted.length % 2 == 0
       result = (sorted.length / 2) - 1
       return (sorted[result] + sorted[result+1]) / 2.0 #  (4 + 5) / 2
    end
end

p median([5, 0, 2, 6, 11, 10, 9])
Briannebriano answered 1/12, 2019 at 5:42 Comment(0)
D
0

Here's a solution:

app_arry = [2, 3, 4, 2, 5, 6, 16].sort

# check array isn't empty
if app_arry.empty?  || app_arry == ""
  puts "Sorry, This will not work."
  return nil
end

length = app_arry.length
puts "Array length = #{length}"
puts "Array = #{app_arry}"

if length % 2  == 0
 # even number of elements
 puts "median is #{(app_arry[length/2].to_f +  app_arry[(length-1)/2].to_f)/2}"
else
 # odd number of elements
 puts "median is #{app_arry[(length-1)/2]}"
end

OUTPUT

Array length = 7

Array = [2, 3, 4, 2, 5, 6, 16]

median is 2

Devries answered 4/2, 2020 at 15:26 Comment(0)
H
0
def median(array, already_sorted=false)
    return nil if array.empty?
    array = array.sort unless already_sorted
    m_pos = array.size / 2
    return array.size % 2 == 1 ? array[m_pos] : mean(array[m_pos-1..m_pos])
end
Hexapody answered 4/2, 2020 at 16:47 Comment(0)
P
0

There are many ways to do this, but for both performance and reliability, I suggest using the enumerable-statistics library created by Ruby committer mrkn.

https://github.com/mrkn/enumerable-statistics

require 'enumerable/statistics'

ary = [1,2,3,3,4]

ary.mean   # => 2.6
ary.median # => 3 
Plowboy answered 28/5, 2022 at 8:38 Comment(0)
G
-2

I think it's good:

#!/usr/bin/env ruby

#in-the-middle value when odd or
#first of second half when even.
def median(ary)
  middle = ary.size/2
  sorted = ary.sort_by{ |a| a }
  sorted[middle]
end

or

#in-the-middle value when odd or
#average of 2 middle when even.
def median(ary)
  middle = ary.size/2
  sorted = ary.sort_by{ |a| a }
  ary.size.odd? ? sorted[middle] : (sorted[middle]+sorted[middle-1])/2.0
end

I used sort_by rather than sort because it's faster: Sorting an array in descending order in Ruby.

Gebelein answered 23/9, 2013 at 9:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.