Kürzester gemeinsamer Superstring

26

Bei einer gegebenen Liste von Zeichenfolgen s_0, s_1, ..., s_nfinden Sie die kürzeste Zeichenfolge S, die jeweils s_0, s_1, ..., s_neine Unterzeichenfolge enthält .

Beispiele :

  • S('LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE')='SEDOLOREMAGNAD'
  • S('ABCDE', 'BCD', 'C')='ABCDE'

Schreiben Sie das kürzeste Programm (oder die kürzeste Funktion), die dieses Problem löst. Sie können Zeichenfolgen als Arrays oder Listen von Zeichen / Ganzzahlen darstellen, wenn Sie möchten. Standardbibliotheken sind in Ordnung. Für die Ein- / Ausgabe können Sie das verwenden, was bequemer ist: STDIN / STDOUT, Benutzereingabeaufforderung, Parameter / Rückgabewert einer Funktion usw.

Die Leistung ist nicht kritisch - Nehmen wir an, bei einer Eingabe mit einer Gesamtlänge von <100 Zeichen muss das Ergebnis bei durchschnittlicher moderner Hardware in <10 Sekunden berechnet werden.

Zakharia Stanley
quelle
3
+1 Schöne Frage. Ich schlage vor, Sie geben einige zusätzliche Beispiele für die erwarteten Ergebnisse an, damit die Menschen leicht beurteilen können, ob die eingereichten Beiträge in der Lage sind, eine Vielzahl von Fällen zu behandeln.
DavidC
Wie soll mit Input / Output umgegangen werden? Soll das Ergebnis gedruckt oder von einer Funktion zurückgegeben werden?
Flornquake
Also, nein "für jede Zeichenkette, wenn sie alles von ... enthält, gib sie zurück" ist keine gültige Lösung?
John Dvorak
Ich bezweifle, dass es eine Antwort geben wird. Diese Frage könnte ganz gut auf Stack Overflow (ohne den Code-Golf-Teil) passen .
John Dvorak

Antworten:

8

Python 2, 170 153/157/159

Verkürzt dank einiger Ideen von Baptiste .

from itertools import*
print min((reduce(lambda s,w:(w+s[max(i*(s[:i]==w[-i:])for i in range(99)):],s)[w in s],p)
for p in permutations(input())),key=len)

Der zweite Zeilenumbruch wird nicht benötigt.

Eingabe: 'LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE'
Ausgabe:SEDOLOREMAGNAD

Selbst bei langen Eingabezeichenfolgen dauert dies weniger als 2 Sekunden, wenn höchstens 7 Eingabezeichenfolgen vorhanden sind (wie im angegebenen Beispiel, das auf meinem Computer in 1,7 bis 1,5 Sekunden ausgeführt wird). Bei 8 oder mehr Eingabezeichenfolgen dauert es jedoch länger als 10 Sekunden, da die Zeitkomplexität ist O(n!).

Wie Baptiste betonte, range(99)muss durch ersetzt werden, range(len(w))wenn beliebige Eingabelängen unterstützt werden sollen (wobei die Gesamtlänge des Codes 157 Zeichen beträgt). Wenn leere Eingabezeichenfolgen unterstützt werden sollen, muss diese in geändert werden range(len(w)+1). Ich denke, range(99)funktioniert für alle Eingaben mit einer Länge von weniger als 200 korrekt.

Weitere Tests:

>>> "AD", "DO", "DOLOR", "DOLORE", "LOREM", "MAGNA", "SED", "ORE",  "R"
SEDOLOREMAGNAD

>>> 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 'abcdefghijklmnopqrstuvw
... xyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstu
... vwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 'ZOOM', 'aZ', 'Za', 'ZA'
aZABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZOOM
Flornbeben
quelle
5

Mathematica 337 418 372

Nachdem LongestCommonSubsequencePositionsich erfolglos versucht hatte, mit Mathematicas zu implementieren , wandte ich mich dem Mustervergleich zu.

