29

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.

3
  • I would start with this link, some comments may be very helpful link Commented Nov 29, 2013 at 11:54
  • Hey i don't want to use min function... I am looking for solution without using min function Commented Nov 29, 2013 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: stackoverflow.com/a/55959734/501113 Commented May 2, 2019 at 21:09

10 Answers 10

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

Sign up to request clarification or add additional context in comments.

3 Comments

This seems a concise/idiomatic approach
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.
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: stackoverflow.com/a/55959734/501113
31

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

5 Comments

Can you add some description to your solution regarding how foldLeft and e works
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 I chose UOE to be consistent with current API (method min throws an UOE when array is empty).
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 ;-)
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: stackoverflow.com/a/55959734/501113
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 
    }
}

2 Comments

Can you give some explanation about your code. I am starter in scala. Feeling difficult to understand how foldLeft works
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: stackoverflow.com/a/55959734/501113
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).

1 Comment

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: stackoverflow.com/a/55959734/501113
5

Consider this (for non-empty orderable arrays),

val ys = xs.sorted
val (min,max) = (ys.head, ys.last)

2 Comments

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.
First, we need to sort, and then we need to select head and last. makes computationally expensice. not suitable for big arrays and lists.
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
}

2 Comments

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: stackoverflow.com/a/55959734/501113
Looks like 'C' translated to scala. var's?
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)
}

2 Comments

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: stackoverflow.com/a/55959734/501113
The foldLeft in the collections effectively provides a solution implemented with "tail recursion".
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

Comments

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

1 Comment

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: stackoverflow.com/a/55959734/501113
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

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.