Supercorda comum mais curta

26

Dada uma lista de seqüências de caracteres, s_0, s_1, ..., s_nencontre a menor Sque contém cada s_0, s_1, ..., s_numa como uma substring .

Exemplos :

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

Escreva o programa (ou função) mais curto que resolve esse problema. Você pode representar seqüências de caracteres como matrizes ou listas de caracteres / números inteiros, se desejar. Bibliotecas padrão estão OK. Para entrada / saída, você pode usar o que for mais conveniente: STDIN / STDOUT, prompt do usuário, parâmetro / valor de retorno de uma função etc.

O desempenho não é crítico - digamos, para uma entrada de comprimento total <100 caracteres, o resultado deve ser calculado em <10 segundos, em média, no hardware moderno.

Zakharia Stanley
fonte
3
+1 boa pergunta. Sugiro que você inclua alguns exemplos adicionais de resultados esperados para que as pessoas possam avaliar facilmente se os envios são capazes de lidar com uma variedade de casos.
DavidC
Como a entrada / saída deve ser tratada? O resultado deve ser impresso ou retornado de uma função?
Flornquake
então "no" para cada string, se ela contiver todos ..., devolva "não é uma solução válida?
John Dvorak
Duvido que haja uma resposta. Essa pergunta pode se encaixar muito bem no Stack Overflow (sem a parte code-golf).
John Dvorak

Respostas:

8

Python 2, 170 153/157/159

Encurtado graças a algumas das idéias de 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)

A segunda quebra de linha não é necessária.

Entrada: 'LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE'
Saída:SEDOLOREMAGNAD

Mesmo com longas seqüências de entrada, isso é executado em menos de 2 segundos se houver no máximo 7 seqüências de entrada (como é o caso do exemplo dado, que é executado em 1,7 1,5 segundos na minha máquina). Com 8 ou mais seqüências de entrada, no entanto, leva mais de 10 segundos, já que a complexidade do tempo é O(n!).

Como Baptiste apontou, range(99)precisa ser substituído por range(len(w))se comprimentos de entrada arbitrários devem ser suportados (tornando o comprimento total do código 157 caracteres). Se cadeias de entrada vazias devem ser suportadas, ela deve ser alterada para range(len(w)+1). Eu acho que range(99)funciona corretamente para qualquer comprimento total de entrada inferior a 200, no entanto.

Mais testes:

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

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

Mathematica 337 418 372

Depois de tentar implementar sem sucesso usando o Mathematica LongestCommonSubsequencePositions, virei para a correspondência de padrões.

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

A regra de correspondência de padrões,

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

pega um par ordenado de palavras (representado como lista de caracteres) e retorna: (1) as palavras {a,y}e {y,b}seguido por (2) a substring comum y, que vincula o final de uma palavra ao início da outra palavra e, finalmente, a palavra combinada {a,y,b}que substituirá as palavras de entrada. Consulte Belisarius para obter um exemplo relacionado: /mathematica/6144/looking-for-longest-common-substring-solution

Três caracteres de sublinhado consecutivos significam que o elemento é uma sequência de zero ou mais caracteres.

Reverseé empregado posteriormente para garantir que os dois pedidos sejam testados. Os pares que compartilham letras vinculáveis ​​são retornados inalterados e ignorados.

Editar :

O texto a seguir remove da lista as palavras "enterradas" (ou seja, totalmente contidas) em outra palavra (em resposta ao comentário de @ flornquake).

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

Exemplo :

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

retorna

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

DavidC
fonte
Isso funciona para entrada "LOREM", "ORE", "R"?
Flornquake
(Ie, não é produzir a saída correta "LOREM"?)
flornquake
@flornquake. Boa pegada. Eu o abordei na versão atual. Espero não ter perdido outros casos. Obrigado.
10133
Nada além do melhor!
11113 DavidC
3

GolfScript, 66 caracteres

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

Muito curto, mas devido à complexidade de tempo exponencial (e ao GolfScript) muito lento, ele quebra o limite de 10 segundos.

Exemplos:

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

Entrada: ['LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE']
Saída:SEDOLOREMAGNAD

Editar

Usando reducee alguns truques de importação sujos, posso reduzir ainda mais isso (e apenas para uma linha!):

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)

Editar 2

Como observou Flornquake, isso fornece resultados incorretos quando uma palavra está contida em outra. A correção para isso adiciona outros 13 caracteres:

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)

Aqui está a versão limpa:

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

É possível raspar alguns caracteres à custa da correção teórica usando em range(99)vez de range(len(x))(créditos ao tremor por pensar nisso).

Baptiste M.
fonte
Se você estiver disposto a sacrificar a exatidão, use a abordagem gananciosa ou o fator de aproximação polinomial da abordagem 2.
Peter Taylor
Ótima solução! Você precisa verificar se novas palavras já estão na supercorda: no entanto, 'LOREM', 'ORE', 'R'produz incorretamente a saída LOREMORER.
Flornquake #
@flornquake Boa captura. Consegui corrigi-lo, mas ele adiciona 13 caracteres.
Baptiste M.
1

Python, 144 caracteres

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]

Spega um conjunto de palavras Aque ainda precisam ser colocadas e uma sequência scontendo as palavras colocadas até o momento. Nós escolher uma palavra restante ado Ae se sobrepõem-lo de0 de len(a)caracteres com o fim de s.

Leva apenas cerca de 0,15 segundos no exemplo fornecido.

Keith Randall
fonte
Muito bom! Mas, como algumas outras soluções, isso não funciona para entradas como ['LOREM', 'ORE', 'R']. Tomei a liberdade de consertar isso e aprimorar sua solução um pouco mais: 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(não é necessária uma segunda linha). Uso: S({'LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE'})retorna '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 ""

Menos dois se a função não precisar ser vinculada a um nome

Geoff Reedy
fonte