Superpermutation minimisation: Difference between revisions

From Rosetta Code
Content added Content deleted
(+ D entry)
Line 70: Line 70:
932
932
6247</pre>
6247</pre>
Using the ldc2 compiler, it finds the result string of length 4_235_533 in about 13.6 seconds.
Using the ldc2 compiler with n=10, it finds the result string of length 4_235_533 in about 13.6 seconds.


=={{header|Python}}==
=={{header|Python}}==

Revision as of 12:13, 18 November 2014

Superpermutation minimisation is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

A superpermutation of N different characters is a string consisting of an arrangement of multiple copies of those N different characters in which every permutation of those characters can be found as a substring.

For example, representing the characters as A..Z, using N=2 we choose to use the first three characters 'AB'. The permutations of 'AB' are the two (i.e. N!), strings: 'AB' and 'BA'.

A too obvious method of generating a superpermutation is to just join all the permutations together forming 'ABBA'.

A little thought will produce the shorter (in fact the shortest) superpermutation of 'ABA' - it contains 'AB' at the beginning and contains 'BA' from the middle to the end.

The "too obvious" method of creation generates a string of length N!*N. Using this as a yardstick, the task is to investigate other methods of generating superpermutations of N from 1-to-7 characters, that never generate larger superpermutations.

Show descriptions and comparisons of algorithms used here, and select the "Best" algorithm as being the one generating shorter superpermutations.

The problem of generating the shortest superpermutation for each N is known to be NP complete, although the minimal strings for small values of N have been found by brute -force searches.

Reference

D

The greedy algorithm from the Python entry. This is a little more complex than the Python code because it uses some helper arrays to avoid some allocations inside the loops, to increase performance. <lang d>import std.stdio, std.ascii, std.algorithm, permutations2;

/** Uses greedy algorithm of adding another char (or two, or three, ...) until an unseen perm is formed in the last n chars. */ string superpermutation(in uint n) pure nothrow @safe in {

   assert(n > 0);

} out(result) {

   // It's a superpermutation.
   assert(uppercase[0 .. n].dup.permutations.all!(p => result.canFind(p)));

} body {

   string result = uppercase[0 .. n];
   bool[const char[]] toFind;
   foreach (const perm; result.dup.permutations)
       toFind[perm] = true;
   toFind.remove(result);
   auto trialPerm = new char[n];
   auto auxAdd = new char[n];
   while (toFind.length) {
       MIDDLE: foreach (immutable skip; 1 .. n) {
           auxAdd[0 .. skip] = result[$ - n .. $ - n + skip];
           foreach (const trialAdd; auxAdd[0 .. skip].permutations!false) {
               trialPerm[0 .. n - skip] = result[$ + skip - n .. $];
               trialPerm[n - skip .. $] = trialAdd[];
               if (trialPerm in toFind) {
                   result ~= trialAdd;
                   toFind.remove(trialPerm);
                   break MIDDLE;
               }
           }
       }
   }
   return result;

}

void main() {

   foreach (immutable n; 1 .. 8)
       n.superpermutation.length.writeln;

}</lang>

Output:
1
3
9
35
164
932
6247

Using the ldc2 compiler with n=10, it finds the result string of length 4_235_533 in about 13.6 seconds.

Python

<lang python>"Generate a short Superpermutation of n characters A... as a string using various algorithms."


from __future__ import print_function, division

from itertools import permutations from math import factorial import string import datetime import gc


MAXN = 7


def s_perm0(n):

   """
   Uses greedy algorithm of adding another char (or two, or three, ...)
   until an unseen perm is formed in the last n chars
   """
   allchars = string.ascii_uppercase[:n]
   allperms = [.join(p) for p in permutations(allchars)]
   sp, tofind = allperms[0], set(allperms[1:])
   while tofind:
       for skip in range(1, n):
           for trial_add in (.join(p) for p in permutations(sp[-n:][:skip])):
               #print(sp, skip, trial_add)
               trial_perm = (sp + trial_add)[-n:]
               if trial_perm in tofind:
                   #print(sp, skip, trial_add)
                   sp += trial_add
                   tofind.discard(trial_perm)
                   trial_add = None    # Sentinel
                   break
           if trial_add is None:
               break
   assert all(perm in sp for perm in allperms) # Check it is a superpermutation
   return sp

