30
\$\begingroup\$

I have written two code pieces to check if a given string is a palindrome:

public static bool FirstMethod(string str)
{
    int i = 0;
    int j = str.Length - 1;

    while (i<j)
    {
        if (str[i] != str[j])
            return false;

        i++;
        j--;
    }

    return true;
}

public static bool SecondMethod(string myString)
{
    string first = myString.Substring(0, myString.Length / 2);
    char[] arr = myString.ToCharArray();
    Array.Reverse(arr);
    string temp = new string(arr);
    string second = temp.Substring(0, temp.Length / 2);
    return first.Equals(second);
}

Please tell me which one is better.

Is there a better method than this?

\$\endgroup\$
12
  • 3
    \$\begingroup\$ I'm not sure either of the methods proposed is an actual valid response. Wikipedia defines a palindrome as : A palindrome is a word, phrase, number, or other sequence of symbols or elements that reads the same forward or reversed, with general allowances for adjustments to punctuation and word dividers. Famous examples include "Amor, Roma", "A man, a plan, a canal: Panama" and "No 'x' in 'Nixon'". Neither of the the methods deals with the removal of punctuation or spaces. \$\endgroup\$ Jul 29, 2014 at 16:23
  • \$\begingroup\$ How about a regular expression? \$\endgroup\$ Jul 29, 2014 at 17:08
  • 7
    \$\begingroup\$ Now write a version that takes into account UTF-16 surrogate pairs and Unicode combining characters :-) \$\endgroup\$
    – svick
    Jul 29, 2014 at 18:20
  • 2
    \$\begingroup\$ How important is performance to you? For readability, I prefer a straight-forward solution: myString == new String(myString.Reverse().ToArray()); \$\endgroup\$
    – Dennis
    Jul 30, 2014 at 5:49
  • 1
    \$\begingroup\$ @Voo I once implemented that using only the methods of the char type, and in was indeed annoying. But later I learned of the StringInfo class which handles splitting a string into text elements. Using it a unicode aware solution is barely more complex than the naive approach. \$\endgroup\$ Aug 7, 2014 at 16:42

9 Answers 9

25
\$\begingroup\$

I would prefer the first one for a few reasons.

  1. It's more readable and easily understandable in my opinion.
  2. There's only one string, so less overhead. (Which is negligible unless this is being called a lot.)
  3. There's only one array. Reverse creates a second array.

That being said, an actual review is in order.

  • Methods that return a boolean value should have names of the form IsSomething or HasSomething. A great name would be IsPalindrome.
  • Neither method accepts anything but a string. Numbers can be palindromic too. Wouldn't it be a nice addition if we could pass an integer to it as well? (Without explicitly calling .ToString() before passing it in that is.)
  • I like that you're only checking for i < j instead of i <= j. It saves a useless iteration when there are an odd number of characters.
  • I also like the while loop. It's a lot cleaner than the for loop suggested by another answer.
  • I won't speak much on the second method, because I think @elios provided a nice implementation of it.
\$\endgroup\$
15
  • 2
    \$\begingroup\$ In my actual code the name of FirstMethod is "IsPalindrome" I named it this way just for the question purpose :)...for the second point yes I failed to check it for palindrome numbers..and for the third point initially I added i<=j then I realized for odd number of characters the middle value would be the same..thanks for pointing out though \$\endgroup\$
    – Rohit
    Jul 29, 2014 at 17:46
  • 6
    \$\begingroup\$ I actually believe that not checking palindromic numbers is a good thing. First of all, it means you have to write less code. Second, I reject the assumption that numbers themselves can be palindromic. For instance, 22 would be a so-called palindromic number. However, 22 is 10110 in binary, which is not palindromic. To check if a number is palindromic, you need to know the number system (base 10, base 2, base 16). \$\endgroup\$ Jul 29, 2014 at 19:52
  • 1
    \$\begingroup\$ It would be a nice extension, that I agree on. I was afraid however that it came across as something one must do, instead of as a feature one could add, and if so, only after the need has been discovered. However, for accepting a number, I do think that one should expect a base to be supplied, optionally defaulting to 10 if the language supports it. (By the way, I upvoted. I completely agree with all the other remarks.) \$\endgroup\$ Jul 29, 2014 at 20:11
  • 3
    \$\begingroup\$ @ckuhn203: About the "racecar" vs "voiture de course", I do not think this is comparable to needing to know the base. A number in and by itself is not something consisting of separate entities, and only by choosing a representation (binary, decimal, hexadecimal), do we get something that consist of an ordered list of entities. The number one hundred and twenty-one becomes the entities 1, 2, 1 (in precisely that order). That does me to think to add that not only strings can be palindromic, but any list of items (supporting equality). The algorithm chosen would still be my choice for this. \$\endgroup\$ Jul 29, 2014 at 20:17
  • 3
    \$\begingroup\$ @WernerCD, racecar in french is a different word to racecar in english. Whereas 0xF and 15 are the same number and mean the same thing. equal(15, 0xF) is true, but equal("racecar", "voiture de course") is false. \$\endgroup\$
    – Jonathan.
    Jul 30, 2014 at 18:38
18
\$\begingroup\$

Here is the most common solution for this problem:

public static bool IsPalindrome(string s)
{
    char[] array = s.ToCharArray();
    Array.Reverse(array);
    string backwards = new string(array);

    return s == backwards;
}

and if you are using Linq:

