Question

So, while answering some other question I stumbled upon the necessity of computing the median of 5. Now, there's a similar question in another language, but I want a Scala algorithm for it, and I'm not sure I'm happy with mine.

Was it helpful?

Solution

Here's an immutable Scala version that has the minimum number of compares (6) and doesn't look too ugly:

def med5(five: (Int,Int,Int,Int,Int)) = {

  // Return a sorted tuple (one compare)
  def order(a: Int, b: Int) = if (a<b) (a,b) else (b,a)

  // Given two self-sorted pairs, pick the 2nd of 4 (two compares)
  def pairs(p: (Int,Int), q: (Int,Int)) = {
    (if (p._1 < q._1) order(p._2,q._1) else order(q._2,p._1))._1
  }

  // Strategy is to throw away smallest or second smallest, leaving two self-sorted pairs
  val ltwo = order(five._1,five._2)
  val rtwo = order(five._4,five._5)
  if (ltwo._1 < rtwo._1) pairs(rtwo,order(ltwo._2,five._3))
  else pairs(ltwo,order(rtwo._2,five._3))
}

Edit: As requested by Daniel, here's a modification to work with all sizes, and in arrays so it should be efficient. I can't make it pretty, so efficiency is the next best thing. (>200M medians/sec with a pre-allocated array of 5, which is slightly more than 100x faster than Daniel's version, and 8x faster than my immutable version above (for lengths of 5)).

def med5b(five: Array[Int]): Int = {

  def order2(a: Array[Int], i: Int, j: Int) = {
    if (a(i)>a(j)) { val t = a(i); a(i) = a(j); a(j) = t }
  }

  def pairs(a: Array[Int], i: Int, j: Int, k: Int, l: Int) = {
    if (a(i)<a(k)) { order2(a,j,k); a(j) }
    else { order2(a,i,l); a(i) }
  }

  if (five.length < 2) return five(0)
  order2(five,0,1)
  if (five.length < 4) return (
    if (five.length==2 || five(2) < five(0)) five(0)
    else if (five(2) > five(1)) five(1)
    else five(2)
  )
  order2(five,2,3)
  if (five.length < 5) pairs(five,0,1,2,3)
  else if (five(0) < five(2)) { order2(five,1,4); pairs(five,1,4,2,3) }
  else { order2(five,3,4); pairs(five,0,1,3,4) }
}

OTHER TIPS

Jeez, way to over-think it, guys.

def med5(a : Int, b: Int, c : Int, d : Int, e : Int) = 
   List(a, b, c, d, e).sort(_ > _)(2)

As suggested, here's my own algorithm:

def medianUpTo5(arr: Array[Double]): Double = {
    def oneAndOrderedPair(a: Double, smaller: Double, bigger: Double): Double =
        if (bigger < a) bigger
        else if (a < smaller) smaller else a

    def partialOrder(a: Double, b: Double, c: Double, d: Double) = {
        val (s1, b1) = if (a < b) (a, b) else (b, a)
        val (s2, b2) = if (c < d) (c, d) else (d, c)
        (s1, b1, s2, b2)
    }

    def medianOf4(a: Double, b: Double, c: Double, d: Double): Double = {
        val (s1, b1, s2, b2) = partialOrder(a, b, c, d)
        if (b1 < b2) oneAndOrderedPair(s2, s1, b1)
        else oneAndOrderedPair(s1, s2, b2)
    }

    arr match {
        case Array(a)       => a
        case Array(a, b)    => a min b
        case Array(a, b, c) =>
            if (a < b) oneAndOrderedPair(c, a, b) 
            else oneAndOrderedPair(c, b, a)
        case Array(a, b, c, d) => medianOf4(a, b, c, d)
        case Array(a, b, c, d, e) =>
            val (s1, b1, s2, b2) = partialOrder(a, b, c, d)
            if (s1 < s2) medianOf4(e, b1, s2, b2)
            else medianOf4(e, b2, s1, b1)
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top