How Can I Combine Slicing and Reversing to Determine Palindromes in Python?

  • Python
  • Thread starter WWGD
  • Start date
  • Tags
    String
In summary, the code is written to reverse a string, but it is not running. The author is trying to combine two functions to determine whether a string is a palindrome, but the code does not work.
  • #36
PeterDonis said:
Yes, but what about strings that represent negative numbers?
Ok. Then I'll have to add a clause. Or just declare it's not a palindrome, as there are no numbers ending up in a -. Or set it up differently, to compare places 1 ( bypassing the 1st/0th spot) through k with places k through 1.
 
Technology news on Phys.org
  • #37
WWGD said:
Ok. Then I'll have to add a clause.
This sort of check was already in the code in item 1 in your OP. That's why I was somewhat confused that you left this out in the new code.
 
  • #38
Sigh. In C:
int isPalindrome(char *inp) { return (strcmp(inp, strrev(inp))==0) }
 
  • #39
Svein said:
Sigh. In C:
int isPalindrome(char *inp) { return (strcmp(inp, strrev(inp))==0) }
Sure, if you leave out the requirement to evaluate strings representing negative numbers without including the minus sign. And if you leave out printed output of the results. But the OP is including both of those things. Without them, of course you could do it as a one-liner in Python as easily as you can in C.
 
  • #40
I think we are all a bit confused here. Let's go back to the code in the very first post:

Python:
# Reversing a string :
def Rev(x):
  String = str(x)
  if String[0]= '- ' :
     return int( '-' + String[ :0:-1} )
  else:
     return int( String[:: -1] )

