Given an array, find any three numbers which sum to zero.
This problem is a continuation of a code kata in Python . I'm learning Scala and would like feedback on more efficient, cleaner, and functional ways of solving (and unit testing) this problem.
SumToZero.scala
class SumToZeroNotFound(msg:String=null, cause:Throwable=null)
extends java.lang.Exception (msg, cause) {}
class SumToZero{
def process(arr: Array[Int], size: Int = 3): Array[Int] = {
for(combination <- arr.combinations(size))
if(combination.sum == 0) //note: can yield combinations here if we want to find all combinations that sum to zero
return combination
throw new SumToZeroNotFound()
}
}
TestSumToZero.scala
import org.junit.runner.RunWith
import org.scalatest.junit._
import org.scalatest._
import scala.runtime.ScalaRunTime.stringOf
@RunWith(classOf[JUnitRunner])
class TestSumToZero extends FunSuite {
test("should return entire array that sums to zero") {
val stz = new SumToZero
val res = stz.process(Array(1,2,-3))
val str_res = stringOf(res)
assert( str_res == "Array(1, 2, -3)",
"res: " + str_res)
}
test("should raise NotFound exception if no combination sums to zero"){
val stz = new SumToZero
intercept[SumToZeroNotFound]{
stz.process(Array(1,2,3,4))
}
}
test("should return first set of nums that sum to zero"){
val stz = new SumToZero
val res = stz.process(Array(125, 124, -100, -25, 1, 2, -3, 10, 100))
val str_res = stringOf(res)
assert( str_res == "Array(125, -100, -25)",
"res: " + str_res)
}
}
Updated SumToZero.scala
class SumToZeroNotFound(msg:String=null, cause:Throwable=null)
extends java.lang.Exception (msg, cause) {}
trait SumToZero{
@throws(classOf[SumToZeroNotFound])
def process(arr: Array[Int], size: Int = 3): Array[Int]
}
object SumToZeroBruteForce extends SumToZero{
//brute force process any size combination
override def process(arr: Array[Int], size: Int = 3): Array[Int] = {
arr.combinations(size).find(_.sum == 0).getOrElse(throw new SumToZeroNotFound)
}
}
object SumToZeroBinSearch extends SumToZero{
//optimized with sorting and binary search
//scala does not seem to have a native binary search routine
implicit private class Search(val arr: Array[Int]){
def binSearch(target: Int) = {
java.util.Arrays.binarySearch(arr.asInstanceOf[Array[Int]], target)
}
}
@throws(classOf[IllegalArgumentException])
override def process(arr: Array[Int], size: Int = 3): Array[Int] = size match {
case 3 =>
val sorted_arr = arr.sortWith(_<_)
val max_len = sorted_arr.length
for((i_val, i) <- sorted_arr.takeWhile(_ <= 0).zipWithIndex ){
val maxj = sorted_arr(max_len - 1) - i_val
for(j_val <- sorted_arr.slice(i+1, max_len).takeWhile(_ <= maxj)){
val temp_sum = i_val + j_val
val res_idx = sorted_arr.binSearch(-temp_sum)
if (res_idx > -1) {
return Array(i_val, j_val, sorted_arr(res_idx))
}
}
}
throw new SumToZeroNotFound
case _ => throw new IllegalArgumentException("only support size of three for now")
}
}
object SumToZero{
def apply(impl: String) = impl match{
case "with_bin_search" => SumToZeroBinSearch
case _ => SumToZeroBruteForce
}
}
Updated TestSumToZero
import org.junit.runner.RunWith
import org.scalatest.junit._
import org.scalatest._
import scala.runtime.ScalaRunTime.stringOf
@RunWith(classOf[JUnitRunner])
class TestSumToZero extends FunSuite{
final val methods = Array("brute_force", "with_bin_search")
def testAllMethods(arr: Array[Int], expected_res: String = "",
methods: Array[String] = this.methods) = {
for (method <- methods){
println("testing: " + method)
val stz = SumToZero(method)
val res = stz.process(arr)
val str_res = stringOf(res.sortWith(_<_))
assert( str_res == expected_res,
"res: " + str_res)
}
}
test("should return entire array that sums to zero") {
testAllMethods(Array(1,2,-3), "Array(-3, 1, 2)")
}
test("should raise NotFound exception if no combination sums to zero"){
this.methods.foreach(method =>
intercept[SumToZeroNotFound]{
testAllMethods(Array(1,2,3), "", Array(method))
})
}
test("should return first set of nums that sum to zero"){
testAllMethods(Array(125, 124, -100, -25, 1, 2, -3, 10, 100), "Array(-100, -25, 125)")
}
}
I'd like to use Scala spec for unit testing, but was not able to get it working with Gradle. Please comment if you are able to build Scala projects with Gradle and produce JUnit-style output.