Reputation: 45
I am a Scala newbie. I tried turkish citizenship number validation algorithm.
How do I implement and optimize this scala code?
You can find my java version this link https://gist.github.com/hasanozgan/5601623
trait TurkishCitizenshipNumberValidator {
private def odd(tckn: String): Int = {
tckn.zipWithIndex.foldLeft(0) {
(total, x) =>
x match {
case i if ((i._2 % 2 == 0 && i._2 < 10)) => ((i._1.asDigit) + total)
case _ => total
}
}
}
private def even(tckn: String): Int = {
tckn.zipWithIndex.foldLeft(0) {
(total, x) =>
x match {
case i if ((i._2 % 2 == 1) && i._2 < 9) => ((i._1.asDigit) + total)
case _ => total
}
}
}
private def total(tckn: String): Int = {
tckn.zipWithIndex.foldLeft(0) {
(total, x) =>
x match {
case i if (i._2 < 10) => ((i._1.asDigit) + total)
case _ => total
}
}
}
def turkishCitizenshipNumberValidator(t: String): Boolean = {
val digit10 = total(t) % 10
val digit9 = ((odd(t) * 7) - even(t)) % 10
((t(9).asDigit == digit9) && t(10).asDigit == digit10)
}
}
object test extends TurkishCitizenshipNumberValidator {
// http://tckimliknouretici.appspot.com/
turkishCitizenshipNumberValidator("29419391592")
//> res0: Boolean = true
}
Upvotes: 2
Views: 255
Reputation: 1725
With problems like this where a string needs to be deconstructed and then processed it is sometimes easier to zip the pattern to the string and then use the usual collection methods to do the processing.
In this specific case, the pattern is a simple even-odd pattern, so zipping the Citizenship number to a string of alternating characters, one-two's, is the place to start, like so...
def turkishCitizenshipNumberValidator( digits:String ) = { val pattern = Stream.continually("12".toStream).flatten // An odd & evens pattern val d = digits. filter(_.isDigit). // Filter out the non-digits map(_.asDigit). // Convert Char to Int zip(pattern) // Zip to a repeating string of one-two's, for odd & evens val odds = d.take(9).filter( _._2 == '1' ).map( _._1 ).sum val evens = d.take(8).filter( _._2 == '2' ).map( _._1 ).sum val total = d.take(10).map( _._1 ).sum d.size == 11 && (odds * 7 - evens) % 10 == d(9)._1 && total % 10 == d(10)._1 } turkishCitizenshipNumberValidator("29419391592")
Upvotes: 0
Reputation: 167891
If you want it both compact and clear, and need basic input validation (right length, things are digits), I'd
def turkishCitizenshipNumberValidator(t: String): Boolean = {
if (t.length != 11 || !t.forall(_.isDigit)) false
else {
val n = t.map(_.asDigit)
val evens = n.grouped(2).take(5).map(_(0)).sum
val odds = n.grouped(2).take(4).map(_(1)).sum
n(10) == (n.take(10).sum % 10) && n(9) == ((odds*7 - evens) % 10)
}
}
The keys here are using grouped
to pull the string apart in even-odd pairs, and mapping digits to numbers at the start so it's not too much of a headache to work with them.
Edit: If you'd rather obfuscate your code, try this!
def tCNV(t: String) = t.map(_.asDigit).foldLeft(Seq(0,0,0)){ (v,n) => v(2) match {
case 10 => Seq(v(0)-n,v(1),0); case 9 => Seq(v(0)+n,v(1)-n,10)
case i => Seq(v(0)+n, v(1)+n*(7*(i%2)+(i%2-1)), i+1)
}}.take(2).map(_%10).forall(_ == 0)
Upvotes: 2
Reputation: 22461
How about this?
def turkishCitizenshipNumberValidator(t: String): Boolean = {
val digits = t.init.map(_.asDigit)
val digit10 = digits.sum % 10
val (odd, even) = digits.zipWithIndex.partition(_._2 % 2 == 0)
val digit9 = ((odd.map(_._1).sum * 7) - even.init.map(_._1).sum) % 10
((t(9).asDigit == digit9) && t(10).asDigit == digit10)
}
Upvotes: 1
Reputation: 67280
Well, you have three times the same body with a minor difference. So you should factor out that body as one auxiliary method with an additional function argument to test the index:
private def check(tckn: String)(pred: Int => Boolean): Int = {
tckn.zipWithIndex.foldLeft(0) {
(total, x) =>
x match {
case i if pred(i._2) => ((i._1.asDigit) + total)
case _ => total
}
}
}
private def odd(tckn: String): Int = check(tckn)(i => i % 2 == 0 && i < 10)
etc.
Secondly, you can simplify the check
code a bit, by extracting the tuple of character and index, and you don't need a pattern match if you just have one guard, as a simple if
will do (more a matter of taste, though).
private def check(tckn: String)(pred: Int => Boolean): Int =
tckn.zipWithIndex.foldLeft(0) { case (total, (char, idx)) =>
if (pred(idx)) char.asDigit + total else total
}
Upvotes: 1