public static bool IsPalindrome(string s)
{
    return s == new string(s.Reverse().ToArray());
}

but if you are planning to execute this code thousands of times (which I doubt), your first method would be the fastest.

\$\endgroup\$
10
\$\begingroup\$

I agree with @ckuhn203 in that the first looks cleaner.

I'm not too familiar with C# and its style, but in C I would definitely prefer using a for loop:

public static bool FirstMethod(string str)
{
    for (int i = 0, j = str.Length - 1; i < j; i++, j--) {
        if (str[i] != str[j]) {
            return false;
        }
    }
    return true;
}
\$\endgroup\$
5
\$\begingroup\$

Consider using LINQ:

static bool IsPalindrome(string s)
{
    var characterPairs = s.Zip(s.Reverse(), (l, r) => l == r);
    return characterPairs.Take(s.Length / 2).All(isMatch => isMatch);
}
\$\endgroup\$
5
  • \$\begingroup\$ there is no Reverse method in System.String \$\endgroup\$
    – elios264
    Jul 29, 2014 at 16:57
  • 3
    \$\begingroup\$ @elios264 using System.Linq; \$\endgroup\$
    – dss539
    Jul 29, 2014 at 16:59
  • \$\begingroup\$ dont thing you need to pass a lambda to All if it is going to be identity \$\endgroup\$
    – jk.
    Jul 29, 2014 at 21:04
  • \$\begingroup\$ @jk. I wish that were true. It would look cleaner. However... Error 1 No overload for method 'All' takes 0 arguments \$\endgroup\$
    – dss539
    Jul 30, 2014 at 15:36
  • \$\begingroup\$ @dss539 you are right! how peculiar that Any has that overload but All doesn't \$\endgroup\$
    – jk.
    Jul 30, 2014 at 16:46
4
\$\begingroup\$

The first one is probably a bit faster, the second could be cleaned up a bit, but is easier to read. Unless you know you have a performance problem, prefer the second one.

For the second option, you should extract two sub-strings from the original input string called "firstHalf" and "secondHalf" corresponding to the first and second half of the input string respectively. Then reverse "secondHalf" and compare.

\$\endgroup\$
3
\$\begingroup\$

Another way to write this using LINQ, though I'm not sure it's actually clearer:

Enumerable.Range(0, str.Length / 2).All(i => str[i] == str[str.Length - i - 1])
\$\endgroup\$
2
  • \$\begingroup\$ finally a one line answer! \$\endgroup\$
    – elios264
    Jul 29, 2014 at 18:49
  • 1
    \$\begingroup\$ @elios264 My answer can also be one line, but I split it for readability. return s.Zip(s.Reverse(), (l,r)=>l==r).Take(s.Length/2).All(b=>b) \$\endgroup\$
    – dss539
    Jul 29, 2014 at 19:15
2
\$\begingroup\$

My computer science teacher would turn over if at least one person didn't respond with an example of recursion. I'm not a C# guy myself, but if you wanted to nerd out I think that would work a little something like this:

public static bool IsPalindrome(string str)
{
    int len = str.length - 1;

    return str[0] == str[len] && (
           len <= 1 || IsPalindrome( str.Substring(1,len) )
           );
}

As long as you are chopping up new strings you could potentially write this so that long strings could be distributed across multiple cores. That's a 1/number_of_cores improvement!

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

I would add that you could make your method an extension method of string. It just emphasises that being a palindrome is a property of a string representation of some sort.

For ease of understanding, the second method stands out. The index based version works, but most people would scratch their heads if they saw it without a hint.

Also it's a premature optimisation to cut the string in half, even though of course it is logical. Much more clear to reverse and compare. If somehow it really is a bottleneck, then cut it in half.

\$\endgroup\$
7
  • 1
    \$\begingroup\$ I knew right away what that code was doing, why would people scratch their heads? \$\endgroup\$
    – Malachi
    Jul 29, 2014 at 16:50
  • 1
    \$\begingroup\$ I agree with @Malachi. The first version is actually easier to read to my eyes. However, my original background was in C/C++ so maybe that's why. The information density is higher. #2 looks like some Java Enterprise Architect wrote it. (not a bad thing necessarily) \$\endgroup\$
    – dss539
    Jul 29, 2014 at 16:58
  • \$\begingroup\$ The index manipulation version is not obvious at all. I'm assuming someone would be skimming the code, not parsing every little bit. You'd have to catch the fact that i and j are moving opposite to each other and have a think about why it stops when they cross. The second way actually says "reverse" in the code and compares. \$\endgroup\$
    – Carlos
    Jul 29, 2014 at 18:42
  • 1
    \$\begingroup\$ It's a pretty basic algorithm. If someone I worked with saw it and didn't understand it, I would be concerned about their level of ability. Loops and arrays should be one of the first things you learn as a programmer. \$\endgroup\$
    – RubberDuck
    Jul 29, 2014 at 19:02
  • 1
    \$\begingroup\$ @ckuhn203 I think Carlos' point wasn't that he didn't understand it, but rather that it took longer to understand than version #2. I can easily see that being true for me if I did not have a background in C/C++. \$\endgroup\$
    – dss539
    Jul 29, 2014 at 19:13
0
\$\begingroup\$

Another shameless linq answer that doesn't require reversing or creating a new enumeration and only goes as far as it needs to:

public static bool IsPalindrome(this string str)
{
    int len = str.Length;
    return str.Take(len / 2).All(o => o == str[--len]);
}
\$\endgroup\$

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