28

I want to find the min and max elements of an array using for comprehension. Is it possible to do that with one iteration of array to find both min element and max element?

I am looking for a solution without using scala provided array.min or max.

Rajeev
  • 4,762
  • 8
  • 41
  • 63
  • I would start with this link, some comments may be very helpful [link](http://stackoverflow.com/questions/424800/what-is-the-best-way-to-get-the-minimum-or-maximum-value-from-an-array-of-number) – Fabian Nov 29 '13 at 11:54
  • Hey i don't want to use min function... I am looking for solution without using min function – Rajeev Nov 29 '13 at 11:56
  • I think I met all your criteria, @Rajeev, especially if processing a huge list. See my answer below for a "Scala idiomatic" solution: https://stackoverflow.com/a/55959734/501113 – chaotic3quilibrium May 02 '19 at 21:09

10 Answers10

61

You can get min and max values of an Array[Int] with reduceLeft function.

scala> val a = Array(20, 12, 6, 15, 2, 9)

a: Array[Int] = Array(20, 12, 6, 15, 2, 9)

scala> a.reduceLeft(_ min _)

res: Int = 2

scala> a.reduceLeft(_ max _)

res: Int = 20

See this link for more information and examples of reduceLeft method: http://alvinalexander.com/scala/scala-reduceleft-examples

Henri Hietala
  • 3,021
  • 1
  • 20
  • 27
  • This seems a concise/idiomatic approach – WestCoastProjects Jan 24 '15 at 22:58
  • 2
    This answer is ***INCORRECT***! If you carefully read the original question, the poster asked IN A SINGLE PASS how to collect both the min and the max. This answer is showing how to do it in TWO PASSES, which is EXACTLY what the original poster was trying to avoid. – chaotic3quilibrium May 02 '19 at 20:03
  • The single pass part of the original poster's request matters if processing a huge list. Additionally, this solution is not generic. See my answer below for a "Scala idiomatic" solution: https://stackoverflow.com/a/55959734/501113 – chaotic3quilibrium May 02 '19 at 20:59
30

Here is a concise and readable solution, that avoids the ugly if statements :

def minMax(a: Array[Int]) : (Int, Int) = {
  if (a.isEmpty) throw new java.lang.UnsupportedOperationException("array is empty")
  a.foldLeft((a(0), a(0)))
  { case ((min, max), e) => (math.min(min, e), math.max(max, e))}
}

Explanation : foldLeft is a standard method in Scala on many collections. It allows to pass an accumulator to a callback function that will be called for each element of the array.

Take a look at scaladoc for further details

Yann Moisan
  • 8,161
  • 8
  • 47
  • 91
  • Can you add some description to your solution regarding how foldLeft and e works – Rajeev Nov 29 '13 at 12:29
  • Probably IAE? UnsupportedOperationException is used for quite different things (when the whole method is unsupported as a type, e.g. remove method for immutable collection). This way you can archive even more concisement using `require(a.nonEmpty, "array is empty")` instead of if. – om-nom-nom Nov 29 '13 at 13:49
  • 1
    @om-nom-nom I chose UOE to be consistent with current API (method `min` throws an UOE when array is empty). – Yann Moisan Nov 29 '13 at 16:48
  • 4
    wait, `if`, a word so clear that you don't need anything else than an english dictionary to understand is 'ugly', but a `foldLeft` with a closure, pattern matching and function calls to the math library is better?? I love the functional paradigm, but there is so much cool-aid I'm willing to drink ;-) – Daniel Langdon May 20 '15 at 17:48
  • This solution will perform extraneous comparisons. This matters if processing a huge list. When the min is to be replaced, there is no reason to perform the max comparison. Additionally, this solution is not generic. See my answer below for a "Scala idiomatic" solution: https://stackoverflow.com/a/55959734/501113 – chaotic3quilibrium May 02 '19 at 20:58
