Trova i pangrammi più brevi da un elenco di parole


10

Un pangramma è una stringa che contiene ogni lettera a- zdella alfabeto inglese, maiuscole e minuscole. (Va bene se il pangram contiene più di una copia di una lettera o se contiene caratteri non di lettere oltre alle lettere.)

Scrivi un programma o una funzione il cui input è un elenco di stringhe e che genera una o più stringhe con le seguenti proprietà:

  • Ogni stringa di output deve essere un pangram.
  • Ogni stringa di output deve essere formata concatenando una o più stringhe dall'elenco di input, separate da spazi.
  • Ogni stringa di output deve essere la più breve o legata per la più breve, tra tutte le stringhe con queste proprietà.

Molti programmi sceglieranno di produrre solo una stringa; dovresti produrre più di una stringa se altrimenti dovessi scrivere un codice extra per limitare l'output.

Si può presumere che l'input non contenga caratteri o spazi non stampabili e che nessuna parola contenga più di (26 volte il logaritmo naturale della lunghezza dell'elenco) caratteri. (Non puoi supporre, tuttavia, che l'input contenga solo lettere o solo lettere minuscole; i segni di punteggiatura e le lettere maiuscole sono del tutto possibili.)

Input e output possono essere forniti in qualsiasi formato ragionevole. Per testare il tuo programma, ti consiglio di usare due casi di test: un dizionario di parole inglesi (la maggior parte dei computer ne ha uno) e il seguente caso (per il quale un pangram perfetto (26 lettere) è impossibile, quindi dovresti trovarne uno contenente lettere duplicate):

abcdefghi
defghijkl
ijklmnop
lmnopqrs
opqrstuvw
rstuvwxyz

Dovresti includere un campione dell'output del tuo programma insieme al tuo invio. (Questo potrebbe essere diverso per persone diverse a causa dell'uso di elenchi di parole diverse.)

Condizione di vittoria

Questa è una sfida di . Il vincitore è il programma più breve (in byte) che viene eseguito in tempo polinomiale . (Un riepilogo per le persone che non sanno cosa significhi: se si raddoppia la dimensione dell'elenco di parole, il programma dovrebbe diventare più lento di non più di un fattore costante. Tuttavia, il fattore costante in questione può essere grande quanto te Ad esempio, è valido per diventare quattro volte più lento, o otto volte più lento, ma non per ridurlo di un fattore della lunghezza dell'elenco di parole; il fattore attraverso il quale diventa più lento deve essere limitato.)


Nel determinare la complessità, possiamo usare il fatto che ogni parola è lunga al massimo 26 lettere? Che la dimensione dell'alfabeto sia una costante di 26?
xnor

Sì. Ho messo quella restrizione sull'input lì in parte per rendere la complessità più facile da definire / calcolare.

Penso che questo si traduca in un tecnicismo. Se si ignorano le parole di input ripetute, ci sono al massimo 27 ^ 26 parole di input possibili, e quindi al massimo 2 ^ (27 ^ 26) possibili sottoinsiemi come possibili input. Questo è enorme ma costante. Quindi, qualsiasi programma su questo set finito è a tempo costante, con la costante che rappresenta il numero massimo di passi effettuati su tutti i possibili input.
xnor

Non ho detto che non ci sono parole duplicate nell'input. Immagino che potresti eseguire il programma in un tempo "tecnico" O (n) filtrando i segni di punteggiatura e deduplicando prima l'input, sebbene (o più probabilmente O (n log n), che userebbe molta meno memoria di un radix deduplicato). Quindi dovresti tornare dalla versione filtrata all'elenco di parole originale. Tuttavia, non puoi rivendicare il tempo polinomiale in questione se non esegui effettivamente tutti questi passaggi!

Mi ero dimenticato delle non lettere. Possiamo supporre che questi siano ASCII, o comunque all'interno di un set finito? In tal caso, penso che qualsiasi algoritmo che inizia con la deduplicazione possa pretendere di essere polinomiale.
xnor

Risposte:


3

Ruby 159 (iterativo)

Rubino 227 220 229 227 221 (ricorsivo)