v=Length;
p[t_]:=Subsets[t,{2}];
f[w_]:=Module[{c,x,s=Flatten,r={{a___,Longest[y__]},{y__,b___}}:>{{a,y},{y,b},{y},{a,y,b}}},
c=p@w;
x=SortBy[Cases[s[{#/.r,(Reverse@#)/.r}&/@c,1],{_,_,_,_}],v[#[[3]]]&][[-1]];
Append[Complement[w,{x[[1]],x[[2]]}],x[[4]]]]

g[r_]:=With[{h=Complement[r,Cases[Join[p@r,p@Reverse@r],y_/;!StringFreeQ@@y:>y[[2]]]]},
FixedPoint[f,Characters/@h,v@h-1]<>""]

Die Mustervergleichsregel

r={{a___,Longest[y__]},{y__,b___}}:> {{a,y},{y,b},{y},{a,y,b}}},

ein geordnetes Paar von Worten (dargestellt als Listen von Zeichen) nimmt und: (1) die Worte, {a,y}und {y,b}gefolgt von (2) den gemeinsamen Teilzeichen , y, dass Links des Ende eines Wort mit dem Anfang des anderen Wortes, und, Schließlich das kombinierte Wort {a,y,b}, das die eingegebenen Wörter ersetzt. Ein ähnliches Beispiel finden Sie unter Belisarius: https://mathematica.stackexchange.com/questions/6144/looking-for-longest-common-substring-solution

Drei aufeinanderfolgende Unterstriche bedeuten, dass das Element eine Folge von null oder mehr Zeichen ist.

Reversewird später verwendet, um sicherzustellen, dass beide Aufträge getestet werden. Die Paare, die verbindbare Buchstaben gemeinsam haben, werden unverändert zurückgegeben und ignoriert.

Bearbeiten :

Das Folgende entfernt aus der Liste Wörter, die in einem anderen Wort "begraben" (dh vollständig enthalten) sind (als Antwort auf den Kommentar von @ flornquake).

h=Complement[r,Cases[Join[p@r,p@Reverse@r],x_/;!StringFreeQ@@x:> x[[2]]]]

Beispiel :

 {{"D", "O", "L", "O", "R", "E"}, {"L", "O", "R", "E", "M"}} /. r

kehrt zurück

{{"D", "O", "L", "O", "R", "E"}, {"L", "O", "R", "E", "M"}, { "L", "O", "R", "E"}, {"D", "O", "L", "O", "R", "E", "M"}


Verwendung

g[{"LOREM", "ORE", "R"}]

AbsoluteTiming[g[{"AD", "DO", "DOLOR", "DOLORE", "LOREM", "MAGNA", "SED", "ORE",  "R"}]]

"LOREM"

{0.006256, "SEDOLOREMAGNAD"}

DavidC
quelle
Funktioniert das für die Eingabe "LOREM", "ORE", "R"?
Flornquake
(Dh, produziert es die richtige Ausgabe "LOREM"?)
Flornquake
@flornquake. Schöner Fang. Ich habe es in der aktuellen Version angesprochen. Ich hoffe, ich habe keine anderen Fälle verpasst. Vielen Dank.
DavidC
Nur das Beste!
DavidC
3

GolfScript, 66 Zeichen

{.,1>{.`{[1$]-s:h;.,),\`{:g<`{\+.g?0<{;}*}+h%~}+/}+%.&}*}:s~{,}$0=

Ziemlich kurz, aber aufgrund der exponentiellen Zeitkomplexität (und des sehr langsamen GolfScript) wird das Zeitlimit von 10 Sekunden überschritten.

Beispiele:

['LOREM' 'DOLOR' 'SED' 'DO' 'MAGNA' 'AD' 'DOLORE']
{.,1>{.`{[1$]-s:h;.,),\`{:g<`{\+.g?0<{;}*}+h%~}+/}+%.&}*}:s~{,}$0=
# => SEDOLOREMAGNAD

['AB' 'BC' 'CA' 'BCD' 'CDE']
{.,1>{.`{[1$]-s:h;.,),\`{:g<`{\+.g?0<{;}*}+h%~}+/}+%.&}*}:s~{,}$0=
# => CABCDE
Howard
quelle
2

Python 2, 203 187 200

from itertools import permutations as p
def n(c,s=''):
 for x in c:s+=x[next((i+1 for i,l in [(j,x[:j+1])for j in range(len(x))][::-1]if s.endswith(l)),0):]
 return s
print min(map(n,p(input())),key=len)

