Hasan Ozgan
Hasan Ozgan

Reputation: 45

How do I short this scala code?

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

Answers (4)

Keith Pinson
Keith Pinson

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

Rex Kerr
Rex Kerr

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

Anthony Accioly
Anthony Accioly

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

0__
0__

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

Related Questions