Nuova soluzione iterativa (basata sull'algoritmo descritto da @Niel):

c={('A'..'Z').to_a=>""}
while l=gets
d=c.clone
c.map{|k,v|j=k-l.upcase.chars
w=v+" "+l.strip
d[j]=w if !c[j]||c[j].size<w.size}
c=d
end
x=c[[]]
p x[1..-1] if x

Vecchia soluzione ricorsiva:

W=[]
while l=gets
W<<l.strip
end
I=W.join(" ")+"!!"
C={[]=>""}
def o(r)if C[r]
C[r]
else
b=I
W.map{|x|s=r-x.upcase.chars
if s!=r
c=x+" "+o(s)
b=c if c.size<b.size
end}
C[r]=b
end
end
r=o ('A'..'Z').to_a
p r[0..-2] if r!=I

La misurazione dei byte si basa sull'interruzione della nuova riga finale nel file, il che non ha importanza ruby 2.3.1p112. Il conteggio dei byte è tornato indietro dopo aver corretto un piccolo bug (aggiunta.downcase .upcase per la distinzione tra maiuscole e minuscole come richiesto dalla dichiarazione del problema).

Ecco una versione precedente di precedenti identificativi di accorciamento e simili:

#!/usr/bin/env ruby

$words = [];

while (line=gets)
  $words << line[0..-2];
end

$impossible = $words.join(" ")+"!!";

$cache = {};

def optimize(remaining)
  return $cache[remaining] if ($cache[remaining]);
  return "" if (remaining == []);

  best = $impossible;

  $words.each{|word|
    remaining2 = remaining - word.chars;
    if (remaining2 != remaining)
      curr = word + " " + optimize(remaining2);
      best = curr if (curr.length < best.length);
    end
  };

  $stderr.puts("optimize(#{remaining.inspect})=#{best.inspect}");

  return $cache[remaining] = best;
end

result = optimize(('a'..'z').to_a);

puts(result[0..-1]);

Come funziona? Mantiene sostanzialmente un insieme di personaggi ancora da coprire e si ricorre a una parola solo se ridurrebbe il set scoperto. Inoltre, i risultati della ricorsione sono memorizzati. Ogni sottoinsieme di 2 ^ 26 corrisponde a una voce della tabella di memoization. Ciascuna di queste voci viene calcolata in tempo proporzionale alla dimensione del file di input. Quindi il tutto è O(N)(dov'è Nla dimensione del file di input), sebbene con un'enorme costante.


1

JavaScript (ES6), 249 248 byte, possibilmente in competizione

a=>a.map(w=>w.replace(/[a-z]/gi,c=>b|=1<<parseInt(c,36)-9,b=0,l=w.length)&&(m.get(b)||[])[0]<l||m.set(b,[l,w]),m=new Map)&&[...m].map(([b,[l,w]])=>m.forEach(([t,s],e)=>(m.get(e|=b)||[])[0]<=t+l||m.set(e,[t+l+1,s+' '+w])))&&(m.get(-2^-1<<27)||[])[1]

Spiegazione: trasforma l'array convertendo le lettere in una maschera di bit, salvando solo la parola più breve per ogni maschera di bit in una mappa. Quindi ripetendo una copia della mappa, aumenta la mappa aggiungendo ciascuna maschera di bit combinata se la stringa risultante sarebbe più corta. Restituisce infine la stringa salvata per la bitmap corrispondente a un pangram. (Restituisce undefinedse tale stringa non esiste.)


Interessante. Potresti espatriare di più su come funziona e, se disponibile, pubblicare il codice non golfato?
Depresso

1
Questa dovrebbe essere una voce valida / concorrente. Penso che questo effettivamente funzioni in O ( n log n ), in effetti! (La mappa ha un limite massimo di 2²⁶ voci, e quindi non si presenta nella complessità; quindi l'unico tempo impiegato è il tempo a leggere l'input.)

Ho appena riletto la descrizione e ho capito come funziona ora. Neat. +1 ... Hmm, quando decide di smettere di provare ad aumentare la mappa considerando le coppie? Dovrebbe andare avanti fino a quando non è possibile rilassarsi.
Depresso

@DepressedDaniel Per ogni maschera di bit estratta dall'elenco di parole originale, controlla tutti i diagrammi parziali che ha trovato finora e se l'aggiunta della parola crea un diagramma più corto di quello che attualmente conosce per la maschera di bit combinata.
Neil,

@ ais523 Per input di grandi dimensioni (> 1000 parole), la maggior parte del tempo sembra essere stata scambiata. Ho provato a passare da una mappa a una matrice e è diventato ancora più lento!
Neil,

-1

Python 3, 98 , 94 , 92 byte

print([s for s in input().split()if sum([1 for c in range(65,91)if chr(c)in s.upper()])>25])

Scorre la rappresentazione ASCII dell'alfabeto e aggiunge un 1 a un elenco se la lettera viene trovata nella stringa. Se la somma dell'elenco è maggiore di 25, contiene tutte le lettere dell'alfabeto e verrà stampata.


Penso che puoi rimuovere uno spazio tra (' ')e if. Puoi anche passare ord(i) in range(65,91)a 91>x>=65. Inoltre, qual è la complessità?
NoOneIsHere

1
Qual è la complessità di questa soluzione? È necessario che la risposta sia in complessità polinomiale, altrimenti non è competitiva.
NoOneIsHere

Siamo spiacenti, penso che sia O (n), perché l'elenco di input può variare in lunghezza ma
Erich

Mi dispiace, penso che sia O (n), perché l'elenco di input può variare in lunghezza ma il secondo ciclo va sempre da 65 a 90. Ma non l'ho provato.
Erich,

Non sono sicuro che ciò soddisfi "Ogni stringa di output deve essere la più breve o legata per la più breve, tra tutte le stringhe con queste proprietà".
Depresso
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.