Conversion to palindrome with minimal points used

-1

1

Input: a word (2-100 characters)

Convert this word to a palindrome:

  • delete character - 13 points
  • add character - 12 points
  • increase character - 5 points ('d' > 'e')
  • decrease character - 4 points ('n' > 'm')
  • switch 2 characters - 7 points

What is the minimal points needed to make the word palindrome? (C++/C#)

Output: Minimal points needed to convert the word

Winning criteria: fastest algorithm

e-MEE

Posted 2012-01-24T14:00:36.937

Reputation: 125

3Why is it restricted to C and C++? – user unknown – 2012-01-24T15:06:08.063

@userunknown so I can understand the algorithm :) – e-MEE – 2012-01-24T15:13:04.090

And what is the objective winning criteria? – user unknown – 2012-01-24T15:30:26.543

@userunknown sorry, I'm new in codegolf.SE, and I thought that with the code-challenge tag it was obvious, since I don't want to see backtracking algorithms ^^ – e-MEE – 2012-01-24T16:04:30.987

4Meh. Graph search. – Peter Taylor – 2012-01-24T16:16:15.720

2>

  • There is a meta page to prepare questions, and the chat, and the FAQ. 2) You're free to restrict solutions to C and C#, but this will cost you much audience. 3) A code-challenge may still have some guidelines, and should have a date, when the decision is made.
  • < – user unknown – 2012-01-24T16:24:11.213

    how many points for replacing a character? The same as switch? – elssar – 2012-01-24T16:29:10.213

    Also, no points for comparing two characters? – elssar – 2012-01-24T16:34:55.960

    Can you switch any two characters or just adjacent ones? – hammar – 2012-01-24T17:11:39.697

    @hammar you can switch any two characters – e-MEE – 2012-01-24T20:44:49.467

    @elssar replacing means that you remove one, then add a new one – e-MEE – 2012-01-24T20:45:27.797

    Well I'm replacing with another character of the string, so it's like half a swap. – elssar – 2012-01-25T03:41:48.413

    Answers

    2

    Spent about an hour and got a working solution that I believe should give an optimal result:

    public class PalindromeFinder : IComparer<Tuple<int,string>>
        {
            public PalindromeFinder()
            {
                heap = new IntervalHeap<Tuple<int, string>>(this);
                handles = new Dictionary<string, IPriorityQueueHandle<Tuple<int, string>>>();
            }
    
            private readonly IntervalHeap<Tuple<int, string>> heap;
            private readonly Dictionary<string, IPriorityQueueHandle<Tuple<int, string>>>  handles; 
    
            public int FindMinValue(string startingWord)
            {
                AddWord(startingWord,0);
                while(!heap.IsEmpty)
                {
    
                    var item = heap.DeleteMin();
                    var word = item.Item2;
                    var value = item.Item1;
                    handles.Remove(word);
                    if(IsPalindrome(word))
                    {
                        return value;
                    }
                    for(int i = 0; i<word.Length; i++)
                    {
                        //decrease
                        var decreased = word.ToCharArray();
                        if (decreased[i] > 'a')
                        {
                            decreased[i]--;
                            AddWord(new string(decreased), value + 4);
                        }
                        //switch
                        for(int j = i+1; j<word.Length; j++)
                        {
                            if (word.Count(x => x == word[i]) >= 2 || word.Count(x => x == word[j]) >= 2)
                            {
                                var swap = word.ToCharArray();
                                var tmp = swap[i];
                                swap[i] = swap[j];
                                swap[j] = tmp;
                                AddWord(new string(swap), value + 7);
                            }
                        }
                        //add character before (only add characters already present)
                        foreach (char c in word.Distinct())
                        {
                            var added = word.Insert(i, c.ToString());
                            AddWord(added, value+12);
                        }
                    }
                    //add character at end of word
                    foreach (char c in word.Distinct())
                    {
                        var added = word + c.ToString();
                        AddWord(added, value + 12);
                    }
                }
                return int.MaxValue;
            }
    
            private void AddWord(string word, int value)
            {
                var min = int.MaxValue;
                if(handles.ContainsKey(word))
                {
                    var tuple = heap.Delete(handles[word]);
                    min = tuple.Item1;
                    handles.Remove(word);
                }
                IPriorityQueueHandle<Tuple<int, string>> handle = null;
                heap.Add(ref handle,new Tuple<int, string>(Math.Min(min, value), word));
                handles[word] = handle;
            }
    
            private bool IsPalindrome(string word)
            {
                int left = 0;
                int right = word.Length - 1;
                while(left<right)
                {
                    if (word[left] != word[right]) return false;
                    left++;
                    right--;
                }
                return true;
            }
    
            public int Compare(Tuple<int, string> x, Tuple<int, string> y)
            {
                return x.Item1 - y.Item1;
            }
        }
    

    Uses priority queue implementation from C5 collections. This is basically the brute force best-first search solution to the problem. It runs out of memory pretty fast, and is less efficient than is probably possible. I could fix that by implementing a better data structure like a DAWG, but out of time for now. Maybe later.It does work well for short words, and even for long ones that are within a few steps of a palindrome already.

    Edit:

    Made some improvements on Peter's suggestion, but the complexity is still too high. On input: "superman", runs for about a minute until it runs out of memory with over 11 million items in heap and a max value of 42 reached. Still a bit off from being a viable general solution.

    captncraig

    Posted 2012-01-24T14:00:36.937

    Reputation: 4 373

    You've got a copy-paste bug in the decrease case. Although it would be "better" to reuse the char[] and just do increased[i]-=2;. – Peter Taylor – 2012-01-25T16:57:08.003

    Also, you can limit the memory required to some extent by setting a bound. Anything which takes more than 13*(startingWord.Length-1) can be discarded in AddWord. – Peter Taylor – 2012-01-25T17:00:34.080

    Good catch on the bug. edited in. I think that optimization would not help very much though. There are a ton of possibilities with scores far lower than 13N. For "superman", it runs out of memory when the maximum value in the heap is only 38, with 11 million entries. I think to improve this I need to do some sort of estimation and apply A* to the search. Having trouble deciding on a consistent heuristic though. – captncraig – 2012-01-25T17:12:25.420

    3On further thought, incrementing a character is never going to be optimal (decrementing the one it matches is better), and deleting a character is never going to be optimal (inserting one to match it is better), and there's no point doing a swap unless one of the characters you swap is present in multiple copies. Those should reduce the branching factor considerably. – Peter Taylor – 2012-01-25T18:59:20.970

    I hadn't considered those. I figured maybe a swap-increment sequence might lead to something, but there would indeed be a corresponding decrement-swap sequence. – captncraig – 2012-01-25T22:38:43.630

    0

    C++

    Made this under the assumption that simply changing the character costs the same as switching two characters(7 points) & that comparing costs 0 points. A pretty basic & dumb solution, but it works.

    int main(int argc, char *argv[]) {
        int c, l, p;
        p = 0;
        if(argc != 2) { 
            cout<<"Incorrect arguments\n";
            return 1;
        }
        else if(strlen(argv[1])<2 || strlen(argv[1])>100) {
            cout<<"Not a valid string(between 2 & 100 characters)\n";
            return 1;
        }
        l = strlen(argv[1]);
        c = l/2;
        for(int i=0;i<c;i++) {
            if(argv[1][i] != argv[1][l-i-1]) {
                argv[1][i] = argv[1][l-i-1];
                p+=7;
            }
        }
        cout<<argv[1]<<endl<<p<<endl;
        return 0;
    }
    

    elssar

    Posted 2012-01-24T14:00:36.937

    Reputation: 579

    if the string is "abcddba", the cost is 4 – e-MEE – 2012-01-25T07:22:37.157

    @e-MEE yeah that's the minimum required. My algorithm is not that elegant. Will have to do a complete rewrite to get that kind of efficiency – elssar – 2012-01-25T07:25:58.493