Eingabe: ['LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE']
Ausgabe:SEDOLOREMAGNAD

Bearbeiten

Mit reduceeinigem Dirty-Import-Trick kann ich dies weiter reduzieren (und nur auf eine Zeile!):

print min((reduce(lambda a,x:a+x[next((i+1 for i,l in [(j,x[:j+1])for j in range(len(x))][::-1]if a.endswith(l)),0):],P,'')for P in __import__('itertools').permutations(input())),key=len)

Bearbeiten 2

Wie Flornquake feststellte, führt dies zu falschen Ergebnissen, wenn ein Wort in einem anderen enthalten ist. Das Update fügt weitere 13 Zeichen hinzu:

print min((reduce(lambda a,x:a+(x[next((i+1 for i,l in [(j,x[:j+1])for j in range(len(x))][::-1]if a.endswith(l)),0):],'')[x in a],P,'')for P in __import__('itertools').permutations(input())),key=len)

Hier ist die aufgeräumte Version:

from itertools import permutations

def solve(*strings):
    """
    Given a list of strings, return the shortest string that contains them all.
    """
    return min((simplify(p) for p in permutations(strings)), key=len)

def prefixes(s):
    """
    Return a list of all the prefixes of the given string (including itself),
    in ascending order (from shortest to longest).
    """
    return [s[:i+1] for i in range(len(s))]
    return [(i,s[:i+1]) for i in range(len(s))][::-1]

def simplify(strings):
    """
    Given a list of strings, concatenate them wile removing overlaps between
    successive elements.
    """
    ret = ''
    for s in strings:
        if s in ret:
            break
        for i, prefix in reversed(list(enumerate(prefixes(s)))):
            if ret.endswith(prefix):
                ret += s[i+1:]
                break
        else:
            ret += s
    return ret

print solve('LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE')

Es ist möglich, einige Charaktere auf Kosten der theoretischen Korrektheit zu rasieren, indem man range(99)anstelle von range(len(x))(Credits für das Flornquake, um an dieses zu denken) verwendet.

Baptiste M.
quelle
Wenn Sie bereit sind, auf Korrektheit zu verzichten, können Sie auch den gierigen Ansatz oder den polynomialen Approximationsfaktor 2 verwenden.
Peter Taylor
Schöne lösung! Sie müssen jedoch prüfen, ob sich bereits neue Wörter in der Superzeichenfolge befinden: 'LOREM', 'ORE', 'R'Die Ausgabe wird falsch erzeugt LOREMORER.
Flornquake
@flornquake Guter Fang. Ich habe es geschafft, es zu reparieren, aber es fügt 13 Zeichen hinzu.
Baptiste M.
1

Python, 144 Zeichen

S=lambda A,s:min(S(A-set([a]),s+a[i:])for a in A for i in range(len(a)+1)if i==0 or s[-i:]==a[:i])if A else(len(s),s)
T=lambda L:S(set(L),'')[1]

SNimmt eine Reihe von Wörtern A, die noch platziert werden müssen, und eine Zeichenfolge smit Wörtern, die bisher platziert wurden. Wir holen eine Rest Wort aaus Aund überlappen sie von 0zu len(a)Zeichen mit dem Ende s.

Dauert im angegebenen Beispiel nur ca. 0,15 Sekunden.

Keith Randall
quelle
Wirklich nett! Wie bei einigen anderen Lösungen funktioniert dies jedoch nicht für Eingaben wie ['LOREM', 'ORE', 'R']. Ich habe mir die Freiheit genommen, das zu beheben und Ihre Lösung noch weiter zu verbessern: S=lambda A,s='':A and min((S(A-{a},(s+a[max(i*(s[-i:]==a[:i])for i in range(len(a))):],s)[a in s])for a in A),key=len)or s(eine zweite Zeile wird nicht benötigt). Verbrauch: S({'LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE'})kehrt zurück 'SEDOLOREMAGNAD'.
Flornquake
0

Haskell, 121

import Data.List
a p []=[(length p,p)]
a p s=[r|w<-s,t<-tails w,isInfixOf w$p++t,r<-a(p++t)(s\\[w])]
s=snd.minimum.a ""

Minus zwei, wenn die Funktion nicht an einen Namen gebunden sein muss

Geoff Reedy
quelle