def s_perm1(n):

   """
   Uses algorithm of concatenating all perms in order if not already part
   of concatenation.
   """
   allchars = string.ascii_uppercase[:n]
   allperms = [.join(p) for p in sorted(permutations(allchars))]
   perms, sp = allperms[::], 
   while perms:
       nxt = perms.pop()
       if nxt not in sp:
           sp += nxt
   assert all(perm in sp for perm in allperms)
   return sp

def s_perm2(n):

   """
   Uses algorithm of concatenating all perms in order first-last-nextfirst-
   nextlast... if not already part of concatenation.
   """
   allchars = string.ascii_uppercase[:n]
   allperms = [.join(p) for p in sorted(permutations(allchars))]
   perms, sp = allperms[::], 
   while perms:
       nxt = perms.pop(0)
       if nxt not in sp:
           sp += nxt
       if perms:
           nxt = perms.pop(-1)
           if nxt not in sp:
               sp += nxt
   assert all(perm in sp for perm in allperms)
   return sp

def _s_perm3(n, cmp):

   """
   Uses algorithm of concatenating all perms in order first,
   next_with_LEASTorMOST_chars_in_same_position_as_last_n_chars, ...
   """
   allchars = string.ascii_uppercase[:n]
   allperms = [.join(p) for p in sorted(permutations(allchars))]
   perms, sp = allperms[::], 
   while perms:
       lastn = sp[-n:]
       nxt = cmp(perms,
                 key=lambda pm:
                   sum((ch1 == ch2) for ch1, ch2 in zip(pm, lastn)))
       perms.remove(nxt)
       if nxt not in sp:
           sp += nxt
   assert all(perm in sp for perm in allperms)
   return sp

def s_perm3_max(n):

   """
   Uses algorithm of concatenating all perms in order first,
   next_with_MOST_chars_in_same_position_as_last_n_chars, ...
   """
   return _s_perm3(n, max)

def s_perm3_min(n):

   """
   Uses algorithm of concatenating all perms in order first,
   next_with_LEAST_chars_in_same_position_as_last_n_chars, ...
   """
   return _s_perm3(n, min)


longest = [factorial(n) * n for n in range(MAXN + 1)] weight, runtime = {}, {} print(__doc__) for algo in [s_perm0, s_perm1, s_perm2, s_perm3_max, s_perm3_min]:

   print('\n###\n### %s\n###' % algo.__name__)
   print(algo.__doc__)
   weight[algo.__name__], runtime[algo.__name__] = 1, datetime.timedelta(0)
   for n in range(1, MAXN + 1):
       gc.collect()
       gc.disable()
       t = datetime.datetime.now()
       sp = algo(n)
       t = datetime.datetime.now() - t
       gc.enable()
       runtime[algo.__name__] += t
       lensp = len(sp)
       wt = (lensp / longest[n]) ** 2
       print('  For N=%i: SP length %5i Max: %5i Weight: %5.2f'
             % (n, lensp, longest[n], wt))
       weight[algo.__name__] *= wt
   weight[algo.__name__] **= 1 / n  # Geometric mean
   weight[algo.__name__] = 1 / weight[algo.__name__]
   print('%*s Overall Weight: %5.2f in %.1f seconds.'
         % (29, , weight[algo.__name__], runtime[algo.__name__].total_seconds()))

print('\n###\n### Algorithms ordered by shortest superpermutations first\n###') print('\n'.join('%12s (%.3f)' % kv for kv in

               sorted(weight.items(), key=lambda keyvalue: -keyvalue[1])))
     

print('\n###\n### Algorithms ordered by shortest runtime first\n###') print('\n'.join('%12s (%.3f)' % (k, v.total_seconds()) for k, v in

               sorted(runtime.items(), key=lambda keyvalue: keyvalue[1])))

