Trouvez les pangrammes les plus courts dans une liste de mots

10

Un pangram est une chaîne qui contient chaque lettre a- zde l'alphabet anglais, insensible à la casse. (C'est OK si le pangram contient plus d'une copie d'une lettre, ou s'il contient des caractères non-lettre en plus des lettres.)

Écrivez un programme ou une fonction dont l'entrée est une liste de chaînes et qui génère une ou plusieurs chaînes ayant les propriétés suivantes:

  • Chaque chaîne de sortie doit être un pangram.
  • Chaque chaîne de sortie doit être formée en concaténant une ou plusieurs chaînes de la liste d'entrée, séparées par des espaces.
  • Chaque chaîne de sortie doit être la plus courte, ou liée pour la plus courte, parmi toutes les chaînes ayant ces propriétés.

De nombreux programmes choisissent de ne sortir qu'une seule chaîne; vous ne voudriez sortir plus d'une chaîne que si vous deviez autrement écrire du code supplémentaire pour limiter la sortie.

Vous pouvez supposer que l'entrée ne contient aucun caractère ou espace non imprimable et qu'aucun mot ne contient plus de (26 fois le logarithme naturel de la longueur de la liste) caractères. (Cependant, vous ne pouvez pas supposer que l'entrée ne contient que des lettres ou uniquement des lettres minuscules; les signes de ponctuation et les lettres majuscules sont tout à fait possibles.)

L'entrée et la sortie peuvent être données dans n'importe quel format raisonnable. Pour tester votre programme, je vous recommande d'utiliser deux cas de test: un dictionnaire de mots anglais (la plupart des ordinateurs en ont un), et le cas suivant (pour lequel un pangram parfait (26 lettres) est impossible, vous devrez donc en trouver un contenant des lettres en double):

abcdefghi
defghijkl
ijklmnop
lmnopqrs
opqrstuvw
rstuvwxyz

Vous devez inclure un échantillon de la sortie de votre programme avec votre soumission. (Cela peut être différent pour différentes personnes en raison de l'utilisation de listes de mots différentes.)

Condition de victoire

Il s'agit d'un défi de . Le gagnant est le programme le plus court (en octets) qui s'exécute en temps polynomial . (Un résumé pour les personnes qui ne savent pas ce que cela signifie: si vous doublez la taille de la liste de mots, le programme ne devrait ralentir que d'un facteur constant. Cependant, le facteur constant en question peut être aussi grand que vous. Par exemple, il est valide pour qu'il devienne quatre fois plus lent ou huit fois plus lent, mais pas pour qu'il devienne plus petit d'un facteur de la longueur de la liste de mots; le facteur par lequel il devient plus lent doit être limité.)


la source
Pour déterminer la complexité, pouvons-nous utiliser le fait que chaque mot comporte au maximum 26 lettres? Que la taille de l'alphabet est une constante de 26?
xnor
Oui. J'ai mis cette restriction sur l'entrée là en partie pour rendre la complexité plus facile à définir / calculer.
Je pense que cela se heurte à une technicité. Si vous ignorez les mots d'entrée répétés, il y a au plus 27 ^ 26 mots d'entrée possibles, et donc au plus 2 ^ (27 ^ 26) sous-ensembles possibles d'entre eux comme entrées possibles. C'est énorme mais constant. Ainsi, tout programme sur cet ensemble fini est à temps constant, la constante étant le nombre maximum de pas effectués sur toutes les entrées possibles.
xnor
Je n'ai pas dit qu'il n'y avait pas de mots en double dans l'entrée. Je suppose que vous pourriez exécuter le programme dans un temps O (n) "technique" en filtrant les signes de ponctuation et en dédupliquant l'entrée en premier, cependant (ou plus probablement O (n log n), qui utiliserait beaucoup moins de mémoire qu'un radix dédupliquer le ferait). Ensuite, vous devrez revenir de la version filtrée à la liste de mots d'origine. Vous ne pouvez cependant pas réclamer le temps polynomial en question à moins que vous ne passiez par toutes ces étapes!
J'avais oublié les non-lettres. Pouvons-nous supposer que ce sont ASCII, ou autrement dans un ensemble fini? Si c'est le cas, je pense que tout algorithme qui commence par la déduplication peut prétendre être du temps polynomial.
xnor

Réponses:

3

Ruby 159 (itératif)

Rubis 227 220 229 227 221 (récursif)

Nouvelle solution itérative (basée sur l'algorithme décrit par @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

Ancienne solution récursive:

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 mesure d'octets est basée sur la suppression de la nouvelle ligne finale dans le fichier, ce qui n'a pas d'importance ruby 2.3.1p112. Le nombre d'octets est remonté après la correction d'un petit bogue (ajout.downcase .upcase pour l'insensibilité à la casse comme requis par l'énoncé du problème).

Voici une version antérieure à avant de raccourcir les identifiants et autres:

#!/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]);

Comment ça marche? Il maintient essentiellement un ensemble de caractères à couvrir et ne revient sur un mot que s'il réduit l'ensemble découvert. De plus, les résultats de la récursivité sont mémorisés. Chaque sous-ensemble de 2 ^ 26 correspond à une entrée de table de mémorisation. Chacune de ces entrées est calculée en temps proportionnellement à la taille du fichier d'entrée. Donc, le tout est O(N)(où Nest la taille du fichier d'entrée), mais avec une énorme constante.

DépriméDaniel
la source
1

JavaScript (ES6), 249 248 octets, éventuellement en concurrence

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]

