2017年9月22日

[演算法] 計算平均、中位數、眾數(Mean Median Mode)

此系列筆記主要依照 [Udemy] Learning Algorithms in JavaScript from Scratch by Eric Traub 的課程脈絡加以整理,但部分程式碼是消化後以自己較易理解的方式重新撰寫,因此和原課程內容有些出入。

問題描述

當我們傳入一個全都是數值的陣列時,計算這些數值的 加總/總合(sum)、平均數(mean)、中位數(Median)和眾數(Mode),並以物件的方式回傳:
function meanMedianMode (arr) {
  // call other three function
  // return obj which has mean, median, mode in it
}

function getMean (arr) {...}
function getMedian (arr) {...}
function getMode (arr) {...}

演算法實做

平均數(Mean)

計算平均數之前,我們要先把所有數字加總,加總的方式我們可以使用 Array.prototype.reduce 這個方法:
let sum = arr.reduce((acc, cur) => acc + cur)
接著在除以所有的陣列數目 sum / arr.length ,最後我們可以透過 Math.round() 將輸出的結果進行四捨五入:
function getMean (arr) {
  let sum = arr.reduce((acc, cur) => acc + cur)
  let mean = sum / arr.length
  return Math.round(mean * 100) / 100  // 取到小數第二位
}

中位數(Median)

中位數會有兩種情況,當輸入陣列的個數是奇數時,就取最中間的那個;當輸入的數目是偶數時,則要取最中間的那兩個加總除以二。
當數目是偶數時中數會是:
median = (arr[arr.length / 2] + arr[arr.length / 2 - 1]) / 2 
這裡要注意的是,因為陣列裡面是使用 index,所以是 arr.length / 2 - 1 而不是 +1
當數目是奇數時,中數會是:
median = arr[(arr.length - 1) / 2 ]
綜合起來:
function getMedian (arr) {
  arr = arr.sort((a, b) => a - b)
  let median
  if (arr.length % 2 === 0) {
    // 數目為偶數
    median = (arr[arr.length / 2] + arr[arr.length / 2 - 1]) / 2
  } else {
    // 數目為奇數
    median = arr[(arr.length - 1) / 2 ]
  }
  return median
}

眾數(mode)

最後一個稍稍複雜的是計算眾數,這裡我們會用到在先前 Harmless Ransom Note 計算陣列中每個元素出現幾次的方法。
我們先計算在陣列中每一個元素出現幾次:
function getMode (arr) {
  let countList = {}
  for (let value of arr) {
    // 將陣列中的 Number 當作 String 來處理,以計算出現次數
    value = value.toString()
    if (!countList[value]) countList[value] = 0
    countList[value]++
  }
  /* ... */
}
接著我們要根據這個 countList 找出被計數最多的元素:
function getMode (arr) {
  /* ... */
  let maxCount = 0
  let mode = []        // 眾數
  for (let prop in countList) {
    if (maxCount < countList[prop]) {
      maxCount = countList[prop]
      mode = [prop]
    } else if (maxCount === countList[prop]) {
      // 如果有同樣數目的眾數
      mode.push(prop)
    }
  }
  
  // 如果每個元素的計數一樣,則沒有眾數
  if (mode.length === Object.keys(countList).length) {
    mode = []
  }
  
  return mode
}

meanMedianMode

最後我們多一個小小的判斷,如果輸入的數值包含不是數值的話,則給予警告:
function meanMedianMode (arr) {
  arr.forEach(item => {
    if (!(/^[0-9]+$/.test(item))) {
      console.warn(item + ' is not a number.')
    }
  })
  
  return {
    mean: getMean(arr),
    median: getMedian(arr),
    mode: getMode(arr)
  }
}

完整程式碼

function meanMedianMode (arr) {
  arr.forEach(item => {
    if (!(/^[0-9]+$/.test(item))) {
       console.warn(item + ' is not a number.')
    }
  })
  
  return {
    mean: getMean(arr),
    median: getMedian(arr),
    mode: getMode(arr)
  }
}


/**
 * 計算加總與平均數
**/
function getMean (arr) {
  let sum = arr.reduce((acc, cur) => acc + cur)
  let mean = sum / arr.length
  return Math.round(mean * 100) / 100      // 取到小數第二位
}

/**
 * 計算中位數
**/
function getMedian (arr) {
  arr = arr.sort((a, b) => a - b)
  let median
  if (arr.length % 2 === 0) {
    // 數目為偶數
    median = (arr[arr.length / 2] + arr[arr.length / 2 - 1]) / 2
  } else {
    // 數目為奇數
    median = arr[(arr.length - 1) / 2 ]
  }
  return median
}

/**
 * 計算眾數
**/
function getMode (arr) {
  let countList = {}
  for (let value of arr) {
    value = value.toString()
    if (!countList[value]) countList[value] = 0
    countList[value]++
  }
  let maxCount = 0
  let mode = []
  for (let prop in countList) {
    if (maxCount < countList[prop]) {
      maxCount = countList[prop]
      mode = [prop]
    } else if (maxCount === countList[prop]) {
      mode.push(prop)
    }
  }
  
  if (mode.length === Object.keys(countList).length) {
    mode = []
  }
  
  return mode
}

console.log(meanMedianMode([1, 2, 3, 4, 5, 4, 6, 1]))     // { mean: 3.25, median: 3.5, mode: [ '1', '4' ] }
console.log(meanMedianMode([9, 10, 23, 10 ,23, 9]))       // { mean: 14, median: 10, mode: [] }

資料來源

0 意見:

張貼留言