</lang>

Output:
Generate a short Superpermutation of n characters A... as a string using various algorithms.

###
### s_perm0
###

    Uses greedy algorithm of adding another char (or two, or three, ...)
    until an unseen perm is formed in the last n chars
    
  For N=1: SP length     1 Max:     1 Weight:  1.00
  For N=2: SP length     3 Max:     4 Weight:  0.56
  For N=3: SP length     9 Max:    18 Weight:  0.25
  For N=4: SP length    35 Max:    96 Weight:  0.13
  For N=5: SP length   164 Max:   600 Weight:  0.07
  For N=6: SP length   932 Max:  4320 Weight:  0.05
  For N=7: SP length  6247 Max: 35280 Weight:  0.03
                              Overall Weight:  6.50 in 0.1 seconds.

###
### s_perm1
###

    Uses algorithm of concatenating all perms in order if not already part
    of concatenation.
    
  For N=1: SP length     1 Max:     1 Weight:  1.00
  For N=2: SP length     4 Max:     4 Weight:  1.00
  For N=3: SP length    15 Max:    18 Weight:  0.69
  For N=4: SP length    64 Max:    96 Weight:  0.44
  For N=5: SP length   325 Max:   600 Weight:  0.29
  For N=6: SP length  1956 Max:  4320 Weight:  0.21
  For N=7: SP length 13699 Max: 35280 Weight:  0.15
                              Overall Weight:  2.32 in 0.1 seconds.

###
### s_perm2
###

    Uses algorithm of concatenating all perms in order first-last-nextfirst-
    nextlast... if not already part of concatenation.
    
  For N=1: SP length     1 Max:     1 Weight:  1.00
  For N=2: SP length     4 Max:     4 Weight:  1.00
  For N=3: SP length    15 Max:    18 Weight:  0.69
  For N=4: SP length    76 Max:    96 Weight:  0.63
  For N=5: SP length   420 Max:   600 Weight:  0.49
  For N=6: SP length  3258 Max:  4320 Weight:  0.57
  For N=7: SP length 24836 Max: 35280 Weight:  0.50
                              Overall Weight:  1.49 in 0.3 seconds.

###
### s_perm3_max
###

    Uses algorithm of concatenating all perms in order first,
    next_with_MOST_chars_in_same_position_as_last_n_chars, ...
    
  For N=1: SP length     1 Max:     1 Weight:  1.00
  For N=2: SP length     4 Max:     4 Weight:  1.00
  For N=3: SP length    15 Max:    18 Weight:  0.69
  For N=4: SP length    56 Max:    96 Weight:  0.34
  For N=5: SP length   250 Max:   600 Weight:  0.17
  For N=6: SP length  1482 Max:  4320 Weight:  0.12
  For N=7: SP length 10164 Max: 35280 Weight:  0.08
                              Overall Weight:  3.06 in 50.2 seconds.

###
### s_perm3_min
###

    Uses algorithm of concatenating all perms in order first,
    next_with_LEAST_chars_in_same_position_as_last_n_chars, ...
    
  For N=1: SP length     1 Max:     1 Weight:  1.00
  For N=2: SP length     4 Max:     4 Weight:  1.00
  For N=3: SP length    15 Max:    18 Weight:  0.69
  For N=4: SP length    88 Max:    96 Weight:  0.84
  For N=5: SP length   540 Max:   600 Weight:  0.81
  For N=6: SP length  3930 Max:  4320 Weight:  0.83
  For N=7: SP length 33117 Max: 35280 Weight:  0.88
                              Overall Weight:  1.16 in 49.8 seconds.

###
### Algorithms ordered by shortest superpermutations first
###
     s_perm0 (6.501)
 s_perm3_max (3.057)
     s_perm1 (2.316)
     s_perm2 (1.494)
 s_perm3_min (1.164)

###
### Algorithms ordered by shortest runtime first
###
     s_perm0 (0.099)
     s_perm1 (0.102)
     s_perm2 (0.347)
 s_perm3_min (49.764)
 s_perm3_max (50.192)