9

I came across this problem from CodeChef. The problem states the following:

A positive integer is called a palindrome if its representation in the decimal system is the same when read from left to right and from right to left. For a given positive integer K of not more than 1000000 digits, write the value of the smallest palindrome larger than K to output.

I can define a isPalindrome method as follows:

def isPalindrome(someNumber:String):Boolean = someNumber.reverse.mkString == someNumber

The problem that I am facing is how do I loop from the initial given number and break and return the first palindrome when the integer satisfies the isPalindrome method? Also, is there a better(efficient) way to write the isPalindrome method?

It will be great to get some guidance here

Timothy Jones
  • 21,495
  • 6
  • 60
  • 90
sc_ray
  • 7,803
  • 11
  • 63
  • 100

9 Answers9

5

If you have a number like 123xxx you know, that either xxx has to be below 321 - then the next palindrom is 123321.

Or xxx is above, then the 3 can't be kept, and 124421 has to be the next one.

Here is some code without guarantees, not very elegant, but the case of (multiple) Nines in the middle is a bit hairy (19992):

object Palindrome extends App {

def nextPalindrome (inNumber: String): String = {
  val len = inNumber.length ()
  if (len == 1 && inNumber (0) != '9') 
    "" + (inNumber.toInt + 1) else {
    val head = inNumber.substring (0, len/2)
    val tail = inNumber.reverse.substring (0, len/2)
    val h = if (head.length > 0) BigInt (head) else BigInt (0)
    val t = if (tail.length > 0) BigInt (tail) else BigInt (0)

    if (t < h) {
      if (len % 2 == 0) head + (head.reverse)
      else inNumber.substring (0, len/2 + 1) + (head.reverse)
    } else {
     if (len % 2 == 1) {
       val s2 = inNumber.substring (0, len/2 + 1) // 4=> 4
       val h2 = BigInt (s2) + 1  // 5 
       nextPalindrome (h2 + (List.fill (len/2) ('0').mkString)) // 5 + "" 
     } else {
       val h = BigInt (head) + 1
       h.toString + (h.toString.reverse)
     }
    }
  }
}

def check (in: String, expected: String) = {
  if (nextPalindrome (in) == expected) 
    println ("ok: " + in) else 
    println (" - fail: " + nextPalindrome (in) + " != " + expected + " for: " + in)
}
//
val nums = List (("12345", "12421"), // f
  ("123456", "124421"), 
  ("54321", "54345"), 
  ("654321", "654456"), 
  ("19992", "20002"),
  ("29991", "29992"),
  ("999", "1001"),
  ("31", "33"),
  ("13", "22"),
  ("9", "11"),
  ("99", "101"),
  ("131", "141"),
  ("3", "4")
)
nums.foreach (n => check (n._1, n._2))
println (nextPalindrome ("123456678901234564579898989891254392051039410809512345667890123456457989898989125439205103941080951234566789012345645798989898912543920510394108095"))

}

I guess it will handle the case of a one-million-digit-Int too.

user unknown
  • 35,537
  • 11
  • 75
  • 121
  • Yeah, that was what I was thinking of. – Eve Freeman Apr 23 '12 at 03:18
  • There is case on which this will fail: `check("999", "1001")` – incrop Apr 23 '12 at 06:12
  • A coma misses in your test cases after '"29992")' not cool for copy paste but I can't edit as it is only one character. To me, going to bigInts is not necessary as it will cost a lot and as necessary functions are easily implementable with strings. – Christopher Chiche Apr 23 '12 at 16:58
  • In your explanation, I don't agree that `12400421 has to be the next one`, it should be `124421`, there is no nead to include some zeros. – Christopher Chiche Apr 23 '12 at 17:04
  • @ChrisJamesC: You're right, it was just an error in the explanation. The code produces for `nextPalindrome ("123456") => res301: String = 124421` as expected. I corrected the prosa. Added comma, thanks. – user unknown Apr 23 '12 at 17:13
5

Doing reverse is not the greatest idea. It's better to start at the beginning and end of the string and iterate and compare element by element. You're wasting time copying the entire String and reversing it even in cases where the first and last element don't match. On something with a million digits, that's going to be a huge waste.

