Superstring comune più breve


26

Dato un elenco di stringhe s_0, s_1, ..., s_ntrova la stringa più breve Sche contiene ciascuna s_0, s_1, ..., s_ncome sottostringa .

Esempi :

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

Scrivi il programma (o la funzione) più breve che risolve questo problema. Se lo desideri, puoi rappresentare stringhe come matrici o elenchi di caratteri / numeri interi. Le librerie standard sono OK. Per input / output è possibile utilizzare tutto ciò che è più conveniente: STDIN / STDOUT, prompt utente, parametro / valore di ritorno di una funzione ecc.

Le prestazioni non sono critiche - diciamo, per un input di lunghezza totale <100 caratteri, il risultato deve essere calcolato in <10 secondi su un hardware moderno medio.


3
+1 Bella domanda. Ti suggerisco di includere alcuni esempi aggiuntivi di risultati attesi in modo che le persone possano facilmente valutare se le domande sono in grado di gestire una varietà di casi.
DavidC,

Come devono essere gestiti input / output? Il risultato deve essere stampato o restituito da una funzione?
terremoto del

quindi, no "per ogni stringa, se contiene tutto ..., restituiscilo" non è una soluzione valida?
John Dvorak,

Dubito che ci sarà una risposta. Questa domanda potrebbe adattarsi abbastanza bene allo Stack Overflow (senza la parte del codice golf).
John Dvorak,

Risposte:


8

Python 2, 170 153/157/159

Abbreviato grazie ad alcune idee di 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)

La seconda interruzione di riga non è necessaria.

Ingresso: 'LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE'
Uscita:SEDOLOREMAGNAD

Anche con stringhe di input lunghe, questo funziona in meno di 2 secondi se ci sono al massimo 7 stringhe di input (come nel caso dell'esempio fornito, che gira in 1,7 1,5 secondi sulla mia macchina). Con 8 o più stringhe di input, tuttavia, sono necessari più di 10 secondi, poiché la complessità temporale è O(n!).

Come ha sottolineato Baptiste, range(99)deve essere sostituito con range(len(w))se devono essere supportate lunghezze di input arbitrarie (rendendo la lunghezza totale del codice 157 caratteri). Se devono essere supportate stringhe di input vuote, è necessario modificarle in range(len(w)+1). Penso che range(99)funzioni correttamente per qualsiasi lunghezza di input totale inferiore a 200, però.

Più test:

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

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

5

Mathematica 337 418 372

Dopo aver tentato senza successo di implementare utilizzando Mathematica LongestCommonSubsequencePositions, sono passato al pattern matching.

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]<>""]

La regola di corrispondenza dei modelli,

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

prende una coppia ordinata di parole (rappresentate come elenchi di caratteri) e restituisce: (1) le parole {a,y}e {y,b}seguita da (2) la sottostringa comune y, che collega la fine di una parola con l'inizio dell'altra parola, e, infine, la parola combinata {a,y,b}che sostituirà le parole di input. Vedi Belisarius per un esempio correlato: /mathematica/6144/looking-for-longest-common-substring-solution

Tre caratteri di sottolineatura consecutivi indicano che l'elemento è una sequenza di zero o più caratteri.

Reverseviene utilizzato in seguito per garantire che entrambi gli ordini vengano testati. Le coppie che condividono lettere collegabili vengono restituite invariate e ignorate.

Modifica :

Quanto segue rimuove dall'elenco le parole "sepolte" (cioè completamente contenute) in un'altra parola (in risposta al commento di @ flornquake).

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

Esempio :

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

ritorna

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


uso

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

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

"LOREM"

{0.006256, "SEDOLOREMAGNAD"}


Funziona per l'input "LOREM", "ORE", "R"?
terremoto del

( "LOREM"
Vale a

@flornquake. Bella presa. L'ho affrontato nella versione corrente. Spero di non aver perso altri casi. Grazie.
DavidC,

Nient'altro che il meglio!
DavidC,

3

GolfScript, 66 caratteri

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

Abbastanza breve, ma a causa della complessità esponenziale del tempo (e GolfScript) molto lenta, infrange il limite di tempo di 10 secondi.

Esempi:

['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

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)

Ingresso: ['LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE']
Uscita:SEDOLOREMAGNAD

modificare

Usando reducee alcuni imbrogli di importazione sporca, posso ridurlo ulteriormente (e solo a una riga!):

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)

Modifica 2

Come notato da Flornquake, questo dà risultati errati quando una parola è contenuta in un'altra. La correzione per questo aggiunge altri 13 caratteri:

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)

Ecco la versione pulita:

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')

È possibile radere alcuni personaggi a scapito della correttezza teorica usando range(99)invece di range(len(x))(crediti a flornquake per aver pensato a questo).


Se sei disposto a sacrificare la correttezza, potresti anche usare l'approccio avido o il fattore di approssimazione polinomiale dell'approccio 2.
Peter Taylor,

Bella soluzione! Tuttavia, è necessario verificare se nella superstring sono già presenti nuove parole: 'LOREM', 'ORE', 'R'produce erroneamente l'output LOREMORER.
terremoto del

@flornquake Buona cattura. Sono riuscito a risolverlo ma aggiunge 13 caratteri.
Baptiste M.,

1

Python, 144 caratteri

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]

Saccetta una serie di parole Ache devono ancora essere posizionate e una stringa scontenente parole posizionate finora. Prendiamo una parola restante ada Ae si sovrappongono da 0a len(a)caratteri con la fine s.

Richiede solo circa 0,15 secondi nell'esempio fornito.


Veramente bello! Ma come alcune altre soluzioni, questo non funziona per input come ['LOREM', 'ORE', 'R']. Mi sono preso la libertà di risolverlo e di golf ancora la tua soluzione: 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(non è necessaria una seconda linea). Utilizzo: S({'LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE'})ritorni 'SEDOLOREMAGNAD'.
terremoto

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 ""

Meno due se la funzione non deve essere associata a un nome

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.