7
def findMinAndMax(array: Array[Int]) = { // a non-empty array

    val initial = (array.head, array.head)  // a tuple representing min-max


    // foldLeft takes an initial value of type of result, in this case a tuple
    // foldLeft also takes a function of 2 parameters.
    // the 'left' parameter is an accumulator (foldLeft -> accum is left)
    // the other parameter is a value from the collection.

    // the function2 should return a value which replaces accumulator (in next iteration)
    // when the next value from collection will be picked.

    // so on till all values are iterated, in the end accum is returned.

    array.foldLeft(initial) { ((min, max), x) => 
          if (x < min) (x, max) 
          else if (x > max) (min, x) 
          else acc 
    }
}
Shyamendra Solanki
  • 8,751
  • 2
  • 31
  • 25
  • Can you give some explanation about your code. I am starter in scala. Feeling difficult to understand how foldLeft works – Rajeev Nov 29 '13 at 12:27
  • I agree that the implementation using the acc._1 makes it confusing to read. At least this solution doesn't perform extraneous comparisons which matters if processing a huge list. Additionally, this solution is not generic. See my answer below for a "Scala idiomatic" solution: https://stackoverflow.com/a/55959734/501113 – chaotic3quilibrium May 02 '19 at 21:01
5

Following on from the other answers - a more general solution is possible, that works for other collections as well as Array, and other contents as well as Int:

def minmax[B >: A, A](xs: Iterable[A])(implicit cmp: Ordering[B]): (A, A) = {
  if (xs.isEmpty) throw new UnsupportedOperationException("empty.minmax")

  val initial = (xs.head, xs.head)

  xs.foldLeft(initial) { case ((min, max), x) => 
    (if (cmp.lt(x, min)) x else min, if (cmp.gt(x, max)) x else max) }
}                                               

For example:

minmax(List(4, 3, 1, 2, 5))              //> res0: (Int, Int) = (1,5)

minmax(Vector('Z', 'K', 'B', 'A'))       //> res1: (Char, Char) = (A,Z)

minmax(Array(3.0, 2.0, 1.0))             //> res2: (Double, Double) = (1.0,3.0)