This is a few orders of magnitude faster than reverse for bigger numbers:

def isPalindrome2(someNumber:String):Boolean = {
  val len = someNumber.length;
  for(i <- 0 until len/2) {
    if(someNumber(i) != someNumber(len-i-1)) return false; 
  }
  return true;
}

There's probably even a faster method, based on mirroring the first half of the string. I'll see if I can get that now...

update So this should find the next palindrome in almost constant time. No loops. I just sort of scratched it out, so I'm sure it can be cleaned up.

def nextPalindrome(someNumber:String):String = {
  val len = someNumber.length;
  if(len==1) return "11";
  val half = scala.math.floor(len/2).toInt;
  var firstHalf = someNumber.substring(0,half);
  var secondHalf = if(len % 2 == 1) {
    someNumber.substring(half+1,len);
  } else {
    someNumber.substring(half,len);
  }

  if(BigInt(secondHalf) > BigInt(firstHalf.reverse)) {
    if(len % 2 == 1) {
      firstHalf += someNumber.substring(half, half+1);
      firstHalf = (BigInt(firstHalf)+1).toString;
      firstHalf + firstHalf.substring(0,firstHalf.length-1).reverse
    } else {
      firstHalf = (BigInt(firstHalf)+1).toString;
      firstHalf + firstHalf.reverse;
    }
  } else {
    if(len % 2 == 1) {
      firstHalf + someNumber.substring(half,half+1) + firstHalf.reverse;
    } else {
      firstHalf + firstHalf.reverse;
    }
  }
}
Eve Freeman
  • 32,467
  • 4
  • 86
  • 101
3

This is most general and clear solution that I can achieve:
Edit: got rid of BigInt's, now it takes less than a second to calculate million digits number.

def incStr(num: String) = {  // helper method to increment number as String
  val idx = num.lastIndexWhere('9'!=, num.length-1)
  num.take(idx) + (num.charAt(idx)+1).toChar + "0"*(num.length-idx-1)
}

def palindromeAfter(num: String) = {
  val lengthIsOdd = num.length % 2 
  val halfLength  = num.length / 2 + lengthIsOdd
  val leftHalf  = num.take(halfLength)               // first half of number (including central digit)
  val rightHalf = num.drop(halfLength - lengthIsOdd) // second half of number (also including central digit)      

  val (newLeftHalf, newLengthIsOdd) =  // we need to calculate first half of new palindrome and whether it's length is odd or even
    if (rightHalf.compareTo(leftHalf.reverse) < 0) // simplest case - input number is like 123xxx and xxx < 321
      (leftHalf, lengthIsOdd) 
    else if (leftHalf forall ('9'==))              // special case - if number is like '999...', then next palindrome will be like '10...01' and one digit longer
      ("1" + "0" * (halfLength - lengthIsOdd), 1 - lengthIsOdd)
    else                                           // other cases - increment first half of input number before making palindrome
      (incStr(leftHalf), lengthIsOdd)

  // now we can create palindrome itself
  newLeftHalf + newLeftHalf.dropRight(newLengthIsOdd).reverse
}   
incrop
  • 2,698
  • 1
  • 18
  • 17
  • @incorp - Thanks for the response but I noticed that for palindromeAfter("1422") I get 1551, instead of 1441. In your logic, if leftHalf < rightHalf, you are incrementing the leftHalf by 1, which I am not sure is what we should be doing. – sc_ray Apr 25 '12 at 03:41
  • Oh, thanks. Of course I should reverse left half before comparsion (as proposed by @user unknown), not right. Fixed it. – incrop Apr 25 '12 at 05:46
  • I apologize about offering code-review in such piecemeal fashion and with such a lag. In your incStr function fails for numbers such as 9, 99, 999 etc..etc. It throws a StringIndexOutOfBoundsException. – sc_ray May 03 '12 at 03:45
  • I noticed you are not calling the method when you have all 9s but I just wanted to point that incStr isn't exactly an increment in the purest sense of the verb. – sc_ray May 03 '12 at 03:47
  • Yes, I was keeping in mind that it don't handle all inputs (it also return wrong result for negative values). I might made these computations inline, but thought that will not be very readable, so I moved them to separate helper method. Name `incrementStringAsIntegerWhenItsNotAllNines` is better describes behavior of this method, but it's way too long :) – incrop May 03 '12 at 07:49
