4
\$\begingroup\$

I have a function that compares ZIP codes. It also has to handle cases where the input ZIP code be missing leading zeros, have extraneous leading zeros. This is what I've come up with. I don't have any issues with it. But since it's in Scala, I feel there must be a more functional way to write this.

My only functional thought (outside of the digit validation) is to insert them into a collection, then filter the collection by length != 5, and then attempt to fix them.

Here's my imperative, stateful version.

  def compareZip(first: String, second: String): Boolean = {
    var firstTmp: String = null
    var secondTmp: String = null
    if(!first.forall(_.isDigit) || !second.forall(_.isDigit)) false
    else if (first.length == ZIPCODE_LENGTH && first.length == second.length) {
      first equals second
    }
    else {
      if (first.length > ZIPCODE_LENGTH) {
        firstTmp = first takeRight ZIPCODE_LENGTH
      }
      else if (first.length < ZIPCODE_LENGTH) {
        firstTmp = ZERO_STR * (ZIPCODE_LENGTH - first.length) + first
      }
      else firstTmp = first

      if (second.length > ZIPCODE_LENGTH) {
        secondTmp = second takeRight ZIPCODE_LENGTH
      }
      else if (second.length < ZIPCODE_LENGTH) {
        secondTmp = ZERO_STR * (ZIPCODE_LENGTH - second.length) + second
      }
      else secondTmp = second
      firstTmp equals secondTmp
    }
  }
\$\endgroup\$
  • 1
    \$\begingroup\$ What about +4 zip codes? Do you need to handle those? \$\endgroup\$ – RubberDuck Aug 22 '17 at 19:51
  • \$\begingroup\$ Good question. No \$\endgroup\$ – stan Aug 26 '17 at 0:22
6
\$\begingroup\$

The title of your post is

Compare ZIP codes, disregarding leading zeros

The solution is simple -- just drop the leading zeros. Then you can compare the strings directly.

This leads us to something like this:

def compareZip(first: String, second: String): Boolean = {
  first.dropWhile(c => c == '0') == second.dropWhile(c => c =='0')
}

If you want, you can factor out the common lambdas.

There's no need to do anything more complicated than that. "Fixing" the zipcode only makes things more complicated. And, since the result never leaves the comparison function anyway, all that work is wasted.

\$\endgroup\$
  • \$\begingroup\$ While you're correct - that is what I said in the title, the description clearly states " It also has to handle cases where the input ZIP code be missing leading zeros, have extraneous leading zeros." \$\endgroup\$ – stan Aug 26 '17 at 0:23
  • \$\begingroup\$ @StanislavPalatnik, that changes nothing. Eliminating the leading zeros (extraneous or not) will allow for comparison with missing leading zeros. There is nothing in your description that contradicted your title. That was my point -- there's no reason to make it more complicated than that. \$\endgroup\$ – Nathan Davis Aug 26 '17 at 0:55
  • \$\begingroup\$ Ok I get it. Awesome. \$\endgroup\$ – stan Aug 26 '17 at 3:50
3
\$\begingroup\$

This would be my approach.

def compareZip(first: String, second: String, zipLen: Int ): Boolean = {
  val (zCode, pad) = 
    first.reverse.zipAll(second.reverse, '0', '0').splitAt(zipLen)

  zCode.forall { case (x,y) => x == y && x.isDigit } &&
    pad.forall { case (x,y) => x == y && x == '0' }
}

zipAll() will pad the shorter string to the length of the longer. Since the strings have been reversed the padding becomes leading zeros. The result is split() into the zip code part and the padding part. The zip characters have to match and have to be digit characters. The pad characters, if any, have to all be zeros.

\$\endgroup\$
1
\$\begingroup\$

The key to simplifying this is to reduce the special cases.

If you always left-pad with zeros, and always take ZIPCODE_LENGTH, then things are probably a bit slower, but the code clears up.

I'm not a huge Scala person so take this with a grain of salt, but it seems you can essentially simplify down to your only-digits check and a comparison of standardized zipcodes:

/** Returns the zipcode left-padded with zeros */
def standardizeZip(zipcode: String): List[Char] = {
  zipCode.reverse.toList.padTo(ZIPCODE_LENGTH, '0').take(ZIPCODE_LENGTH).reverse
}

def zipsEqual(a: String, b: String): Boolean = {
  a.forall(_.isDigit) &&
  b.forall(_.isDigit) &&
  (standardizeZip(a) equals standardizeZip(b))
}

You could probably use foldRight to do less reversing, but the strings/lists are quite short either way.

\$\endgroup\$
  • \$\begingroup\$ If I'm not mistaken, zipsEqual("912345","12345") should be false. \$\endgroup\$ – jwvh Aug 22 '17 at 21:40
  • \$\begingroup\$ @jwvh Not within the bounds of the original question statement, I think, but yeah, in a more robust solution. Upvoted, thanks for pointing that out. \$\endgroup\$ – BenC Aug 22 '17 at 21:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.