(It's also possible to write this a bit more concisely using cmp.min() and cmp.max(), but only if you remove the B >: A type bound, which makes the function less general).

DNA
  • 42,007
  • 12
  • 107
  • 146
  • This solution will perform extraneous comparisons. This matters if processing a huge list. When the min is to be replaced, there is no reason to perform the max comparison. See my answer below for an improved "Scala idiomatic" solution: https://stackoverflow.com/a/55959734/501113 – chaotic3quilibrium May 02 '19 at 21:03
5

Consider this (for non-empty orderable arrays),

val ys = xs.sorted
val (min,max) = (ys.head, ys.last)
elm
  • 20,117
  • 14
  • 67
  • 113
  • 10
    Inefficient for large arrays. All that's needed is min and max, there's no need (and thus wasteful) to sort all the other elements. – Steve Kuo Sep 29 '17 at 21:43
  • First, we need to sort, and then we need to select head and last. makes computationally expensice. not suitable for big arrays and lists. – Nikhil G Jan 25 '21 at 06:10
4
val xs: Array[Int] = ???

var min: Int = Int.MaxValue
var max: Int = Int.MinValue

for (x <- xs) {
  if (x < min) min = x
  if (x > max) max = x
}
Jesper
  • 202,709
  • 46
  • 318
  • 350
  • 1
    This solution is not idiomatic Scala AT ALL. And this solution will also perform extraneous comparisons. This matters if processing a huge list. When the min is to be replaced, there is no reason to perform the max comparison. Additionally, this solution is not generic. See my answer below for a "Scala idiomatic" solution: https://stackoverflow.com/a/55959734/501113 – chaotic3quilibrium May 02 '19 at 21:02
  • Looks like 'C' translated to scala. `var`'s? – WestCoastProjects Dec 28 '20 at 19:56
3

I'm super late to the party on this one, but I'm new to Scala and thought I'd contribute anyway. Here is a solution using tail recursion:

@tailrec
def max(list: List[Int], currentMax: Int = Int.MinValue): Int = {
    if(list.isEmpty) currentMax
    else if ( list.head > currentMax) max(list.tail, list.head)
    else max(list.tail,currentMax)
}
jlents
  • 790
  • 8
  • 20
  • You appeared to have not read the original poster's request. Where is the min part of your solution? Additionally, this solution is not generic. See my answer below for a "Scala idiomatic" solution: https://stackoverflow.com/a/55959734/501113 – chaotic3quilibrium May 02 '19 at 21:05
  • The foldLeft in the collections effectively provides a solution implemented with "tail recursion". – chaotic3quilibrium May 04 '19 at 11:01
2

Of all of the answers I reviewed to this questions, DNA's solution was the closest to "Scala idiomatic" I could find. However, it can be slightly improved by...:

  1. Performing as few comparisons as needed (important for very large collections)
  2. Provide ideal ordering consistency by only using the Ordering.lt method
  3. Avoiding throwing an Exception
  4. Making the code more readable for those new to and learning Scala

The comments should help clarify the changes.

def minAndMax[B>: A, A](iterable: Iterable[A])(implicit ordering: Ordering[B]): Option[(A, A)] =
  if (iterable.nonEmpty)
    Some(
      iterable.foldLeft((iterable.head, iterable.head)) {
        case (minAndMaxTuple, element) =>
          val (min, max) =
            minAndMaxTuple //decode reference to tuple
          if (ordering.lt(element, min))
            (element, max) //if replacing min, it isn't possible max will change so no need for the max comparison
          else
            if (ordering.lt(max, element))
              (min, element)
            else
              minAndMaxTuple //use original reference to avoid instantiating a new tuple
      }
    )
  else
    None

And here's the solution expanded to return the lower and upper bounds of a 2d space in a single pass, again using the above optimizations:

def minAndMax2d[B >: A, A](iterable: Iterable[(A, A)])(implicit ordering: Ordering[B]): Option[((A, A), (A, A))] =
  if (iterable.nonEmpty)
    Some(
      iterable.foldLeft(((iterable.head._1, iterable.head._1), (iterable.head._2, iterable.head._2))) {
        case ((minAndMaxTupleX, minAndMaxTupleY), (elementX, elementY)) =>
          val ((minX, maxX), (minY, maxY)) =
            (minAndMaxTupleX, minAndMaxTupleY) //decode reference to tuple
          (
              if (ordering.lt(elementX, minX))
                (elementX, maxX) //if replacing minX, it isn't possible maxX will change so no need for the maxX comparison
              else
                if (ordering.lt(maxX, elementX))
                  (minX, elementX)
                else
                  minAndMaxTupleX //use original reference to avoid instantiating a new tuple
            , if (ordering.lt(elementY, minY))
                (elementY, maxY) //if replacing minY, it isn't possible maxY will change so no need for the maxY comparison
              else
                if (ordering.lt(maxY, elementY))
                  (minY, elementY)
                else
                  minAndMaxTupleY //use original reference to avoid instantiating a new tuple
          )
      }
    )
  else
    None
chaotic3quilibrium
  • 5,661
  • 8
  • 53
  • 86
0

You could always write your own foldLeft function - that will guarantee one iteration and known performance.

val array = Array(3,4,62,8,9,2,1)
if(array.isEmpty) throw new IllegalArgumentException // Just so we can safely call array.head

val (minimum, maximum) = array.foldLeft((array.head, array.head)) {  // We start of with the first element as min and max
  case ((min, max), next) =>
    if(next > max) (min, next)
    else if(next < min) (next, max)
    else (min, max)
}

println(minimum, maximum) //1, 62
Akos Krivachy
  • 4,915
  • 21
  • 28
  • At least this solution does not perform extraneous comparisons. However, it is re-instantiating the `(min, max)` tuple if neither the min nor the max have changed (which is the most traversed code pathway). This matters if processing a huge list. Additionally, this solution is not generic. See my answer below for a "Scala idiomatic" solution: https://stackoverflow.com/a/55959734/501113 – chaotic3quilibrium May 02 '19 at 21:07
0
scala> val v = Vector(1,2)
scala> v.max
res0: Int = 2
scala> v.min
res1: Int = 2

You could use the min and max methods of Vector

Vivek
  • 320
  • 1
  • 5
  • Welcome to StackOverflow. When answering a question this old (asked over 6 years ago) you have to ask yourself why such a simple solution isn't mentioned in any of the other 9 answers. Reread the question. It specifically requests a single pass of the collection (your answer requires 2 passes) and that it should be **without** using `.min` and `.max`. – jwvh May 14 '20 at 05:09