1

According to your range-less proposal: the same thing but using Stream:

def isPalindrome(n:Int):Boolean = n.toString.reverse == n.toString
def ints(n: Int): Stream[Int] = Stream.cons(n, ints(n+1))
val result = ints(100).find(isPalindrome)

And with iterator (and different call method, the same thing you can do with Stream, actually):

val result = Iterator.from(100).find(isPalindrome)

But as @user unknown stated, it is direct bruteforce and not practical with large numbers.

om-nom-nom
  • 62,329
  • 13
  • 183
  • 228
  • I don't think it is a practical approach for a number of 1 Million digits, not even for 100 digits. – user unknown Apr 23 '12 at 03:19
  • @om-nom-nom - Nice! Just out of curiosity what is the order of execution of the methods? Is find being executed for every number generated?Also, does Stream store all the evaluated numbers in memory? – sc_ray Apr 23 '12 at 03:21
  • 1
    @sc_ray Stream is the lazy collection (and it caches all of its usings): when you create Stream all you have is head (tail evaluation is deffered). But if you call strict methods (such us find) it's get evaluated, one-by-one (all checked numbers would be stored into memory), until first match. If you dont want to use caching you may consider to look at Iterator. – om-nom-nom Apr 23 '12 at 03:25
  • @om-nom-nom Thanks! What would fail with large numbers, the Iterator approach or reversing the strings in the isPalindrome method? Since, iterator is just maintaining the current state, I don't see how that would fail, but there are definitely ineffeciencies in the isPalindrome method. – sc_ray Apr 23 '12 at 03:45
  • @sc_ray oh god, I've misinterpreted you question, my approach is more error-prone and will definitely fail with large numbers, you better accept userunknown's or Wes answer – om-nom-nom Apr 23 '12 at 04:40
1

To check if List of Any Type is palindrome using slice, without any Loops

def palindrome[T](list: List[T]): Boolean = {
if(list.length==1 || list.length==0 ){
  false
}else {
  val leftSlice: List[T] = list.slice(0, list.length / 2)
  var rightSlice :List[T]=Nil
  if (list.length % 2 != 0) {
    rightSlice = list.slice(list.length / 2 + 1, list.length).reverse
  } else {
    rightSlice = list.slice(list.length / 2, list.length).reverse
  }
  leftSlice ==rightSlice
}

}

Though the simplest solution would be

     def palindrome[T](list: List[T]): Boolean = {
      list == list.reverse
      }
Abhishek Galoda
  • 2,753
  • 24
  • 38
0

You can simply use the find method on collections to find the first element matching a given predicate:

  def isPalindrome(n:Int):Boolean = n.toString.reverse == n.toString
  val (start, end) = (100, 1000)
  val result: Option[Int] = (start to end).find(isPalindrome)
  result foreach println

  >Some(101)
