I an a newcomer in the Scala world, and I would like some advice from more experienced people to find out whether what I am writing goes in the direction of idiomatic code.
In particular, I have to write a simple algorithm that will do the following. We are given an integer number of, say, candies to be distributed, and a list of proportions, which are also integers. We should split the candies into groups, such that the proportions are respected as accurately as possible, given that a single candy cannot be broken up.
For instance, given 17
candies to distribute in proportions of (3, 2, 5)
we should obtain (5, 4, 8)
.
Here is my code, including a quick ScalaCheck test.
import org.scalacheck.Prop._
import org.scalacheck.Gen
object Distribution {
private class IntWithDivision(val self: Int) {
def @/(other: Int): Float = self.toFloat / other
}
private implicit def divisibleInt(int: Int) = new IntWithDivision(int)
def distribute(amount: Int, proportions: Seq[Int]): Seq[Int] = {
val sum = proportions.sum
val byDifect = proportions map { x => (amount * x @/ sum).toInt }
val approximations = (byDifect, proportions).zipped map { (x, y) => x @/ y }
val lowValuesIndices = (
approximations.view.zipWithIndex
sortBy { _._1 }
take (amount - byDifect.sum)
map { _._2 }
).force
val remainders = (
approximations.view.zipWithIndex
map { x => if (lowValuesIndices contains x._2) 1 else 0 }
).force
(byDifect, remainders).zipped map { _ + _ }
}
def main(args: Array[String]) {
val pos = Gen.choose(1, 50000)
val posList = Gen.containerOf[List, Int](pos)
val propSum = forAll(pos, posList) { (amount: Int, proportions: List[Int]) =>
(proportions.size > 0) ==> (amount == distribute(amount, proportions).sum)
}
propSum.check
}
}
In particular, I would like advice on the following points:
- The algorithm goes over the list of proportions and derived lists a few times. I would like to use more laziness, but using
proportions.view
at the beginning and then returning aforce
gives a type error. - The code is more convoluted than I would like. I think it may be simplified knowing the Collections API better.
Any help or suggestion is welcomed.
assignedCandies/assignedProportion
is lowest, and repeat. It is easy to see that in this way, the no kid will get more than one leftover, so I directly compute those kids having the lowest value and assing one candy to all of them. \$\endgroup\$