This code is clearly NOT designed to reverse a string (otherwise you wouldn't need to do String = str(x) and you would return a string, not an integer). It is designed to reverse the (decimal) digits of an INTEGER. But it has a few typos, and there is a better way to implement it. First let's sort the typos:
Python:
# Reverse an integer.
def Rev(x):
  String = str(x)
  if String[0] == '-':
     return int('-' + String[:0:-1])
  else:
     return int(String[::-1])
... and make sure it works with some tests ...
Python:
assert Rev(123) == 321
assert Rev(-123) == -321
assert Rev(120) == 21
assert Rev(-120) == -21
print("Passed")

But that code is so horrible I can't let it "pass", so let's fix that too:
Python:
def getReversedDigits(n: int) -> int:
    '''Takes an integer n, returns n with its digits reversed.'''
    if x < 0:
        return -int(str(-x)[::-1])
    return int(str(x)[::-1])

Now we can create:
Python:
def isIntegerPalindrome(n: int) -> bool:
    '''Takes an integer n, returns True iff n is a palindrome, otherwise False.'''
    return n == getReversedDigits(n)

I leave it to the reader to create some tests to verify isIntegerPalindrome.
 
Last edited:
  • #41
Somewhat OT, but here's an implementation of a palindrome function in Erlang, a language I've been studying for a few weeks. I didn't realize it, but Erlang is/was used heavily by the Ericsson telecom company of Sweden. Since it'a doubtful that few if any members here are knowledgeable in Erlang, I'll say a little about how this implementation works.

The first two lines of the code indicate that this is a module, a container for functions, and that this module exports a function named "palindrome". The /1 suffix indicates that it takes one argument.

The parameter to my palindrome function is Str, a string of characters. It returns either true or false, depending on whether the string is or is not a palindrome. Both true and false are atoms, that are sort of like constants, and that are known to the Erlang compiler. As I have implemented this function, I am concerned only with strings of characters that are symmetrical. As such, the string "-12321" could not possibly be a palindrome, but "ab2%!%2ba" is one.

I'm using several BIFs (built-in functions) that are present in modules that come with Erlang. These include is_empty(), length(), and slice(), all of which are defined in the string module.

The first thing I do is to get the length of the string, and store it in Len. I then check for a null string, using the is_empty() function (I could have used the value of Len, instead.) If so, the function returns false; the notation -> false is how Erlang returns values. Erlang uses pattern matching to determine which branch of an if clause or a case clause. If the string is not null, there isn't a match with string:is_empty(Str), but there will othewise be a match with true.

If the code hits the true clause, it uses the slice() function to grab the first and last characters of the string. Once I have these, I use the Len variable to determine how long the string is. A one-character string is automatically a palindrome, so the code returns true. A two-character string will be a palindrome if its two characters are the same. A three-character string will be a palindrome if its first and last characters are the same. It doesn't matter what the middle character is.

if the code gets past the 1, 2, or 3 cases without making a match, the catchall, don't-care case (_) must match. In this last case, I check to see if the first and last characters are the same. If not, the code returns false. If they are the same, I create a new string that omits the first and last characters, and make a recursive call to palindrome() with this shorter string.

I've done a fair amount of testing, using a list of about a dozen strings of different lengths, with a mix of palindromes and non-palindromes, and the code seems to work as expected.

Code:
-module(pal).
-export([palindrome/1]).

palindrome(Str) -> 
  Len = string:length(Str),
  case string:is_empty(Str) of 
    true  -> false;     %% Null string
    false ->               %% String contains at least one character
       Start = string:slice(Str, 0, 1),
       End = string:slice(Str, Len-1, 1),
       case Len of
          1 -> true;                   %% One char string
          2 ->                         %% Two chars string
             if
               Start =/= End -> false;
               Start == End -> true
             end;          
          _ ->                         %% Three or more chars
             if
               Start =/= End -> false;
               Start == End ->
               NewStr = string:slice(Str, 1, Len-2),
               palindrome(NewStr)
             end
        end
  end.
 
Last edited:
  • Like
Likes WWGD
  • #42
Not being familiar with Erlang, maybe I missed something, but...

... it looks like case 3 is not needed.

The "%% Four or more chars" can easily handle it.
 
  • #43
Tom.G said:
.. it looks like case 3 is not needed.

The "%% Four or more chars" can easily handle it.
That's right -- case 3 isn't needed. I whipped the code up today, and didn't think it all the way through. If a string has an odd number of characters, and it successfully passes the tests for first and last characters being equal, the last recursive call will have a string with 1 character, which is handled by case 1, In the same fashion, a string with an even number of characters that successfully passes the same tests, will eventually get down to a string with two characters, which is handled by case 2.

Edited to add: I revised the previously-shown code to eliminate case 3.
 
Last edited:
  • #44
Unless the Erlang compiler has an optimization setting of "prescient" that algorithm is going to result in really expensive code - it has to create ## n \over 2 ## new strings using ## \mathcal O(n^2) ## memory! There is a way to efficiently test for palindromes with a recursive algorithm, provided you have a compiler with tail call optimization.
 
  • #45
pbuk said:
Unless the Erlang compiler has an optimization setting of "prescient" that algorithm is going to result in really expensive code - it has to create ## n \over 2 ## new strings using ## \mathcal O(n^2) ## memory! There is a way to efficiently test for palindromes with a recursive algorithm, provided you have a compiler with tail call optimization.
Erlang permits only single assignment to a variable, so yes, a new string is created in each recursive call. However, the code I wrote uses a tail call, and the Erlang compiler is able to optimize recursive calls of this sort.
 
  • #46
If you add an offset parameter to the function call you can keep calling if with the same string.
 
  • #47
pbuk said:
If you add an offset parameter to the function call you can keep calling if with the same string.
I'm not sure that would make a difference. I don't know enough about Erlang to know whether it passes parameters by reference or by value, or if in each call to palindrome() a copy of the string (possibly shortened as you described) is made.

I've streamlined the code a bit, and I'm pretty happy with it. Short 'n' sweet.

In the latest version, I'm using pattern matching to separate a call with an empty string as opposed to a call with a string with at least one character.
Code:
-module(pal).
-export([palindrome/1]).

palindrome([]) -> false;          %% Null string
palindrome(Str) ->                %% Non-null string
   Len = string:length(Str),  
   Start = string:slice(Str, 0, 1),
   End = string:slice(Str, Len-1, 1),
   case Len of 
      1 -> true;                  %% One char string
      2 ->                        %% Two chars string
         if 
           Start =/= End -> false;
           Start == End -> true
         end; 
      _ ->                         %% Three or more chars
         if 
           Start =/= End -> false;
           Start == End -> 
             NewStr = string:slice(Str, 1, Len-2), 
             palindrome(NewStr)      %% Recursive call
         end
    end.
 
  • Like
Likes Tom.G
  • #48
Mark44 said:
I'm not sure that would make a difference.
Oh it would:
Code:
doEndsMatch(Candidate, Offset0, Offset1) ->
    Left = string:slice(Candidate, Offset0, 1),
    Right = string:slice(Candidate, Offset1, 1),
    if
        Left =/= Right -> false;
        Offset1 - Offset0 < 3 -> true;
        true ->
            doEndsMatch(Candidate, Offset0 + 1, Offset1 - 1)
    end.

isPalindrome([]) -> false;
isPalindrome(Candidate) ->
    doEndsMatch(Candidate, 0, string:length(Candidate) - 1).

Mark44 said:
I don't know enough about Erlang to know whether it passes parameters by reference or by value
All variables in Erlang are immutable so the distinction is not relevant.

Mark44 said:
or if in each call to palindrome() a copy of the string (possibly shortened as you described) is made.
You are making the (shortened) copy explicitly yourself with NewStr = string:slice(Str, 1, Len-2).
 
  • Informative
Likes Mark44
  • #49
@pbuk, nice!
Do you have Erlang installed? I didn't check yet, but it looks like your code is syntactically correct and would run and produce the right results.
 
  • #51
Rightly or wrongly, I'll take it as a personal compliment to my explanatory skills that you were able to significantly revise my code to get your version.
 
  • Like
Likes pbuk
  • #52
OK. What if the string starts with some whitespace?
 
  • #53
Svein said:
OK. What if the string starts with some whitespace?
Then you would need to define how whitespace (and other non-alphanumeric) characters should be treated in the context of a palindrome (I would do this by adding test cases), work out whether the way the current algorithm deals with them fits that definition (by running the test cases), and if not, change it so that it does (and all the tests pass).

You would also need to ensure that the way the code deals with characters outside the ASCII range fit your definition of a palindrome (noting that in many languages including Erlang strings are sequences of Unicode code points). For instance is the French phrase Léon, émir cornu d’un roc, rime Noël a palindrome?
 
Last edited:
  • #54
pbuk said:
And if you are going to handle numbers, be careful with e.g. 12343210. This is why we have unit testing (another reason why playing with the REPL or Jupyter notebook is not "real" programming).
I'm still figuring out how to compose slices [:a:b} , with a,b Integers, to eliminate a last place 0 and then reverse it. I've been putting of the (pretentious term) , algebra of slices (to avoid going ito loc. and .iloc). So far, I get that [:0:-1] reverses. But I need to set up an hour or so to detail it.
 
  • #55
Svein said:
OK. What if the string starts with some whitespace?
This is more at an intro level, so not really an issue and would require too much of a detour.
 
  • #56
WWGD said:
A general question about Jupyter for Python : Do definitions apply throughout in different cells in the same notebook, i.e., if I define a function in cell j of notebook k, can I apply it in any other cell of the same notebook k? Strangely, I've been able to do it only some times. I m ean, I used Rev(x), the function that reverses a string, in the definition of the Pal function, as in : "if String==Rev(String) return("Yes, Pal"), but this hasn't worked other times.

Heading out till tomorrow on PC. Thanks all.
I know this is old, and maybe it's covered elsewhere in this thread. In Jupyter, if you run a cell that creates a function or variable, then those functions and variables will remain in memory, available to use for the remainder of that session (until you close or reset the Kernel). You could open a notebook, then scroll halfway down, execute a cell which creates some variables or functions, then scroll back to the top and use those functions.

But it would be better to set up the notebook so that it could be run in order by another person, so they get the same results as you.
 
  • Like
Likes WWGD

Similar threads

Replies
1
Views
1K
Replies
2
Views
956
Replies
3
Views
2K
Replies
5
Views
3K
Replies
18
Views
1K
Back
Top