Eric
  • 15,494
  • 38
  • 61
  • Thanks. What if I do not want to specify a range but iterate down an infinite stream of numbers starting from 'start' and break out of my iteration when I get my first palindrome. – sc_ray Apr 23 '12 at 02:53
  • Also, what does the Option[Int] return type do? – sc_ray Apr 23 '12 at 02:54
  • Nevermind, found out what the Option[Int] does, which is returns the first value of type Int that satisfies the predicate. If the predicate is not satisfied by any of the numbers within the range, it will return 'None'. I am a scala n00b, thus the lame questions. – sc_ray Apr 23 '12 at 02:59
  • 1
    @sc_ray if you still interested what option is for, look [here](http://stackoverflow.com/a/9698507/298389) – om-nom-nom Apr 23 '12 at 03:14
  • 2
    I don't think it is a practical approach for a number of 1 Million digits, not even for 100 digits. – user unknown Apr 23 '12 at 03:21
0

Solution to verify if a String is a palindrome

This solution doesn't reverse the String. However I am not sure that it will be faster.

def isPalindrome(s:String):Boolean = { 
  s.isEmpty ||   
  ((s.last == s.head) && isPalindrome(s.tail.dropRight(1)))
}

Solution to find next palindrome given a String

This solution is not the best for scala (pretty the same as Java solution) but it only deals with Strings and is suitable for large numbers

You just have to mirror the first half of the number you want, check if it is higher than the begin number, otherwise, increase by one the last digit of the first half and mirror it again.

First, a function to increment the string representation of an int by 1:

def incrementString(s:String):String = {
  if(s.nonEmpty){
    if (s.last == '9')
      incrementString(s.dropRight(1))+'0'
    else
      s.dropRight(1) + (s.last.toInt +1).toChar
  }else
    "1"
}

Then, a function to compare to string representation of ints: (the function 'compare' doesn't work for that case)

/* is less that 0 if x<y, is more than 0 if x<y, is equal to 0 if x==y */
def compareInts(x:String, y:String):Int = {
  if (x.length !=y.length)
    (x.length).compare(y.length)
  else
    x.compare(y)
}

Now the function to compute the next palindrome:

def nextPalindrome(origin_ :String):String = {
  /*Comment if you want to have a strictly bigger number, even if you already have a palindrome as input */
  val origin = origin_
  /* Uncomment if you want to have a strictly bigger number, even if you already have a palindrome as input */
  //val origin = incrementString(origin_)

  val length = origin.length
  if(length % 2 == 0){
    val (first, last) = origin.splitAt(length/2); 
    val reversed = first.reverse
    if (compareInts(reversed,last) > -1)
      first ++ reversed
    else{
      val firstIncr = incrementString(first)
      firstIncr ++ firstIncr.reverse
    }
  } else {
    val (first,last) = origin.splitAt((length+1)/2)
    val reversed = first.dropRight(1).reverse
    if (compareInts(reversed,last) != -1)
      first ++ reversed
    else{
      val firstIncr = incrementString(first)
      firstIncr ++ firstIncr.dropRight(1).reverse
    }
  }
}
Christopher Chiche
  • 15,075
  • 9
  • 59
  • 98
  • Fails for many test cases I made: `- fail: 999 != 1001 for: 999 - fail: 11 != 22 for: 13 - fail: 9 != 11 for: 9 - fail: 99 != 101 for: 99 - fail: 131 != 141 for: 131 - fail: 3 != 4 for: 3` – user unknown Apr 23 '12 at 13:43
  • Hi, first I considered the problem as "larger or equal to". Then, I found a problem on when I input 13, it outputs 11, so I will check that and edit my post. Thanks for the (kind of brutal) suggestions – Christopher Chiche Apr 23 '12 at 16:40
  • @userunknown thanks for your suggestion, I had a problem in the 'compareInts' function where I misused `compare` for strings. – Christopher Chiche Apr 23 '12 at 16:46
  • The *brutal* suggestions are copy-pasted output of my test cases, which you could just copy, since your interface is identical. – user unknown Apr 23 '12 at 17:05
  • It now all work for your test cases. If we consider the "strictly bigger" case that I put in comments. – Christopher Chiche Apr 23 '12 at 17:07
0

You could try something like this, I'm using basic recursive:

 object Palindromo {
  def main(args: Array[String]): Unit = {

    var s: String = "arara"
    println(verificaPalindromo(s))
  }

  def verificaPalindromo(s: String): String = {
    if (s.length == 0 || s.length == 1)
      "true" 
    else
      if (s.charAt(0).toLower == s.charAt(s.length - 1).toLower)
        verificaPalindromo(s.substring(1, s.length - 1))
      else
        "false"
  }
}
Jubs
  • 1
  • 1
    This isn't a solution as requested in the question, i.e. "the value of the smallest palindrome larger than K". Re-read all the previous answers to get a better idea of what is required. – jwvh Oct 06 '19 at 05:07
0
  @tailrec
  def palindrome(str: String, start: Int, end: Int): Boolean = {
    if (start == end)
      true
    else if (str(start) != str(end))
      false
    else
      pali(str, start + 1, end - 1)
  }

  println(palindrome("arora", 0, str.length - 1))
Avenger
  • 793
  • 11
  • 31
  • Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – Mark Rotteveel Dec 06 '20 at 10:07