Explication: Transforme le tableau en convertissant les lettres en masque de bits, en enregistrant uniquement le mot le plus court pour chaque masque de bits dans une carte. Ensuite, en itérant sur une copie de la carte, augmentez la carte en ajoutant chaque masque de bits combiné si la chaîne résultante serait plus courte. Enfin, retournez la chaîne enregistrée pour le bitmap correspondant à un pangram. (Renvoie undefinedsi aucune chaîne de ce type n'existe.)

Neil
la source
Intéressant. Pourriez-vous en dire plus sur son fonctionnement et, si disponible, publier le code non golfé?
DepressedDaniel
1
Cela devrait être une entrée valide / concurrente. Je pense que cela fonctionne en fait dans O ( n log n ), en fait! (La carte a une limite stricte de 2²⁶ entrées, et n'apparaît donc pas dans la complexité; ainsi, le seul temps passé est le temps de lecture de l'entrée.)
Je viens de relire la description et je comprends comment cela fonctionne maintenant. Soigné. +1 ... Hmm, quand décide-t-il d'arrêter d'essayer d'augmenter la carte en considérant les paires? Il devrait continuer jusqu'à ce qu'aucune relaxation ne soit possible.
DepressedDaniel
@DepressedDaniel Pour chaque masque binaire extrait de la liste de mots d'origine, il vérifie tous les pangrammes partiels qu'il a trouvés jusqu'à présent et si l'ajout du mot crée un pangramme plus court que celui qu'il connaît actuellement pour le masque binaire combiné.
Neil
@ ais523 Pour les entrées de grande taille (> 1 000 mots), la plupart du temps semble être consacré à l'échange. J'ai essayé de passer d'une carte à une baie et c'est devenu encore plus lent!
Neil
-1

Python 3, 98 , 94 , 92 octets

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

Itère à travers la représentation ASCII de l'alphabet et ajoute un 1 à une liste si la lettre se trouve dans la chaîne. Si la somme de la liste est supérieure à 25, elle contient toutes les lettres de l'alphabet et sera imprimée.

Erich
la source
Je pense que vous pouvez supprimer un espace entre (' ')et if. Vous pouvez également passer ord(i) in range(65,91)à 91>x>=65. Quelle est également la complexité?
NoOneIsHere
1
Quelle est la complexité de cette solution? Il est nécessaire que la réponse soit en complexité polynomiale, sinon elle n'est pas concurrente.
NoOneIsHere
Désolé, je pense que c'est O (n), car la liste d'entrée peut varier en longueur mais
Erich
Désolé, je pense que c'est O (n), car la liste d'entrée peut varier en longueur mais la deuxième boucle va toujours de 65 à 90. Mais je ne l'ai pas testée.
Erich
Pas sûr que cela satisfasse "Chaque chaîne de sortie doit être la plus courte, ou liée pour la plus courte, parmi toutes les chaînes avec ces propriétés."
DepressedDaniel