This is another question from Scala for the Impatient
which says
Write a function lteqgt(values: Array[Int], v: Int) that returns a triple containing the counts of values less than v, equal to v, and greater than v.
The way I have done that is
scala> def lteqgt(values: Array[Int], v: Int): (Int, Int, Int) = (values.count(_ < v), values.count(_ == v), values.count(_ > v))
lteqgt: (values: Array[Int], v: Int)(Int, Int, Int)
scala> lteqgt(Array(0,0,1,1,1,2,2), 1)
res47: (Int, Int, Int) = (2,3,2)
Problem?
I am traversing the array 3
times to collect the counts, Is there a way to collect the values in first go? an idiomatic way?
This is perfect case to use foldLeft
. It will traverse your collection exactly once, without creating another collection (as groupBy
does) and it's more concise comparing to the more generic aggregate
.
def lteqgt(values: Array[Int], v: Int): (Int, Int, Int) =
values.foldLeft((0, 0, 0)) {
case ((lt, eq, gt), el) =>
if (el < v) (lt + 1, eq, gt)
else if (el == v) (lt, eq + 1, gt)
else (lt, eq, gt + 1)
}
If you want to achieve ultimate efficiency, while avoiding imperative approach, then tail-recursion is the way to go:
def lteqgt(values: Array[Int], v: Int): (Int, Int, Int) = {
def rec(i:Int, lt:Int, eq:Int, gt:Int):(Int, Int, Int) =
if (i == values.length) (lt, eq, gt)
else if (values(i) < v) rec(i + 1, lt + 1, eq, gt)
else if (values(i) == v) rec(i + 1, lt, eq + 1, gt)
else rec(i + 1, lt, eq, gt + 1)
rec(0, 0, 0, 0)
}
This avoids construction Tuple
and boxed Ints on each iteration. The whole thing compiles to while
loop in java (here is the transpiled output if you are interested).
While a functional solution is arguably more elegant, let's not forget the "boring" but efficient imperative solution.
def lteqgt(values: Array[Int], v: Int): (Int, Int, Int) = {
var lt = 0
var eq = 0
var gt = 0
values.foreach (i => {
if (i<v) lt += 1
else if (i==v) eq += 1
else gt += 1
})
(lt,eq,gt)
}
The above is likely to generate one function call per loop, as Aivean points out below. For more efficiency, we can remove the closure manually. (It's a pity that such optimization is not yet done by the compiler, though)
def lteqgt(values: Array[Int], v: Int): (Int, Int, Int) = {
var lt = 0
var eq = 0
var gt = 0
var i = 0
while (i < values.length) {
if (values(i) < v ) lt += 1
else if (values(i) == v) eq += 1
else gt += 1
i += 1
}
(lt,eq,gt)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With