agrafe de batterie de cheval de cerveau

48

Objectif

À partir d’une liste de mots de passe composés de trois mots, déchiffrez-les tous. Chaque fois que vous devinez, vous recevrez un indice dans le style de Mastermind , décrivant combien de caractères correspondent au mot de passe et combien se trouvent dans leur position correcte. L'objectif est de minimiser le nombre total de suppositions sur tous les cas de test.

Mots de passe

Dans la liste de mots par défaut de mon système, j'ai choisi au hasard 10 000 mots distincts pour constituer le dictionnaire de ce défi. Tous les mots sont composés de a-zseulement. Ce dictionnaire peut être trouvé ici ( brut ).

À partir de ce dictionnaire, j'ai généré 1000 phrases secrètes composées de trois mots aléatoires séparés par des espaces chacun ( apple jacks feverpar exemple). Des mots individuels peuvent être réutilisés dans chaque phrase secrète ( hungry hungry hippos). Vous pouvez trouver la liste des mots de passe ici ( brute ), avec un par ligne.

Votre programme peut utiliser / analyser le fichier de dictionnaire comme bon vous semble. Vous ne pouvez pas analyser les phrases de passe à optimiser pour cette liste spécifique. Votre algorithme devrait quand même fonctionner avec une liste de phrases différente

Devinant

Pour deviner, vous soumettez une chaîne à un vérificateur. Il ne devrait retourner que :

  • Le nombre de caractères de votre chaîne également dans la phrase secrète ( pas à la bonne position)
  • Le nombre de caractères dans la position correcte

Si votre chaîne est une correspondance parfaite, il peut produire quelque chose indiquant que (le mien utilise -1pour la première valeur).

Par exemple, si la phrase secrète est the big catet que vous devinez tiger baby mauling, le vérificateur devrait être renvoyé 7,1. 7 caractères ( ige<space>ba<space>) sont dans les deux chaînes mais dans des positions différentes, et 1 ( t) est dans la même position dans les deux. Notez que les espaces comptent.

J'ai écrit un exemple (lu: pas optimisé) en Java, mais n'hésitez pas à écrire le vôtre tant qu'il ne donne que les informations requises.

int[] guess(String in){
    int chars=0, positions=0;
    String pw = currentPassword; // set elsewhere, contains current pass
    for(int i=0;i<in.length()&&i<pw.length();i++){
        if(in.charAt(i)==pw.charAt(i))
            positions++;
    }
    if(positions == pw.length() && pw.length()==in.length())
        return new int[]{-1,positions};
    for(int i=0;i<in.length();i++){
        String c = String.valueOf(in.charAt(i));
        if(pw.contains(c)){
            pw = pw.replaceFirst(c, "");
            chars++;
        }
    }
    chars -= positions;
    return new int[]{chars,positions};
}

Notation

Votre score est simplement le nombre de suppositions que vous soumettez au vérificateur (en comptant la dernière, la bonne) pour toutes les phrases de test. Le score le plus bas gagne.

Vous devez déchiffrer toutes les phrases de la liste. Si votre programme échoue sur l'un d'entre eux, il est invalide.

Votre programme doit être déterministe. Si exécuté deux fois sur le même ensemble de phrases secrètes, il devrait renvoyer le même résultat.

En cas d'égalité pour la première fois, je vais exécuter les entrées à égalité sur mon ordinateur quatre fois chacune, et le temps moyen le plus bas pour résoudre les 1000 cas l'emporte. Mon ordinateur fonctionne sous Ubuntu 14.04, avec un i7-3770K et 16 Go de mémoire vive, au cas où cela ferait une différence pour votre programme. Pour cette raison, et pour faciliter les tests, votre réponse doit être dans une langue avec un compilateur / interprète téléchargeable gratuitement à partir du Web (à l'exception des essais gratuits) et ne nécessitant pas d'inscription.

Titre adapté de XKCD

Géobits
la source
1
Titre de xkcd.com/936
NinjaBearMonkey
Puis-je mettre des caractères autres que a..z et un espace dans la chaîne à soumettre?
Ray
@Ray Je ne vois pas de raison pour laquelle ne pas en avoir pour le moment, mais je ne suis pas sûr de ce que cela vous rapportera. Allez-y, je suis curieux.
Geobits
3
Les humains peuvent-ils se soumettre? Je commencerai par: "ça vaut le coup"
AndoDaan
2
@AndoDaan Pour la première phrase? 9 0. Cela pourrait prendre un certain temps: P
Geobits

Réponses:

13

Scala 9146 (min 7, max 15, moyenne 9.15) durée: 2000 secondes

Comme beaucoup d'entrées, je commence par obtenir la longueur totale, puis les espaces, un peu plus d'informations, la réduction aux candidats restants et des devinettes.

Inspiré par la bande dessinée originale xkcd, j'ai essayé d'appliquer ma compréhension rudimentaire de la théorie de l'information. Il y a un billion de phrases possibles ou un peu moins de 40 bits d'entropie. Je me suis fixé comme objectif moins de 10 suppositions par phrase de test, ce qui signifie que nous devons apprendre en moyenne près de 5 bits par requête (car la dernière est inutile). À chaque conjecture, nous obtenons deux nombres et, grosso modo, plus l'éventail de ces nombres est grand, plus nous nous attendons à en apprendre.

Pour simplifier la logique, j’utilise chaque requête comme deux questions distinctes. Chaque chaîne de devinettes est donc composée de deux parties: un côté gauche s'intéresse au nombre de positions correctes (chevilles noires dans le cerveau) et un côté droit s’intéresse au nombre de caractères corrects ( total des piquets). Voici un jeu typique:

Phrase:        chasteness legume such
 1: p0 ( 1/21) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -aaaaaaaaaaaabbbbbbbbbcccccccccdddddddddeeeeeeeeeeeeeeefffffffffgggggggggggghhhhhhhhhiiiiiiiiiiiiiiiiiijjjjjjkkkkkkkkkllllllllllllmmmmmmmmmnnnnnnnnnnnnoooooooooooopppppppppqqqrrrrrrrrrrrrssssssssssssssstttttttttuuuuuuuuuuuuvvvvvvwwwwwwxxxyyyyyyyyyzzzzzz
 2: p1 ( 0/ 8)   -  - -  ---    - ---aaaaaaaaaaaadddddddddeeeeeeeeeeeeeeefffffffffjjjjjjkkkkkkkkkllllllllllllooooooooooooqqqwwwwwwxxxyyyyyyyyyzzzzzz
 3: p1 ( 0/11) ----- ------ ---------bbbbbbbbbdddddddddeeeeeeeeeeeeeeefffffffffgggggggggggghhhhhhhhhiiiiiiiiiiiiiiiiiikkkkkkkkkllllllllllllppppppppptttttttttvvvvvv
 4: p1 ( 2/14) ---------- ------ ----ccccccccceeeeeeeeeeeeeeehhhhhhhhhkkkkkkkkkllllllllllllmmmmmmmmmooooooooooooqqqrrrrrrrrrrrrsssssssssssssssvvvvvvwwwwwwzzzzzz
 5: p3 ( 3/ 3) iaaiiaaiai iaaiia iaaiaaaaaaaaaaaabbbbbbbbbdddddddddiiiiiiiiiiiiiiiiiikkkkkkkkkllllllllllllqqquuuuuuuuuuuuvvvvvvyyyyyyyyy
 6: p3 ( 3/11) aaaasassaa aaaasa aaaaaaaaaaaaaaaabbbbbbbbbcccccccccdddddddddfffffffffhhhhhhhhhppppppppprrrrrrrrrrrrssssssssssssssstttttttttuuuuuuuuuuuuwwwwwwxxxyyyyyyyyy
 7: p4 ( 4/10) accretions shrive pews
 8: p4 ( 4/ 6) barometric terror heir
 9: p4 SUCCESS chasteness legume such

Espaces de devinettes

Chaque estimation d'espace peut renvoyer au plus 2 piquets noirs; J'ai essayé de construire des hypothèses pour renvoyer 0,1 et 2 chevilles avec des probabilités 1 / 4,1 / 2 et 1/4 respectivement. Je pense que c’est le mieux que vous puissiez faire pour un volume d’information attendu de 1,5 bit. J'ai opté pour une chaîne en alternance pour la première estimation, suivie de celles générées aléatoirement, bien qu'il soit généralement intéressant de commencer à deviner dès la deuxième ou la troisième tentative, car nous connaissons les fréquences de longueur des mots.

L'apprentissage des jeux de caractères compte

Pour les suppositions du côté droit, je choisis des ensembles de caractères aléatoires (toujours 2 de e / i / a / s) afin que le nombre attendu renvoyé soit égal à la moitié de la longueur de la phrase. Une variance plus élevée signifie plus d'informations et d'après la page wikipedia sur la distribution binomiale, je calcule environ 3,5 bits par requête (au moins pour les premiers avant que les informations ne deviennent redondantes). Une fois l'espacement connu, j'utilise des chaînes aléatoires des lettres les plus courantes sur le côté gauche, choisies de manière à ne pas entrer en conflit avec le côté droit.

Coalescence des candidats restants

Ce jeu est un compromis entre vitesse de traitement / efficacité de la requête et l'énumération des candidats restants peut prendre très longtemps sans informations structurées telles que des caractères spécifiques. J'ai optimisé cette partie en recueillant principalement des informations invariantes dans l'ordre des mots, ce qui me permet de pré-calculer les comptes de jeux de caractères pour chaque mot et de les comparer avec les comptes tirés des requêtes. Je compresse ces nombres dans un entier long, en utilisant le comparateur d'égalité de la machine et l'additionneur pour tester tous mes nombres de caractères en parallèle. C'était une victoire énorme. Je peux emballer jusqu'à 9 chefs d'accusation dans le Long, mais j'ai trouvé que la collecte des informations supplémentaires n'en valait pas la peine et j'ai opté pour 6 à 7.

Une fois que les candidats restants sont connus, si l'ensemble est raisonnablement petit, je sélectionne celui dont le journal prévu est le plus faible des candidats restants. Si le jeu est assez grand pour que cela prenne beaucoup de temps, je choisis un petit jeu d'échantillons.

Merci tout le monde. C'était un jeu amusant et m'a incité à m'inscrire sur le site.

Mise à jour: code épuré pour plus de simplicité et de lisibilité, avec des ajustements mineurs à l’algorithme, ce qui a permis d’améliorer le score.
Score original: 9447 (min 7, max 13, moyenne 9,45) temps: 1876 secondes

Le nouveau code est 278 lignes de Scala, ci-dessous

object HorseBatteryStapleMastermind {
  def main(args: Array[String]): Unit = run() print ()

  val n = 1000       // # phrases to run
  val verbose = true // whether to print each game

  //tweakable parameters
  val prob = 0.132   // probability threshold to guess spacing 
  val rngSeed = 11   // seed for random number generator
  val minCounts = 6  // minimum char-set counts before guessing

  val startTime = System.currentTimeMillis()
  def time = System.currentTimeMillis() - startTime

  val phraseList = io.Source.fromFile("pass.txt").getLines.toArray
  val wordList = io.Source.fromFile("words.txt").getLines.toArray

  case class Result(num: Int = 0, total: Int = 0, min: Int = Int.MaxValue, max: Int = 0) {
    def update(count: Int) = Result(num + 1, total + count, Math.min(count, min), Math.max(count, max))

    def resultString = f"#$num%4d  Total: $total%5d  Avg: ${total * 1.0 / num}%2.2f  Range: ($min%2d-$max%2d)"
    def timingString = f"Time:  Total: ${time / 1000}%5ds Avg: ${time / (1000.0 * num)}%2.2fs"
    def print() = println(s"$resultString\n$timingString")
  }

  def run(indices: Set[Int] = (0 until n).to[Set], prev: Result = Result()): Result = {
    if (verbose && indices.size < n) prev.print()

    val result = prev.update(Querent play Oracle(indices.head, phraseList(indices.head)))

    if (indices.size == 1) result else run(indices.tail, result)
  }

  case class Oracle(idx: Int, phrase: String) {
    def query(guess: String) = Grade.compute(guess, phrase)
  }

  object Querent {

    def play(oracle: Oracle, n: Int = 0, notes: Notes = Notes0): Int = {
      if (verbose && n == 0) println("=" * 100 + f"\nPhrase ${oracle.idx}%3d:    ${oracle.phrase}")

      val guess = notes.bestGuess
      val grade = oracle.query(guess)

      if (verbose) println(f"${n + 1}%2d: p${notes.phase} $grade $guess")

      if (grade.success) n + 1 else play(oracle, n + 1, notes.update(guess, grade))
    }

    abstract class Notes(val phase: Int) {
      def bestGuess: String
      def update(guess: String, grade: Grade): Notes
    }

    case object Notes0 extends Notes(0) {
      def bestGuess = GuessPack.firstGuess

      def genSpaceCandidates(grade: Grade): List[Spacing] = (for {
        wlen1 <- WordList.lengthRange
        wlen2 <- WordList.lengthRange
        spacing = Spacing(wlen1, wlen2, grade.total)
        if spacing.freq > 0
        if grade.black == spacing.black(bestGuess)
      } yield spacing).sortBy(-_.freq).toList

      def update(guess: String, grade: Grade) =
        Notes1(grade.total, genSpaceCandidates(grade), Limiter(Counts.withMax(grade.total - 2), Nil), GuessPack.stream)
    }

    case class Notes1(phraseLength: Int, spacingCandidates: List[Spacing], limiter: Limiter, guesses: Stream[GuessPack]) extends Notes(1) {
      def bestGuess = (chance match {
        case x if x < prob => guesses.head.spacing.take(phraseLength)
        case _             => spacingCandidates.head.mkString
      }) + guesses.head.charSet

      def totalFreq = spacingCandidates.foldLeft(0l)({ _ + _.freq })
      def chance = spacingCandidates.head.freq * 1.0 / totalFreq

      def update(guess: String, grade: Grade) = {
        val newLim = limiter.update(guess, grade)
        val newCands = spacingCandidates.filter(_.black(guess) == grade.black)

        newCands match {
          case best :: Nil if newLim.full => Notes3(newLim.allCandidates(best))
          case best :: Nil                => Notes2(best, newLim, guesses.tail)
          case _                          => Notes1(phraseLength, newCands, newLim, guesses.tail)
        }
      }
    }

    case class Notes2(spacing: Spacing, limiter: Limiter, guesses: Stream[GuessPack]) extends Notes(2) {
      def bestGuess = tile(guesses.head.pattern) + guesses.head.charSet

      def whiteSide(guess: String): String = guess.drop(spacing.phraseLength)
      def blackSide(guess: String): String = guess.take(spacing.phraseLength)

      def tile(guess: String) = spacing.lengths.map(guess.take).mkString(" ")
      def untile(guess: String) = blackSide(guess).split(" ").maxBy(_.length) + "-"

      def update(guess: String, grade: Grade) = {
        val newLim = limiter.updateBoth(whiteSide(guess), untile(guess), grade)

        if (newLim.full)
          Notes3(newLim.allCandidates(spacing))
        else
          Notes2(spacing, newLim, guesses.tail)
      }
    }

    case class Notes3(candidates: Array[String]) extends Notes(3) {
      def bestGuess = sample.minBy(expLogNRC)

      def update(guess: String, grade: Grade) =
        Notes3(candidates.filter(phrase => grade == Grade.compute(guess, phrase)))

      def numRemCands(phrase: String, guess: String): Int = {
        val grade = Grade.compute(guess, phrase)
        sample.count(phrase => grade == Grade.compute(guess, phrase))
      }

      val sample = if (candidates.size <= 32) candidates else candidates.sortBy(_.hashCode).take(32)

      def expLogNRC(guess: String): Double = sample.map(phrase => Math.log(1.0 * numRemCands(phrase, guess))).sum
    }

    case class Spacing(wl1: Int, wl2: Int, phraseLength: Int) {
      def wl3 = phraseLength - 2 - wl1 - wl2
      def lengths = Array(wl1, wl2, wl3)
      def pos = Array(wl1, wl1 + 1 + wl2)
      def freq = lengths.map(WordList.freq).product
      def black(guess: String) = pos.count(guess(_) == ' ')
      def mkString = lengths.map("-" * _).mkString(" ")
    }

    case class Limiter(counts: Counts, guesses: List[String], extraGuesses: List[(String, Grade)] = Nil) {
      def full = guesses.size >= minCounts

      def update(guess: String, grade: Grade) =
        if (guesses.size < Counts.Max)
          Limiter(counts.update(grade.total - 2), guess :: guesses)
        else
          Limiter(counts, guesses, (guess, grade) :: extraGuesses)

      def updateBoth(whiteSide: String, blackSide: String, grade: Grade) =
        Limiter(counts.update(grade.total - 2).update(grade.black - 2), blackSide :: whiteSide :: guesses)

      def isCandidate(phrase: String): Boolean = extraGuesses forall {
        case (guess, grade) => grade == Grade.compute(guess, phrase)
      }

      def allCandidates(spacing: Spacing): Array[String] = {

        val order = Array(0, 1, 2).sortBy(-spacing.lengths(_)) //longest word first
        val unsort = Array.tabulate(3)(i => order.indexWhere(i == _))

        val wordListI = WordList.byLength(spacing.lengths(order(0)))
        val wordListJ = WordList.byLength(spacing.lengths(order(1)))
        val wordListK = WordList.byLength(spacing.lengths(order(2)))

        val gsr = guesses.reverse
        val countsI = wordListI.map(Counts.compute(_, gsr).z)
        val countsJ = wordListJ.map(Counts.compute(_, gsr).z)
        val countsK = wordListK.map(Counts.compute(_, gsr).z)

        val rangeI = 0 until wordListI.size
        val rangeJ = 0 until wordListJ.size
        val rangeK = 0 until wordListK.size

        (for {
          i <- rangeI.par
          if Counts(countsI(i)) <= counts
          j <- rangeJ
          countsIJ = countsI(i) + countsJ(j)
          if Counts(countsIJ) <= counts
          k <- rangeK
          if countsIJ + countsK(k) == counts.z
          words = Array(wordListI(i), wordListJ(j), wordListK(k))
          phrase = unsort.map(words).mkString(" ")
          if isCandidate(phrase)
        } yield phrase).seq.toArray
      }
    }

    object Counts {
      val Max = 9
      val range = 0 until Max
      def withMax(size: Int): Counts = Counts(range.foldLeft(size.toLong) { (z, i) => (z << 6) | size })

      def compute(word: String, x: List[String]): Counts = x.foldLeft(Counts.withMax(word.length)) { (c: Counts, s: String) =>
        c.update(if (s.last == '-') Grade.computeBlack(word, s) else Grade.computeTotal(word, s))
      }
    }

    case class Counts(z: Long) extends AnyVal {
      @inline def +(that: Counts): Counts = Counts(z + that.z)
      @inline def apply(i: Int): Int = ((z >> (6 * i)) & 0x3f).toInt
      @inline def size: Int = this(Counts.Max)

      def <=(that: Counts): Boolean =
        Counts.range.forall { i => (this(i) <= that(i)) && (this.size - this(i) <= that.size - that(i)) }

      def update(c: Int): Counts = Counts((z << 6) | c)
      override def toString = Counts.range.map(apply).map(x => f"$x%2d").mkString(f"Counts[$size%2d](", " ", ")")
    }

    case class GuessPack(spacing: String, charSet: String, pattern: String)

    object GuessPack {
      util.Random.setSeed(rngSeed)
      val RBF: Any => Boolean = _ => util.Random.nextBoolean() //Random Boolean Function

      def genCharsGuess(q: Char => Boolean): String =
        (for (c <- 'a' to 'z' if q(c); j <- 1 to WordList.maxCount(c)) yield c).mkString

      def charChooser(i: Int)(c: Char): Boolean = c match {
        case 'e' => Array(true, true, true, false, false, false)(i % 6)
        case 'i' => Array(false, true, false, true, false, true)(i % 6)
        case 'a' => Array(true, false, false, true, true, false)(i % 6)
        case 's' => Array(false, false, true, false, true, true)(i % 6)
        case any => RBF(any)
      }

      def genSpaceGuess(q: Int => Boolean = RBF): String = genPatternGuess(" -", q)

      def genPatternGuess(ab: String, q: Int => Boolean = RBF) =
        (for (i <- 0 to 64) yield (if (q(i)) ab(0) else ab(1))).mkString

      val firstGuess = genSpaceGuess(i => (i % 2) == 1) + genCharsGuess(_ => true)

      val stream: Stream[GuessPack] = Stream.from(0).map { i =>
        GuessPack(genSpaceGuess(), genCharsGuess(charChooser(i)), genPatternGuess("eias".filter(charChooser(i))))
      }
    }
  }

  object WordList {
    val lengthRange = wordList.map(_.length).min until wordList.map(_.length).max

    val byLength = Array.tabulate(lengthRange.end)(i => wordList.filter(_.length == i))

    def freq(wordLength: Int): Long = if (lengthRange contains wordLength) byLength(wordLength).size else 0

    val maxCount: Map[Char, Int] = ('a' to 'z').map(c => (c -> wordList.map(_.count(_ == c)).max * 3)).toMap
  }

  object Grade {
    def apply(black: Int, white: Int): Grade = Grade(black | (white << 8))
    val Success = Grade(-1)

    def computeBlack(guess: String, phrase: String): Int = {
      @inline def posRange: Range = 0 until Math.min(guess.length, phrase.length)
      @inline def sameChar(p: Int): Boolean = (guess(p) == phrase(p)) && guess(p) != '-'
      posRange count sameChar
    }

    def computeTotal(guess: String, phrase: String): Int = {
      @inline def minCount(c: Char): Int = Math.min(phrase.count(_ == c), guess.count(_ == c))
      minCount(' ') + ('a' to 'z').map(minCount).sum
    }

    def compute(guess: String, phrase: String): Grade = {
      val black = computeBlack(guess, phrase)
      if (black == guess.length && black == phrase.length)
        Grade.Success
      else
        Grade(black, computeTotal(guess, phrase) - black)
    }
  }

  case class Grade(z: Int) extends AnyVal {
    def black: Int = z & 0xff
    def white: Int = z >> 8
    def total: Int = black + white
    def success: Boolean = this == Grade.Success
    override def toString = if (success) "SUCCESS" else f"($black%2d/$white%2d)"
  }
}
Peter Weistroffer
la source
2
Bienvenue sur le site et félicitations! Vous n'avez pas beaucoup coupé la prime, mais vous l'avez fait. Ayez des points Internet imaginaires!
Geobits
Tout simplement brillant.
Solution géniale! C'est le seul en dessous de la barre des 10 000!
Sanjay Jain
15

C - total: 37171, min: 24, max: 55, temps: environ 10 secondes

J'ai emprunté l'idée de Ray de trouver la longueur de chaque mot en devinant avec des espaces, sauf que je fais une recherche binaire plutôt que linéaire, ce qui me permet d'économiser beaucoup de suppositions.

Une fois que je détermine la longueur d'un mot, j'imagine que le premier mot correspond à sa longueur et j'enregistre le nombre de positions correctes. Ensuite, je sélectionne le premier mot de l'ensemble des mots qui partagent le même nombre de positions que le mot mystère. Pour ma troisième conjecture, je sélectionne le premier mot de l'ensemble des mots partageant le même nombre de positions que mon mot mystère et le même nombre de positions que ma deuxième conjecture avec le mot mystère, etc.

En utilisant cette méthode, je suis capable de deviner chaque mot, un à la fois, en environ 5 à 10 suppositions. Évidemment le troisième mot que je dois faire un peu différemment parce que je ne connais pas sa longueur, mais la méthode est similaire. J'utilise un fichier contenant une matrice du nombre de positions que chaque mot partage en commun et que j'ai précalculées. La majeure partie de l'exécution est engagée lors du chargement des données précalculées. Vous pouvez tout télécharger ici .

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>

#define DICTIONARY_SIZE 10000
#define PHRASE_COUNT 1000
#define MAX_STRING 512
#define MAX_SAVED_GUESSES 100
#define DEBUG

typedef struct {
    int wordlen;
    char word[MAX_STRING];
} dictionary_entry;

static int g_guesses;
static int g_max_word_len;
static int g_min_word_len;
static char *g_password;
static dictionary_entry g_dictionary[DICTIONARY_SIZE];
static char g_phrases[PHRASE_COUNT][MAX_STRING];
static int g_pos_matrix[DICTIONARY_SIZE][DICTIONARY_SIZE];

/* Returns true if the guess was correct and false otherwise */
int guess(char *in, int *chars, int *positions)
{
    int i, j, contains;
    char c, pw[1024];

    /* Increment the total guess count */
    g_guesses++;

    strcpy(pw, g_password);
    *chars = 0;
    *positions = 0;
    for (i = 0; (i < strlen(in)) && (i < strlen(pw)); i++)
        if (in[i] == pw[i])
            (*positions)++;
    if (strcmp(in, pw) == 0) {
        *chars = -1;
        return 1;
    }
    for (i = 0; i < strlen(in); i++) {
        for (j = 0; j < strlen(pw); j++) {
            if (pw[j] == in[i]) {
                (*chars)++;
                pw[j] = '*';
                break;
            }
        }
    }
    (*chars) -= (*positions);
    return 0;
}

int checker() {
    char guess_str[MAX_STRING], *guess_ptr;
    int i, j, saved_guesses, word;
    int guesses;
    int chars, positions;
    int wordlen[3], wordidx[3];
    int guesswordidx[MAX_SAVED_GUESSES];
    int guesswordpos[MAX_SAVED_GUESSES];
    int tryit, finished;

    /* Initialize everything */
    finished = 0;
    guess_ptr = guess_str;
    for (i = 0; i < 3; i++) {
        wordlen[i] = -1;
        wordidx[i] = -1;
    }

    guesses = 0;
    for (word = 0; word < 3; word++) {
        saved_guesses = 0;

        // If we're not on the last word, figure out how long the word is by
        // doing a binary search using spaces
        if (word < 2) {
            int a = g_min_word_len, b = g_max_word_len;
            int c;
            while (wordlen[word] == -1) {
                c = (b + a) / 2;
                for (i = 0; i <= c; i++) {
                    guess_ptr[i] = ' ';
                }
                guess_ptr[i] = '\0';
                guess(guess_str, &chars, &positions);
                guesses++;
                if (positions == 0) {
                    if (b - a < 2)
                        wordlen[word] = b;
                    a = c;
                } else {
                    if (b - a < 2)
                        wordlen[word] = c;
                    b = c;
                }
            }
            #ifdef DEBUG
            printf("\tLength of next word is %d.\n", wordlen[word]);
            #endif
        }


        // Look for words using matching positions from previous guesses to improve our search
        for (i = 0; i < DICTIONARY_SIZE; i++) {
            tryit = 1;
            for (j = 0; j < saved_guesses; j++) {
                if (g_pos_matrix[guesswordidx[j]][i] != guesswordpos[j]) {
                    tryit = 0;
                    break;
                }
            }
            // If the word length is incorrect then don't bother
            if ((word < 2) && (g_dictionary[i].wordlen != wordlen[word]))
                tryit = 0;
            if (tryit) {
                strcpy(guess_ptr, g_dictionary[i].word);
                guess(guess_str, &chars, &positions);
                guesses++;
                #ifdef DEBUG
                printf("\tWe guessed \"%s\", it had %d correct positions.\n", g_dictionary[i].word, positions);
                #endif
                guesswordidx[saved_guesses] = i;
                guesswordpos[saved_guesses] = positions;
                saved_guesses++;

                // If we're on the last word and all the positions matched then check if we've found the phrase
                if ((word == 2) && (g_dictionary[i].wordlen == positions)) {
                    sprintf(guess_ptr, "%s %s %s", g_dictionary[wordidx[0]].word, g_dictionary[wordidx[1]].word, g_dictionary[i].word);
                    guesses++;
                    if (guess(guess_ptr, &chars, &positions)) {
                        finished = 1;
                        break;
                    }
                }
            }
        }
        wordidx[word] = guesswordidx[saved_guesses - 1];
        wordlen[word] = g_dictionary[guesswordidx[saved_guesses - 1]].wordlen;
        #ifdef DEBUG
        printf("\tThe next word is \"%s\".\n", g_dictionary[wordidx[word]].word);
        #endif
        guess_ptr += wordlen[word] + 1;
        for (i = 0; i < guess_ptr - guess_str; i++) {
            guess_str[i] = '#';
        }
    }
    #ifdef DEBUG
    if (finished) {
        sprintf(guess_str, "%s %s %s", g_dictionary[wordidx[0]].word, g_dictionary[wordidx[1]].word, g_dictionary[wordidx[2]].word);
        printf("\tPhrase is \"%s\". Found in %d guesses.\n", guess_str, guesses);
    } else {
        printf("Oh noes! Something went wrong!\n");
        exit(1);
    }
    #endif
    return guesses;
}

int main(int argc, char **argv)
{
    FILE *dictfp, *phrasefp, *precompfp;
    int i, j, total_count, chars, positions;

    g_max_word_len = 0;
    g_min_word_len = 9999;
    dictfp = fopen("dictionary.txt", "r");
    for (i = 0; i < DICTIONARY_SIZE; i++) {
        fgets(g_dictionary[i].word, MAX_STRING, dictfp);
        while (!isalpha(g_dictionary[i].word[strlen(g_dictionary[i].word) - 1]))
            g_dictionary[i].word[strlen(g_dictionary[i].word) - 1] = '\0';
        g_dictionary[i].wordlen = strlen(g_dictionary[i].word);
        if (g_dictionary[i].wordlen < g_min_word_len)
            g_min_word_len = g_dictionary[i].wordlen;
        if (g_dictionary[i].wordlen > g_max_word_len)
            g_max_word_len = g_dictionary[i].wordlen;
    }
    fclose(dictfp);

    phrasefp = fopen("phrases.txt", "r");
    for (i = 0; i < PHRASE_COUNT; i++) {
        fgets(g_phrases[i], MAX_STRING, phrasefp);
        while (!isalpha(g_phrases[i][strlen(g_phrases[i]) - 1]))
            g_phrases[i][strlen(g_phrases[i]) - 1] = '\0';
    }
    fclose(phrasefp);

    precompfp = fopen("precomp.txt", "r");
    for (i = 0; i < DICTIONARY_SIZE; i++) {
        for (j = 0; j < DICTIONARY_SIZE; j++) {
            fscanf(precompfp, "%d ", &(g_pos_matrix[i][j]));
        }
    }

    g_guesses = 0;
    int min = 9999, max = 0, g;
    for (i = 0; i < PHRASE_COUNT; i++) {
        g_password = g_phrases[i];
        #ifdef DEBUG
        printf("Testing passphrase \"%s\"...\n", g_password);
        #endif
        g = checker();
        if (g < min) min = g;
        if (g > max) max = g;
    }

    printf("Total %d. Min %d. Max %d.\n", g_guesses, min, max);
    return 0;
}

C'est aussi amusant de le regarder en mots:

Testing passphrase "somebody sighed intimater"...
    Length of next word is 8.
    We guessed "abashing", it had 0 correct positions.
    We guessed "backlogs", it had 1 correct positions.
    We guessed "befitted", it had 0 correct positions.
    We guessed "caldwell", it had 0 correct positions.
    We guessed "disgusts", it had 0 correct positions.
    We guessed "encroach", it had 0 correct positions.
    We guessed "forenoon", it had 3 correct positions.
    We guessed "hotelman", it had 2 correct positions.
    We guessed "somebody", it had 8 correct positions.
    The next word is "somebody".
    Length of next word is 6.
    We guessed "abacus", it had 0 correct positions.
    We guessed "baboon", it had 0 correct positions.
    We guessed "celery", it had 0 correct positions.
    We guessed "diesel", it had 2 correct positions.
    We guessed "dimple", it had 1 correct positions.
    We guessed "duster", it had 1 correct positions.
    We guessed "hinged", it had 3 correct positions.
    We guessed "licked", it had 3 correct positions.
    We guessed "sighed", it had 6 correct positions.
    The next word is "sighed".
    We guessed "aaas", it had 0 correct positions.
    We guessed "b", it had 0 correct positions.
    We guessed "c", it had 0 correct positions.
    We guessed "debauchery", it had 2 correct positions.
    We guessed "deceasing", it had 0 correct positions.
    We guessed "echinoderm", it had 3 correct positions.
    We guessed "enhanced", it had 1 correct positions.
    We guessed "intimater", it had 9 correct positions.
    The next word is "intimater".
    Phrase is "somebody sighed intimater". Found in 38 guesses.
Orby
la source
1
J'aime celui-ci, une prochaine étape intuitive pourrait être de vous assurer que la liste de mots que vous utilisez pour deviner est bien ordonnée. Assurez-vous au moins que vous avez un bon mot de départ pour chaque quantité de lettres.
Dennis Jaheruddin
C'est une bonne idée. Merci pour les commentaires.
Orby
15

Python 3.4 - min: 21, max: 29, total: 25146, temps: 20min

min: 30, max: 235, total: 41636, temps: 4min

Mise à jour:

  1. Utilisez la recherche binaire pour trouver de l'espace. L'idée est empruntée à la réponse d' Orby . Un point que j’ai optimisé est que si vous avez trouvé deux espaces dans une plage lors de la recherche du premier espace, vous pouvez restreindre la plage de recherche du deuxième espace.
  2. Enregistrez les mauvaises suppositions avec leur résultat. Comparez avec eux dans les hypothèses suivantes. Cela peut économiser beaucoup.
  3. Réduisez le nombre de lettres à 12, grâce à la mise à jour # 2.

entrez la description de l'image ici


Cette méthode n'utilise pas le hasard pour que le score ne change pas.

D'abord, il utilise des suppositions comme celle-ci pour trouver les premier et deuxième espaces de la réponse.

. ......................
.. .....................
... ....................
.... ...................
# more follows, until two spaces found.

Ensuite, chaque lettre est comptée en devinant aaaaa..., bbbbb....... Après cela, il vous en coûtera environ 40 étapes. En fait, nous n'avons pas besoin d'essayer toutes les lettres et nous pouvons les essayer dans un ordre arbitraire. Dans la plupart des cas, essayer environ 20 lettres suffit. Ici j'ai choisi 21.

Maintenant, il connaît la longueur du premier mot et du deuxième mot afin de filtrer une liste de candidats pour ces deux mots. Normalement, il y aura environ 100 candidats pour chacun.

Ensuite, il suffit d’énumérer le premier et le deuxième mot. Une fois les deux premiers mots énumérés, nous pouvons déduire tous les troisièmes mots valides puisque nous savons que le nombre de caractères est important.

Pour optimiser la vitesse, j'utilise le concurrent.futurespour ajouter le multitraitement au programme. Vous avez donc besoin de Python 3 pour l’exécuter et je l’ai testé avec Python 3.4 sur ma machine Linux. En outre, vous devez installer numpy.

import sys
import functools
from collections import defaultdict
from concurrent.futures import ProcessPoolExecutor
import numpy as np


def debug(*args, **kwargs):
    return
    print(*args, **kwargs)


def compare(answer, guess):
    b = sum(1 for x, y in zip(guess, answer) if x == y)
    a = 0
    c = defaultdict(int)
    for x in answer:
        c[x] += 1

    for x in guess:
        if c.get(x, 0) > 0:
            a += 1
            c[x] -= 1
    return a, b


def checker_task(guesser):
    @functools.wraps(guesser)
    def task(case):
        i, answer = case
        return (i, answer, run_checker(answer, guesser))
    return task


def run_checker(answer, guesser):
    guess_count = 0
    guesser = guesser()
    guess = next(guesser)
    while True:
        guess_count += 1
        if answer == guess:
            break
        try:
            guess = guesser.send(compare(answer, guess))
        except StopIteration:
            raise Exception('Invalid guesser')
    try:
        guesser.send((-1, -1))
    except StopIteration:
        pass
    return guess_count


# Preprocessing
words = list(map(str.rstrip, open('dict.txt')))
words_with_len = defaultdict(list)
for word in words:
    words_with_len[len(word)].append(word)

M = 12
chars = 'eiasrntolcdupmghbyfvkwzxjq'[:M]
char_ord = {c: i for i, c in enumerate(chars)}

def get_fingerprint(word):
    counts = [0] * (M + 1)
    for c in word:
        counts[char_ord.get(c, M)] += 1
    return tuple(counts[:-1])

word_counts = {word: np.array(get_fingerprint(word)) for word in words}

# End of preprocessing


# @profile
@checker_task
def guesser1():
    # Find spaces using binary search
    max_word_len = max(map(len, words))
    max_len = max_word_len * 3 + 2
    # debug('max_len', max_len)
    s_l = [1, 3]
    s_r = [max_len - 3, max_len - 1]

    for i in range(2):
        while s_l[i] + 1 < s_r[i]:
            # debug(list(zip(s_l, s_r)))
            mid = (s_l[i] + s_r[i]) // 2
            guess = '.' * s_l[i] + ' ' * (mid - s_l[i])
            a, b = yield guess
            if b > 1 and i == 0:
                s_l[1] = max(s_l[1], s_l[0] + 2)
                s_r[1] = min(s_r[1], mid)
                s_r[0] = mid - 2
            elif b > 0:
                s_r[i] = mid
            else:
                s_l[i] = mid
        if i == 0:
            s_l[1] = max(s_l[1], s_l[0] + 2)

    spaces = s_l
    del s_l, s_r

    word_lens = [spaces[0], spaces[1] - spaces[0] - 1, None]
    debug('word_lens', word_lens)
    debug('spaces', spaces)
    char_counts = [0] * M
    for i, c in enumerate(chars):
        guess = c * max_len
        _, char_counts[i] = yield guess

    char_counts = np.array(char_counts)

    candidates = [words_with_len[word_lens[0]], words_with_len[word_lens[1]], words]
    for i, ws in enumerate(candidates):
        candidates[i] = [word for word in ws if np.alltrue(char_counts >= word_counts[word])]
    P = defaultdict(list)
    for word in candidates[2]:
        P[get_fingerprint(word)].append(word)
    debug('candidates', list(map(len, candidates)))

    wrong_guesses = []
    # @profile
    def search(i, counts, current):
        if i == 2:
            rests = tuple(char_counts - counts)
            for word in P[rests]:
                current[i] = word
                guess_new = ' '.join(current)
                for guess, t in wrong_guesses:
                    if t != compare(guess_new, guess):
                        break
                else:
                    yield current
            return
        for word in candidates[i]:
            counts += word_counts[word]
            if np.alltrue(char_counts >= counts):
                current[i] = word
                yield from search(i + 1, counts, current)
            counts -= word_counts[word]

    try_count = 0
    for result in search(0, np.array([0] * M), [None] * 3):
        guess = ' '.join(result)
        a, b = yield guess
        try_count += 1
        if a == -1:
            break
        wrong_guesses.append((guess, (a, b)))
    debug('try_count', try_count)


def test(test_file, checker_task):
    cases = list(enumerate(map(str.rstrip, open(test_file))))
    scores = [None] * len(cases)
    with ProcessPoolExecutor() as executor:
        for i, answer, score in executor.map(checker_task, cases):
            print('-' * 80)
            print('case', i)
            scores[i] = score
            print('{}: {}'.format(answer, score))
            sys.stdout.flush()
    print(scores)
    print('sum:{} max:{} min:{}'.format(sum(scores), max(scores), min(scores)))


if __name__ == '__main__':
    test(sys.argv[1], guesser1)
Rayon
la source
1
J'ai vraiment du mal à comprendre ça. Bon travail.
Orby
1
Comment avez-vous généré le graphique?
Beta Decay
1
@BetaDecay Un script utilisant matplotlib.
Ray
1
@ DennisJaheruddin Oui, c'est très moche. Correction maintenant.
Ray
1
Je pense que vous devriez utiliser matplotlibs xkcdify pour le graphique matplotlib.org/xkcd/examples/showcase/xkcd.html
MrLemon le
14

Java 13 923 (min: 11, max: 17)

Mise à jour: score amélioré (dépassé le <14 / crack moyen!), Nouveau code

  • Vérification des caractères connus maintenant plus dense (maintenant ABABAB *, au lieu de -AAAA *)
  • Quand aucun caractère connu n'est disponible, deux inconnus seront comptés en une seule hypothèse.
  • Les suppositions erronées sont stockées et utilisées pour vérifier les correspondances possibles
  • Quelques ajustements constants avec la nouvelle logique en place

Original post

J'ai décidé de me concentrer complètement sur le nombre de suppositions plutôt que sur les performances (en fonction des règles). Cela a abouti à un très programme intelligent lent.

Au lieu de voler des programmes connus, j'ai décidé de tout écrire à partir de zéro, mais il s'avère que certaines / la plupart des idées sont les mêmes.

Algorithme

Voici comment fonctionne le mien:

  1. Faites une seule requête qui donne la quantité totale de e et de caractères
  2. Ensuite, nous recherchons les espaces, en ajoutant des caractères inconnus à la fin pour obtenir le nombre de caractères.
  3. Une fois que les espaces ont été trouvés, nous souhaitons en savoir plus sur le nombre de caractères. En même temps, je reçois également davantage de données sur les caractères connus (si elles sont à positions égales), ce qui m'aidera à éliminer beaucoup d'expressions.
  4. Lorsque nous atteignons une certaine limite (traînée / erreur), toutes les phrases possibles sont générées et une recherche binaire est lancée, la plupart du temps en ajoutant toujours des caractères inconnus à la fin.
  5. Enfin nous faisons des suppositions!

Exemple de suppositions

Voici un exemple concret:

Phase 1 (find the e's and total character count):
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbccccccccccccccccccddddddddddddddddddffffffffffffffffffgggggggggggggggggghhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiijjjjjjjjjjjjjjjjjjkkkkkkkkkkkkkkkkkkllllllllllllllllllmmmmmmmmmmmmmmmmmmnnnnnnnnnnnnnnnnnnooooooooooooooooooppppppppppppppppppqqqqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrssssssssssssssssssttttttttttttttttttuuuuuuuuuuuuuuuuuuvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwwwwwwwxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyzzzzzzzzzzzzzzzzzz
Phase 2 (find the spaces):
        ----------------iiiiiiiiiiiiiiiiii
              ----------aaaaaaaaaaaa
           -------------sssssssssssssss
          --------------rrrrrrrrrrrr
         ---------------nnnnnnnnnnn
                 -------ttttttttt
               ---------oooooooo
                --------lllllll
Phase 3 (discovery of characters, collecting odd/even information):
eieieieieieieieieieieieicccccc
ararararararararararararddddd
ntntntntntntntntntntntntuuuuu
Phase 4 (binary search with single known character):
------------r------------ppppp
Phase 5 (actual guessing):
enveloper raging charter
racketeer rowing halpern

Parce que mon code ne se concentre jamais sur des mots simples et ne collecte que des informations sur la phrase complète dont il a besoin pour générer beaucoup de phrases, ce qui le rend très lent.

Code

Et enfin voici le code (laid), n'essayez même pas de le comprendre, désolé:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MastermindV3 {

    // Order of characters to analyze:
    // eiasrntolcdupmghbyfvkwzxjq - 97
    private int[] lookup = new int[] {4, 8, 0, 18, 17, 13, 19, 14, 11, 2, 3, 20, 15, 12, 6, 7, 1, 24, 5, 21, 10, 22, 25, 23, 9, 16};

    public static void main(String[] args) throws Exception {
        new MastermindV3().run();
    }

    private void run() throws Exception {
        long beforeTime = System.currentTimeMillis();
        Map<Integer, List<String>> wordMap = createDictionary();
        List<String> passPhrases = createPassPhrases();

        int min = Integer.MAX_VALUE;
        int max = 0;
        for(String phrase:passPhrases) {

            int before = totalGuesses;
            solve(wordMap, phrase);
            int amount = totalGuesses - before;

            min = Math.min(min, amount);
            max = Math.max(max, amount);
            System.out.println("Amount of guesses: "+amount+" : min("+min+") max("+max+")");
        }
        System.out.println("Total guesses: " + totalGuesses);
        System.out.println("Took: "+ (System.currentTimeMillis()-beforeTime)+" ms");
    }

    /**
     * From the original question post:
     * I've added a boolean for the real passphrase.
     * I'm using this method to check previous guesses against my own matches (not part of Mastermind guesses)
     */
    int totalGuesses = 0;
    int[] guess(String in, String pw, boolean againstRealPassphrase) {
        if(againstRealPassphrase) {
            //Only count the guesses against the password, not against our own previous choices
            totalGuesses++;
        }
        int chars=0, positions=0;
        for(int i=0;i<in.length()&&i<pw.length();i++){
            if(in.charAt(i)==pw.charAt(i))
                positions++;
        }
        if(positions == pw.length() && pw.length()==in.length())
            return new int[]{-1,positions};
        for(int i=0;i<in.length();i++){
            String c = String.valueOf(in.charAt(i));
            if(pw.contains(c)){
                pw = pw.replaceFirst(c, "");
                chars++;
            }
        }
        chars -= positions;
        return new int[]{chars,positions};
    }

    private void solve(Map<Integer, List<String>> wordMap, String pw) {

        // Do one initial guess which gives us two things:
        // The amount of characters in total
        // The amount of e's

        int[] initialResult = guess(Facts.INITIAL_GUESS, pw, true);

        // Create the object that tracks all the known facts/bounds:
        Facts facts = new Facts(initialResult);

        // Determine a pivot and find the spaces (binary search)
        int center = ((initialResult[0] + initialResult[1]) / 3) + 1;
        findSpaces(center, facts, pw);

        // When finished finding the spaces (and some character information)
        // We can calculate the lengths:
        int length1 = (facts.spaceBounds[0]-1);
        int length2 = (facts.spaceBounds[2]-facts.spaceBounds[0]-1);
        int length3 = (facts.totalLength-facts.spaceBounds[2]+2);

        // Next we enter a discovery loop where we find out two things:
        // 1) The amount of a new character
        // 2) How many of a known character are on an even spot
        int oddPtr = 0;
        int pairCnt = 0;

        // Look for more characters, unless we have one HUGE word, which should be brute forcible easily
        int maxLength = Math.max(length1, Math.max(length2, length3));
        while(maxLength<17 && !facts.doneDiscovery()) { // We don't need all characters, the more unknowns the slower the code, but less guesses

            // Try to generate a sequence with ABABABABAB... with two characters with known length
            String testPhrase = "";
            int expected = 0;
            while(oddPtr < facts.charPtr && (facts.oddEvenUsed[oddPtr]!=-1 || facts.charBounds[lookup[oddPtr]] == 0)) {
                oddPtr++;
            }
            // If no character unknown, try pattern -A-A-A-A-A-A-A... with just one known pattern
            int evenPtr = oddPtr+1;
            while(evenPtr < facts.charPtr && (facts.oddEvenUsed[evenPtr]!=-1 || facts.charBounds[lookup[evenPtr]] == 0)) {
                evenPtr++;
            }

            if(facts.oddEvenUsed[oddPtr]==-1 && facts.charBounds[lookup[oddPtr]] > 0 && oddPtr < facts.charPtr) {
                if(facts.oddEvenUsed[evenPtr]==-1 && facts.charBounds[lookup[evenPtr]] > 0 && evenPtr < facts.charPtr) {
                    for(int i = 0; i < (facts.totalLength + 3) / 2; i++) {
                        testPhrase += ((char)(lookup[oddPtr] + 97) +""+ ((char)(lookup[evenPtr] + 97)));
                    }
                    expected += facts.charBounds[lookup[oddPtr]] + facts.charBounds[lookup[evenPtr]];
                } else {
                    for(int i = 0; i < (facts.totalLength + 3) / 2; i++) {
                        testPhrase += ((char)(lookup[oddPtr] + 97) + "-");
                    }
                    expected += facts.charBounds[lookup[oddPtr]];
                }
            }

            // If we don't have known characters to explore, use the phrase-length part to discover the count of an unknown character
            boolean usingTwoNew = false;
            if(testPhrase.length() == 0 && facts.charPtr < 25) {
                usingTwoNew = true;
                //Fill with a new character
                while(testPhrase.length() < (facts.totalLength+2)) {
                    testPhrase += (char)(lookup[facts.charPtr+1] + 97);
                }
            } else {
                while(testPhrase.length() < (facts.totalLength+2)) {
                    testPhrase += "-";
                }
            }

            // Use the part after the phrase-length to discover the count of an unknown character
            for(int i = 0; i<facts.charBounds[lookup[facts.charPtr]];i++) {
                testPhrase += (char)(lookup[facts.charPtr] + 97);
            }

            // Do the actual guess:
            int[] result = guess(testPhrase, pw, true);

            // Process the results, store the derived facts:
            if(oddPtr < facts.charPtr) {
                if(evenPtr < facts.charPtr) {
                    facts.oddEvenUsed[evenPtr] = pairCnt;
                }
                facts.oddEvenUsed[oddPtr] = pairCnt;
                facts.oddEvenPairScore[pairCnt] = result[1];
                pairCnt++;

            }
            if(usingTwoNew) {
                facts.updateCharBounds(result[0]);
                if(result[1] > 0) {
                    facts.updateCharBounds(result[1]);
                }
            } else {
                facts.updateCharBounds((result[0]+result[1]) - expected);
            }
        }

        // Next we generate a list of possible phrases for further analysis:
        List<String> matchingPhrases = new ArrayList<String>();

        // Hacked in for extra speed, loop over longest word first:
        int[] index = sortByLength(length1, length2, length3);

        @SuppressWarnings("unchecked")
        List<String>[] lists = new List[3];
        lists[index[0]] = wordMap.get(length1);
        lists[index[1]] = wordMap.get(length2);
        lists[index[2]] = wordMap.get(length3);

        for(String w1:lists[0]) {
            //Continue if (according to our facts) this word is a possible partial match:
            if(facts.partialMatches(w1)) {
                for(String w2:lists[1]) {
                    //Continue if (according to our facts) this word is a partial match:
                    if(facts.partialMatches(w1+w2)) {
                        for(String w3:lists[2]) {

                            // Reconstruct phrase in correct order:
                            String[] possiblePhraseParts = new String[] {w1, w2, w3};
                            String possiblePhrase = possiblePhraseParts[index[0]]+" "+possiblePhraseParts[index[1]]+" "+possiblePhraseParts[index[2]];

                            //If the facts form a complete match, continue:
                            if(facts.matches(possiblePhrase)) {
                                matchingPhrases.add(possiblePhrase);
                            }
                        }
                    }
                }
            }
        }
        //Sometimes we are left with too many matching phrases, do a smart match on them, binary search style:
        while(matchingPhrases.size() > 8) {
            int lowestError = Integer.MAX_VALUE;
            boolean filterCharacterIsKnown = false;
            int filterPosition = 0;
            int filterValue = 0;
            String filterPhrase = "";

            //We need to filter some more before trying:
            int targetBinaryFilter = matchingPhrases.size()/2;
            int[][] usedCharacters = new int[facts.totalLength+2][26];
            for(String phrase:matchingPhrases) {
                for(int i = 0; i<usedCharacters.length;i++) {
                    if(phrase.charAt(i) != ' ') {
                        usedCharacters[i][phrase.charAt(i)-97]++;
                    }
                }
            }

            //Locate a certain character/position combination which is closest to 50/50:
            for(int i = 0; i<usedCharacters.length;i++) {
                for(int x = 0; x<usedCharacters[i].length;x++) {
                    int error = Math.abs(usedCharacters[i][x]-targetBinaryFilter);
                    if(error < lowestError || (error == lowestError && !filterCharacterIsKnown)) {

                        //If we do the binary search with a known character we can append more information as well
                        //Reverse lookup if the character is known
                        filterCharacterIsKnown = false;
                        for(int f = 0; f<facts.charPtr; f++) {
                            if(lookup[f]==x) {
                                filterCharacterIsKnown = true;
                            }
                        }

                        filterPosition = i;
                        filterValue = x;
                        filterPhrase = "";
                        for(int e = 0; e<i; e++) {
                            filterPhrase += "-"; 
                        }
                        filterPhrase += ""+((char)(x+97));
                        lowestError = error;
                    }
                }
            }

            //Append new character information as well:
            while(filterPhrase.length() <= (facts.totalLength+2)) {
                filterPhrase += "-";
            }

            if(filterCharacterIsKnown && facts.charPtr < 26) {
                //Append new character to discover
                for(int i = 0; i<facts.charBounds[lookup[facts.charPtr]];i++) {
                    filterPhrase += (char)(lookup[facts.charPtr] + 97);
                }
            }
            //Guess with just that character:
            int[] result = guess(filterPhrase, pw, true);

            //Filter the 50%
            List<String> inFilter = new ArrayList<String>();
            for(String phrase:matchingPhrases) {
                if(phrase.charAt(filterPosition) == (filterValue+97)) {
                    inFilter.add(phrase);
                }
            }
            if(result[1]>0) {
                //If we have a match, retain all:
                matchingPhrases.retainAll(inFilter);
            } else {
                //No match, filter all
                matchingPhrases.removeAll(inFilter);
            }

            if(filterCharacterIsKnown && facts.charPtr < 26) {
                //Finally filter according to the discovered character:
                facts.updateCharBounds((result[0]+result[1]) - 1);

                List<String> toKeep = new ArrayList<String>();
                for(String phrase:matchingPhrases) {
                    if(facts.matches(phrase)) {
                        toKeep.add(phrase);
                    }
                }
                matchingPhrases = toKeep;
            }

        }

        // Finally we have some phrases left, try them!
        for(String phrase:matchingPhrases) {

            if(facts.matches(phrase)) {
                int[] result = guess(phrase, pw, true);

                System.out.println(phrase+" "+Arrays.toString(result));
                if(result[0]==-1) {
                    return;
                }
                // No match, update facts:
                facts.storeInvalid(phrase, result);
            }
        }
        throw new IllegalArgumentException("Unable to solve!?");
    }

    private int[] sortByLength(int length1, int length2, int length3) {
        //God this code is ugly, can't be bothered to fix
        int[] index;
        if(length3 > length2 && length2 > length1) {
             index = new int[] {2, 1, 0};
        } else if(length3 > length1 && length1 > length2) {
             index = new int[] {2, 0, 1};
        } else if(length2 > length3 && length3 > length1) {
             index = new int[] {1, 2, 0};
        } else if(length2 > length1 && length1 > length3) {
             index = new int[] {1, 0, 2};
        } else if(length2 > length3) {
            index = new int[]{0, 1, 2};
        } else {
            index = new int[]{0, 2, 1};
        }
        return index;
    }

    private void findSpaces(int center, Facts facts, String pw) {
        String testPhrase = "";
        //Place spaces for analysis:
        for(int i = 0; i<center; i++) {testPhrase+=" ";}while(testPhrase.length()<(facts.totalLength+2)) {testPhrase+="-";}

        //Append extra characters for added information early on:
        for(int i = 0; i<facts.charBounds[lookup[facts.charPtr]];i++) {
            testPhrase += (char)(lookup[facts.charPtr]+97);
        }

        //Update space lower and upper bounds:
        int[] answer = guess(testPhrase, pw, true);
        if(answer[1] == 0) {
            facts.spaceBounds[0] = Math.max(facts.spaceBounds[0], center+1);
            facts.spaceBounds[2] = Math.max(facts.spaceBounds[2], center+3);
        } else if(answer[1] == 1) {
            facts.spaceBounds[1] = Math.min(facts.spaceBounds[1], center);
            facts.spaceBounds[2] = Math.max(facts.spaceBounds[2], center+1);
        } else {
            facts.spaceBounds[3] = Math.min(facts.spaceBounds[3], center);
            facts.spaceBounds[1] = Math.min(facts.spaceBounds[1], center-2);
        }
        int correctAmountChars = (answer[0] + answer[1]) - 2;
        facts.updateCharBounds(correctAmountChars);
        //System.out.println(Arrays.toString(facts.spaceBounds));
        if(facts.spaceBounds[0]==facts.spaceBounds[1]) {
            if(facts.spaceBounds[2]==facts.spaceBounds[3]) return;
            findSpaces(facts.spaceBounds[2] + ((facts.spaceBounds[3]-facts.spaceBounds[2])/3), facts, pw);
        } else {
            findSpaces((facts.spaceBounds[0]+facts.spaceBounds[1])/2, facts, pw);
        }
    }

    private class Facts {

        private static final String INITIAL_GUESS = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbccccccccccccccccccddddddddddddddddddffffffffffffffffffgggggggggggggggggghhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiijjjjjjjjjjjjjjjjjjkkkkkkkkkkkkkkkkkkllllllllllllllllllmmmmmmmmmmmmmmmmmmnnnnnnnnnnnnnnnnnnooooooooooooooooooppppppppppppppppppqqqqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrssssssssssssssssssttttttttttttttttttuuuuuuuuuuuuuuuuuuvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwwwwwwwxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyzzzzzzzzzzzzzzzzzz";
        private final int totalLength;
        private final int[] spaceBounds;
        // Pre-filled with maximum bounds obtained from dictionary:
        private final int[] charBounds = new int[] {12, 9, 9, 9, 15, 9, 12, 9, 18, 6, 9, 12, 9, 12, 12, 9, 3, 12, 15, 9, 12, 6, 6, 3, 9, 6};
        private final int[] oddEvenUsed = new int[] {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
        private final int[] oddEvenPairScore = new int[26];
        private int charPtr;

        public Facts(int[] initialResult) {

            totalLength = initialResult[0] + initialResult[1];
            spaceBounds = new int[] {2, Math.min(totalLength - 2, 22), 4, Math.min(totalLength + 1, 43)};

            //Eliminate firsts
            charBounds[lookup[0]] = initialResult[1];
            //Adjust:
            for(int i = 1; i<charBounds.length; i++) {
                charBounds[lookup[i]] = Math.min(charBounds[lookup[i]], totalLength-initialResult[1]);
            }
            charPtr = 1;
        }

        private List<String> previousGuesses = new ArrayList<String>();
        private List<int[]> previousResults = new ArrayList<int[]>(); 
        public void storeInvalid(String phrase, int[] result) {
            previousGuesses.add(phrase);
            previousResults.add(result);
        }

        public boolean doneDiscovery() {
            if(charPtr<12) { //Always do at least N guesses (speeds up and slightly improves score)
                return false;
            }
            return true;
        }

        public void updateCharBounds(int correctAmountChars) {

            // Update the bounds we know for a certain character:
            int knownCharBounds = 0;
            charBounds[lookup[charPtr]] = correctAmountChars;
            for(int i = 0; i <= charPtr;i++) {
                knownCharBounds += charBounds[lookup[i]];
            }
            // Also update the ones we haven't checked yet, we might know something about them now:
            for(int i = charPtr+1; i<charBounds.length; i++) {
                charBounds[lookup[i]] = Math.min(charBounds[lookup[i]], totalLength-knownCharBounds);
            }
            charPtr++;
            while(charPtr < 26 && charBounds[lookup[charPtr]]==0) {
                charPtr++;
            }
        }

        public boolean partialMatches(String phrase) {

            //Try to match a partial phrase, we can't be too picky because we don't know what else is next
            int[] cUsed = new int[26];
            for(int i = 0; i<phrase.length(); i++) {
                cUsed[phrase.charAt(i)-97]++;
            }
            for(int i = 0; i<cUsed.length; i++) {

                //Only eliminate the phrases that definitely have wrong characters:
                if(cUsed[lookup[i]] > charBounds[lookup[i]]) {
                    return false;
                }
            }
            return true;
        }

        public boolean matches(String phrase) {

            // Try to match a complete phrase, we can now use all information:
            int[] cUsed = new int[26];
            for(int i = 0; i<phrase.length(); i++) {
                if(phrase.charAt(i)!=' ') {
                    cUsed[phrase.charAt(i)-97]++;
                }
            }

            for(int i = 0; i<cUsed.length; i++) {
                if(i < charPtr) {
                    if(cUsed[lookup[i]] != charBounds[lookup[i]]) {
                        return false;
                    }
                } else {
                    if(cUsed[lookup[i]] > charBounds[lookup[i]]) {
                        return false;
                    }
                }
            }

            //Check against what we know for odd/even
            for(int pair = 0; pair < 26;pair++) {
                String input = "";
                for(int i = 0; i<26;i++) {
                    if(oddEvenUsed[i] == pair) {
                        input += (char)(lookup[i]+97);
                    }
                }
                if(input.length() == 1) {
                    input += "-";
                }
                String testPhrase = "";
                for(int i = 0; i<=(totalLength+1)/2 ; i++) {
                    testPhrase += input;
                }

                int[] result = guess(testPhrase, phrase, false);
                if(result[1] != oddEvenPairScore[pair]) {
                    return false;
                }
            }

            //Check again previous guesses:
            for(int i = 0; i<previousGuesses.size();i++) {
                // If the input phrase is the correct phrase it should score the same against previous tries:
                int[] result = guess(previousGuesses.get(i), phrase, false);
                int[] expectedResult = previousResults.get(i);
                if(!Arrays.equals(expectedResult, result)) {
                    return false;
                }
            }
            return true;
        }
    }


    private List<String> createPassPhrases() throws Exception {
        BufferedReader reader = new BufferedReader(new FileReader(new File("pass.txt")));
        List<String> phrases = new ArrayList<String>();
        String input;
        while((input = reader.readLine()) != null) {
            phrases.add(input);
        }
        return phrases;
    }

    private Map<Integer, List<String>> createDictionary() throws Exception {
        BufferedReader reader = new BufferedReader(new FileReader(new File("words.txt")));
        Map<Integer, List<String>> wordMap = new HashMap<Integer, List<String>>();
        String input;
        while((input = reader.readLine()) != null) {
            List<String> words = wordMap.get(input.length());
            if(words == null) {
                words = new ArrayList<String>();
            }
            words.add(input);
            wordMap.put(input.length(), words);
        }
        return wordMap;
    }

}
Roy van Rijn
la source
Vous êtes tellement intelligent.
Ray
2
C'est une idée brillante de compter les fréquences de personnage en parallèle avec la recherche d'espaces.
Ray
1
Je dois dire que je ne peux même pas commencer à comprendre votre technique paire / impaire, ni par pensée abstraite ni par ingénierie inverse. Je ne comprends pas non plus comment vous appelez la fonction de correspondance de mot de passe sans compter une supposition supplémentaire. Un peu d'explication serait la bienvenue.
12

Java - 18 708 requêtes; 2,4 secondes 11 077 requêtes; 125 min

Min: 8, Max: 13, Requêtes effectives: 10,095

J'ai passé beaucoup trop de temps là-dessus. : P

Le code est disponible sur http://pastebin.com/7n9a50NM

Rev 1. disponible à http://pastebin.com/PSXU2bga

Rev 2. disponible sur http://pastebin.com/gRJjpbbu

Ma deuxième révision. J'espérais pouvoir franchir la barrière des 11K pour remporter le prix, mais je n'ai plus le temps d'optimiser cette bête.

Il fonctionne sur un principe totalement distinct des deux versions précédentes (et dure environ 3 500 fois plus longtemps). Le principe général consiste à utiliser un tamisage de l'espace et des caractères pairs / impairs pour réduire la liste des candidats à une taille gérable (généralement entre 2 et 8 millions), puis d'effectuer des requêtes répétées avec un pouvoir discriminant maximal (c'est-à-dire dont la distribution de sortie a maximisé l'entropie).

Pas la vitesse mais la mémoire est la principale limitation. Ma machine virtuelle Java ne me permet pas de réserver un tas supérieur à 1 200 Mo pour une raison obscure (probablement Windows 7), et j'ai ajusté les paramètres pour me donner la meilleure solution possible qui n'épuise pas cette limite. Cela me contrarie qu'une bonne exécution avec les paramètres appropriés briserait 11K sans augmentation significative du temps d'exécution. J'ai besoin d'un nouvel ordinateur. : P

Ce qui me contrarie tout autant, c’est que 982 des requêtes de cette implémentation sont des requêtes de "validation" inutiles. Ils n'ont d'autre objectif que de satisfaire à la règle selon laquelle l'oracle doit renvoyer une valeur spéciale "vous l'avez eue" à un moment donné, même si, dans mon implémentation, la réponse correcte a été déduite avec certitude avant cette requête dans 98,2% des cas. La plupart des autres soumissions inférieures à 11K reposent sur des techniques de filtrage qui utilisent des chaînes candidates comme chaînes de requête et ne subissent donc pas les mêmes pénalités.

Pour cette raison, bien que mon nombre de requêtes officiel soit de 11 077 (à l'exception des leaders, à condition que leur code soit conforme, conforme aux spécifications, etc.), j'affirme hardiment que mon code génère 10 095 requêtes effectives , ce qui signifie que seules 10 095 requêtes sont exécutées. réellement nécessaire pour déterminer toutes les phrases de passe avec une certitude de 100%. Je ne suis pas sûr qu'aucune des autres implémentations ne corresponde à cela, c'est pourquoi je considérerai cela comme une victoire minime. ;)

COTO
la source
Les ZPC vont bien, d'autres entrées les utilisent également. Je pense que le plus commun est ..
Geobits
Le code actuel n'inclut pas une requête "de validation". Je vais en ajouter un maintenant.
COTO
J'ai mis à jour pour rev. 1, qui inclut la requête de validation. Sans surprise, le nombre de requêtes est exactement supérieur de 1 000 à celui de la version précédente.
COTO
1
C'est très gentil. Votre Java est tellement Java-y que ça fait mal. Je ne suis pas habitué à voir un code comme celui-ci sur ce site: D
Geobits
+1 pour les deux étant épique et"perpetually exhausting pool"
cjfaure
8

Java - min: 22, max: 41, total: 28353, temps: 4 secondes

Le programme devine le mot de passe en 3 étapes:

  1. trouver les positions d'espace avec une recherche binaire
  2. compter les occurrences des caractères les plus fréquents dans les 3 mots
  3. trouver les mots en partant de la gauche en utilisant les informations rassemblées ci-dessus

Il gère également un ensemble de "caractères incorrects" renvoyant un résultat nul dans la recherche, ainsi qu'un ensemble de "caractères corrects" placés ailleurs dans la phrase secrète.

Ci-dessous un exemple des valeurs envoyées successivement pour deviner, vous pouvez voir les 3 étapes:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
**  **  **  **  **  **  **  **  **  **  **  **  **  **  **  **  *
****    ****    ****    ****    ****    ****    ****    ****    *
********        ********        ********        ********        *
****************                ****************                *
********** ******** *********************************************
eeeeeeeeeee
eeeeeeeeeee eeeeee
iiiiiiiiiii
iiiiiiiiiii iiiiii
aaaaaaaaaaa
aaaaaaaaaaa aaaaaa
sssssssssss
sssssssssss ssssss
rrrrrrrrrrr
rrrrrrrrrrr rrrrrr
nnnnnnnnnnn
ttttttttttt
ooooooooooo
ooooooooooo oooooo
lllllllllll
a
facilitates 
facilitates w
facilitates wis
facilitates widows 
facilitates widows e
facilitates widows briefcase 

Le code:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;



public class Main5 {

    private static String CHARS = "eiasrntolcdupmghbyfvkwzxjq "; 
    private static String currentPassword;
    private static List<String> words;
    private static List<String> passphrases;

    private static char [] filters = {'e', 'i', 'a', 's', 'r', 'n', 't', 'o', 'l'};

    private static int maxLength;       

    public static void main(String[] args) throws IOException {

        long start = System.currentTimeMillis();
        passphrases = getFile("passphrases.txt");
        words = getFile("words.txt");
        maxLength = 0;
        for (String word : words) {
            if (word.length() > maxLength) {
                maxLength = word.length();
            }
        }

        int total = 0;
        int min = Integer.MAX_VALUE;
        int max = 0;
        for (String passphrase : passphrases) {
            currentPassword = passphrase;
            int tries = findPassword();
            if (tries > max) max = tries;
            if (tries < min) min = tries;
            total += tries;
        }
        long end = System.currentTimeMillis();
        System.out.println("Min : " + min);
        System.out.println("Max : " + max);
        System.out.println("Total : " + total);
        System.out.println("Time : " + (end - start) / 1000);
    }


    public static int findPassword() {

        /**************************************
         * STEP 1 : find the spaces positions *
         **************************************/
        int tries = 0;
        Map<String, int []> res = new HashMap<String, int[]>();
        long maxBits = (long) Math.log((maxLength * 3+2) * Math.exp(2));
        for (int bit = 0; bit < maxBits-2; bit++) {
            String sp = buildSpace(maxLength*3+2, bit);
            tries++;
            int [] ret = guess(sp);
            res.put(sp, ret);
        }
        List<String> candidates = new ArrayList<String>();
        List<String> unlikely = new ArrayList<String>();
        for (int x1 = 1; x1 < maxLength + 1; x1++) {
            for (int x2 = x1+2; x2 < Math.min(x1+maxLength+1, maxLength*3+2); x2++) {
                boolean ok = true;
                for (String key : res.keySet()) {
                    int [] ret = res.get(key);
                    if (key.charAt(x1) == ' ' && key.charAt(x2) == ' ') {
                        // ret[1] should be 2
                        if (ret[1] != 2) ok = false;
                    } else if (key.charAt(x1) == '*' && key.charAt(x2) == '*') {
                        // ret[1] should be 0
                        if (ret[1] != 0) ok = false;
                    } else if (key.charAt(x1) == ' ' || key.charAt(x2) == ' ') {
                        // ret[1] should be 1
                        if (ret[1] != 1) ok = false;
                    }
                }
                if (ok) {
                    String s = "";
                    for (int i = 0; i < maxLength*3+2; i++) {
                        s += i == x1 || i == x2 ? " " : "*";
                    }
                    // too short or too long words are unlikely to occur
                    if (x1 < 4 || x2 - x1 - 1 < 4 || x1 > 12 || x2 - x1 - 1 > 12) {
                        unlikely.add(s);
                    } else {
                        candidates.add(s);
                    }
                }
            }
        }
        candidates.addAll(unlikely);
        String correct = null;
        if (candidates.size() > 1) {

            for (int i = 0; i < candidates.size(); i++) {
                String cand = candidates.get(i);
                int [] ret = null;
                if (i < candidates.size() - 1) {
                    tries++;
                    ret = guess(cand);
                }
                if (i == candidates.size() - 1 || ret[1] == 2) {
                    correct = cand;
                    break;
                }
            }
        } else {
            correct = candidates.get(0);
        }
        int spaceIdx1 = correct.indexOf(' ');
        int spaceIdx2 = correct.lastIndexOf(' ');

        /********************************************
         * STEP 2 : count the most frequent letters *
         ********************************************/
        // test the filter characters in the first, second, last words
        List<int []> f = new ArrayList<int []>();
        for (int k = 0; k < filters.length; k++) {
            char filter = filters[k];
            String testE = "";
            for (int i = 0; i < spaceIdx1; i++) {
                testE += filter;
            }
            int tmpCount = 0;
            for (int [] tmp : f) {
                tmpCount += tmp[0];
            }
            int [] result;
            if (tmpCount == spaceIdx1) {
                // we can infer the result
                result = new int[] {1, 0};
            } else {
                tries++;
                result = guess(testE);
            }
            int [] count = {result[1], 0, 0};
            if (result[0] > 0) {
                // test the character in the second word
                testE += " ";
                for (int i = 0; i < spaceIdx2-spaceIdx1-1; i++) {
                    testE += filter;
                }                   
                tries++;
                result = guess(testE);
                count[1] = result[1] - count[0] - 1;
                if (testE.length() - count[0] - count[1] > 8) { // no word has more than 8 similar letters
                    count[2] = result[0]; 
                } else {
                    if (result[0] > 0) {
                        // test the character in the third word
                        testE += " ";
                        for (int i = 0; i < maxLength; i++) {
                            testE += filter;
                        }
                        tries++;
                        result = guess(testE);
                        count[2] = result[1] - count[0] - count[1] - 2;
                    }
                }
            }
            f.add(new int[] {count[0], count[1], count[2]});
        }

        /***********************************************
         * STEP 3 : find the words, starting from left *
         ***********************************************/
        String phrase = "", word = "";
        int numWord = 0;
        Set<Character> badChars = new HashSet<Character>();
        Set<Character> goodChars = new HashSet<Character>();
        while (true) {
            boolean found = false;
            int wordLength = -1; // unknown
            if (numWord == 0) wordLength = spaceIdx1;
            if (numWord == 1) wordLength = spaceIdx2-spaceIdx1-1;


            // compute counts
            List<Integer> counts = new ArrayList<Integer>();
            for (int [] tmp : f) {
                counts.add(tmp[numWord]);
            }
            // what characters should we test after?
            String toTest = whatNext(word, badChars, numWord == 2 ? goodChars : null,
                    wordLength, counts);
            // if the word is already found.. complete it, no need to call guess
            if (toTest.length() == 1 && !toTest.equals(" ")) {
                phrase += toTest;
                word += toTest;
                goodChars.remove(toTest.charAt(0));
                continue;
            }
            // try all possible letters             
            for (int i = 0; i < toTest.length(); i++) {
                int [] result = null;
                char c = toTest.charAt(i);
                if (badChars.contains(c)) continue;
                boolean sureGuess = c != ' ' && i == toTest.length() - 1;
                if (!sureGuess) {
                    // we call guess ; increment the number of tries
                    tries++;
                    result = guess(phrase + c);
                    // if the letter is not present, add it to the set of "bad" characters
                    if (result[0] == 0 && result[1] == phrase.length()) {                       
                        badChars.add(c);
                    }
                    // if the letter is present somewhere else, add it to the set of "good" characters
                    if (result[0] == 1 && result[1] == phrase.length()) {                       
                        goodChars.add(c);
                    }
                }
                if (sureGuess || result[1] == phrase.length()+1) {
                    goodChars.remove(c);
                    phrase += c;
                    word += c;
                    if (toTest.charAt(i) == ' ') {
                        word = "";
                        numWord++;
                    }
                    found = true;
                    break;
                }
            }
            if (!found) break;
        }
        if (!phrase.equals(currentPassword)) System.err.println(phrase);
        return tries;
    }

    public static int[] guess(String in) {
        int chars=0, positions=0;
        String pw = currentPassword; // set elsewhere, contains current pass
        for(int i=0;i<in.length()&&i<pw.length();i++){
            if(in.charAt(i)==pw.charAt(i))
                positions++;
        }
        if(positions == pw.length() && pw.length()==in.length())
            return new int[]{-1,positions};
        for(int i=0;i<in.length();i++){
            String c = String.valueOf(in.charAt(i));
            if(pw.contains(c)){
                pw = pw.replaceFirst(c, "");
                chars++;
            }
        }
        chars -= positions;
        return new int[]{chars,positions};
    }


    private static String buildSpace(int length, int bit) {
        String sp = "";
        for (int i = 0; i < length; i++) {
            if (((i >> bit) & 1) != 0) {
                sp += " ";
            } else {
                sp += "*";
            }
        }
        return sp;
    }

    public static String whatNext(String s, Set<Character> badChars, Set<Character> goodChars, int length, List<Integer> counts) {
        String ret = "";
        Map<Character, Integer> freq = new HashMap<Character, Integer>();
        for (char c : CHARS.toCharArray()) {
            if (badChars.contains(c)) continue;
            freq.put(c, 0);
        }
        for (String word : words) {
            if (word.startsWith(s) && (word.length() == length || length == -1)) {
                char c1 = word.equals(s) ? ' ' : word.charAt(s.length());
                if (badChars.contains(c1)) continue;

                boolean badWord = false;
                for (int j = 0; j < counts.size(); j++) {
                    int cpt = 0;
                    for (int i = 0; i < word.length(); i++) {
                        if (word.charAt(i) == filters[j]) cpt++;    
                    }
                    if (cpt != counts.get(j)) {
                        badWord = true;
                        break;
                    }
                }
                if (badWord) continue;
                String endWord = word.substring(s.length());

                for (char bad : badChars) {
                    if (endWord.indexOf(bad) != -1) {
                        badWord = true;
                        break;
                    }
                }
                if (badWord) continue;
                if (goodChars != null) {
                    for (char good : goodChars) {
                        if (endWord.indexOf(good) == -1) {
                            badWord = true;
                            break;
                        }
                    }
                }
                if (badWord) continue;
                freq.put(c1, freq.get(c1)+1);
            }
        }
        while (true) {
            char choice = 0;
            int best = 0;
            for (char c : CHARS.toCharArray()) {
                if (freq.containsKey(c) && freq.get(c) > best) {
                    best = freq.get(c);
                    choice = c;
                }
            }
            if (choice == 0) break;
            ret += choice;
            freq.remove(choice);
        }
        return ret;
    }



    public static List<String> getFile(String filename) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(filename));
        List<String> lines = new ArrayList<String>();
        String line = null;
        while ((line = reader.readLine()) != null) {
            lines.add(line);
        }
        reader.close();
        return lines;
    }
}
Arnaud
la source
7

PYTHON 2.7 - 156821 devine, 0.6 seconde

Je me suis tourné vers la vitesse plutôt que vers le plus petit nombre de suppositions, bien que je sache que mon nombre de suppositions est toujours inférieur à ce que serait, par exemple, une attaque directe par dictionnaire. Je ne calcule pas le nombre de lettres dans le mot de passe, mais au mauvais endroit, car ma méthode ne l'utilise pas, mais si vous estimez que cela me donne un avantage injuste, je le mettrai en œuvre. Je commence simplement par une chaîne de proposition vide et j'ajoute un suffixe de caractère qui incrémente ma liste de caractères, en vérifiant le résultat de 'check' pour voir si le nombre de caractères corrects est égal à la longueur de la proposition. Par exemple, si le mot de passe était «mauvais», je suppose que:

un B

une

a B c d

J'ai également essayé de trier les lettres par fréquence de lettres anglaises, ce qui réduisait environ 35% du nombre de suppositions, ainsi que le temps. J'ai craqué tous les mots de passe en 0,82 secondes. Les statistiques sont imprimées à la fin.

import string
import time

class Checker():

    def __init__(self):
        #self.chars          = string.ascii_lowercase + ' '  #ascii letters + space
        self.baseChars     = "eiasrnt olcdupmghbyfvkwzxjq"  #ascii letters in order of frequency, space thrown in a reasonable location
        self.subfreqs      = {}

        self.chars         = "eiasrnt olcdupmghbyfvkwzxjq"
        self.subfreqs['a'] = "tnlrcsb dmipguvykwfzxehajoq"
        self.subfreqs['b'] = "leaiour sbytjdhmvcnwgfpkqxz"
        self.subfreqs['c'] = "oaehtik rulcysqgnpzdmvbfjwx"
        self.subfreqs['d'] = "eioarus ldygnmvhbjwfptckqxz"
        self.subfreqs['e'] = "rsndlat cmepxfvgwiyobuqhzjk"
        self.subfreqs['f'] = "ioefalu rtysbcdgnhkjmqpwvxz"
        self.subfreqs['g'] = "erailho usngymtdwbfpckjqvxz"
        self.subfreqs['h'] = "eaoiurt ylmnsfdhwcbpgkjqvxz"
        self.subfreqs['i'] = "notscle amvdgrfzpbkuxqihjwy"
        self.subfreqs['j'] = "ueaoicb dgfhkjmlnqpsrtwvyxz"
        self.subfreqs['k'] = "eisalny owmurfptbhkcdjgqvxz"
        self.subfreqs['l'] = "eialyou stdmkvpfcngbhrwjqxz"
        self.subfreqs['m'] = "eaiopub msnylchfrwqvdgkjtxz"
        self.subfreqs['n'] = "gtesdia conufkvylhbmjrqpwzx"
        self.subfreqs['o'] = "nrumlts opcwdvgibafkeyxzhjq"
        self.subfreqs['p'] = "eroalih ptusybfgkdmwjcnqvxz"
        self.subfreqs['q'] = "uacbedg fihkjmlonqpsrtwvyxz"
        self.subfreqs['r'] = "eaiostm rdyuncgbplkvfhwjqzx"
        self.subfreqs['s'] = "tesihoc upalmnykwqfbdgrvjxz"
        self.subfreqs['t'] = "iearohs tyulcnwmfzbpdgvkjqx"
        self.subfreqs['u'] = "srnltmc adiebpgfozkxvyqhwuj"
        self.subfreqs['v'] = "eiaouyr bhpzcdgfkjmlnqstwvx"
        self.subfreqs['w'] = "aieonhs rlbcmpdkyfgutwvjqxz"
        self.subfreqs['x'] = "pitcaeh oyulgfbdkjmnqsrwvxz"
        self.subfreqs['y'] = "sepminl acortdwgubfkzhjqvyx"
        self.subfreqs['z'] = "eaizoly usrkmwxcbdgfhjnqptv"


        self.numGuessesTot  = 0
        self.numGuessesCur  = 0
        self.currentIndex   = 0
        self.passwords      = [line.strip() for line in open('passwords.txt', 'r').readlines()]
        self.currentPass    = self.passwords[self.currentIndex]
        self.numPasswords   = len(self.passwords)
        self.mostGuesses    = (0,   '')
        self.leastGuesses   = (1e9, '')

    def check(self, guess):
        self.numGuessesTot += 1
        self.numGuessesCur += 1
        numInPass  = 0
        numCorrect = 0
        lenPass    = len(self.currentPass)
        lenGuess   = len(guess)

        minLength  = min(lenPass, lenGuess)

        for i in range(minLength):
            if guess[i] == self.currentPass[i]:
                numCorrect += 1

        if numCorrect == len(self.currentPass):
            return -1, -1

        # numInPass is not calculated, as I don't use it
        return numInPass, numCorrect

    def nextPass(self):

        if self.numGuessesCur < self.leastGuesses[0]:
            self.leastGuesses = (self.numGuessesCur, self.currentPass)
        if self.numGuessesCur > self.mostGuesses[0]:
            self.mostGuesses  = (self.numGuessesCur, self.currentPass)

        self.numGuessesCur = 0
        self.currentIndex += 1

        if self.currentIndex < self.numPasswords:
            self.currentPass = self.passwords[self.currentIndex]

    def main(self):

        t0 = time.time()

        while self.currentIndex < self.numPasswords:
            guess = ''
            result = (0, 0)
            while result[0] is not -1:
                i = 0
                while i < len(self.chars) and result[1] < len(guess)+1 and result[1] is not -1:
                    result = self.check(guess + self.chars[i])

                    i += 1
                guess += self.chars[i-1]

                if self.chars[i-1] == " ":
                    self.chars = self.baseChars
                    i = 0
                else:
                    self.chars = self.subfreqs[self.chars[i-1]]
                    i = 0
            if result[0] == -1:
                #print self.currentIndex, self.currentPass
                self.nextPass()    

        elapsedTime = time.time() - t0
        print "  Total number of guesses: {}".format(self.numGuessesTot)
        print "  Avg number of guesses:   {}".format(self.numGuessesTot/self.numPasswords)
        print "  Least number of guesses: {} -> {}".format(self.leastGuesses[0], self.leastGuesses[1])
        print "  Most number of guesses:  {} -> {}".format(self.mostGuesses[0],  self.mostGuesses[1])
        print "  Total time:              {} seconds".format(elapsedTime)

if __name__ == "__main__":
    checker = Checker()
    checker.main()

EDIT: Suppression des +1 et -1 perdus de deux des boucles while des itérations précédentes de tests, ainsi que de nouvelles statistiques pour les suppositions les plus faibles et les suppositions pour un mot de passe individuel.

EDIT2: ajout d'une table de recherche pour la lettre suivante la plus courante, par lettre. Augmentation considérable de la vitesse et diminution du nombre de devinettes

stokastic
la source
2
Bien que très rapide, cela utilise certainement un tas de suppositions. Vous pourrez peut-être améliorer un peu en utilisant la fréquence de lettre du fichier dict plutôt que l'anglais commun.
Geobits
@Geobits Corrigé les bugs, j'avais -1 dans l'instruction if dans nextPass () et un +1 dans la boucle while dans main (), issus d'itérations de test précédentes.
Affiche
7

C ++ - 11383 10989 correspondances!

Mise à jour

Correction de fuites de mémoire et suppression d'une nouvelle tentative de réduction de la taille des dictionnaires de mots individuels. Prend environ 50 minutes sur mon mac pro. Le code mis à jour est sur github.


Je suis passé à la stratégie de correspondance de phrase, j'ai retravaillé le code et l'ai mis à jour sur github https://github.com/snjyjn/mastermind

Avec la correspondance basée sur la phrase, il ne reste plus que 11383 tentatives! C'est cher en terme de calcul! Je n'aime pas non plus la structure du code! Et il est toujours loin derrière les autres :-(

Voici comment je le fais:

  1. Mesurez la longueur de la phrase en utilisant une chaîne de 26 caractères maximum (max = 3 * maxwordlen + 2) et 2 espaces. Les premiers caractères maxlen sont les plus fréquents dans le dictionnaire, c.-à-d. E
  2. Utilisez une stratégie de type tamis binaire pour identifier les espaces - effectuez un nombre défini de tentatives et identifiez les paires d'espaces potentielles. Créez des chaînes de test spécifiques à réduire à une seule paire.
  3. En parallèle, ajoutez des chaînes de test 'spécialement conçues' pour obtenir plus d'informations sur la phrase. La stratégie actuelle est la suivante:

    une. Utilisez les caractères dans l'ordre de leur fréquence dans le dictionnaire.

    b. Nous connaissons déjà le compte pour le plus fréquent

    c. 1ère chaîne de test = 5 caractères suivants. Cela nous donne le nombre de ces caractères dans la phrase.

    ré. 3 chaînes de test suivantes = 5 caractères chacune, couvrant un total de 20 caractères en 4 tentatives en plus du 1 premier caractère. Cela nous donne également le compte de ces 5 derniers personnages. les ensembles avec 0 nombre sont parfaits pour réduire la taille du dictionnaire!

    e. Passons maintenant au test précédent qui a eu le moins de comptes nuls, décomposez la chaîne en 2 et utilisez 1 pour le test. Le compte résultant nous parle également de l’autre division.

    F. Maintenant, répétez les tests avec des caractères (base 0),

       1,6,11,16,21
       2,7,12,17,22
       3,8,13,18,23
       4,9,14,19,24
       Cela devrait nous donner 5,10,15,20,25
g. After this, the next set of test strings are all 1 character long.
   though we dont expect to get so many tries!
  1. Une fois les espaces identifiés, utilisez les contraintes jusqu’à présent (autant de tests qu’il est possible de réaliser dans ces tentatives) pour réduire la taille du dictionnaire. Créez également 3 dictionnaires secondaires, 1 pour chaque mot.

  2. Maintenant, essayez de deviner chaque mot et testez-le.
    Utilisez ces résultats pour réduire les tailles de dictionnaire individuelles.
    Décorez-le également avec des caractères de test (après la longueur) pour obtenir plus de contraintes sur la phrase! J'ai utilisé 3 suppositions dans la version finale - 2 pour le mot 1 et 1 pour le mot 2

  3. Cela amène le dictionnaire à une taille gérable. Effectuez un produit croisé en appliquant toutes les contraintes comme précédemment pour créer un dictionnaire de phrases.

  4. Résoudre pour le dictionnaire de phrases à travers une série de suppositions - cette fois en utilisant les informations de position et de correspondance de caractères.

  5. Cette approche nous amène à 11383 tentatives:

    Statistiques Matcher
    ------------------
    Longueur: 1000
    Espaces: 6375
    Mot 1: 1996
    Mot 2: 999
    Phrase: 1013
    TOTAL 11383

    Dictionnaire de statistiques
    mot 0 6517
    mot 1 780 221 92
    mot 2 791 233
    mot 3 772
    phrase 186 20 4 2

    Temps de solution: 20 minutes sur mon macbook pro.

Post précédent

J'ai nettoyé le code et l'ai téléchargé sur https://github.com/snjyjn/mastermind Au cours du processus, je l'ai amélioré et j'ai encore une idée à essayer. Il y a une différence majeure par rapport à ce que j'avais fait hier:

Suppression des devinettes individuelles pour les caractères basés sur les caractères de haute fréquence dans le dictionnaire pour les mots 1 et 2, et à la place, j'utilise une chaîne basée sur le caractère de fréquence le plus élevé pour cette position.

Les statistiques ressemblent maintenant à:

Espaces: 6862
Mot 1: 5960
Mot 2: 5907
Mot 3: 2953
TOTAL 21682

Poste originale

Toutes mes excuses pour la "réponse", mais je viens de créer un compte et je n'ai pas assez de réputation pour ajouter un commentaire.

J'ai un programme c ++, qui prend environ 6,5 secondes et 24107 tentatives de correspondance. Il s'agit d'environ 1400 lignes de c ++. Je ne suis pas content de la qualité du code et je vais le nettoyer avant de le mettre en place dans un jour ou deux. Mais dans l’intérêt de la communauté et contribuant à la discussion, voici ce que je fais:

  • Lisez le dictionnaire, obtenez des informations de base à son sujet - longueur de mot minimale / maximale, fréquence des caractères, etc.

  • Identifiez d'abord les espaces - Celui-ci comporte 2 moitiés, la première est un ensemble de requêtes qui continuent à partitionner l'espace (similaire à un C. Chafouin):

        ********
    **** ****
  ** ** ** **
 - * * * * * * *

Ce n’est pas tout à fait exact, car j’utilise la longueur de mot min / max, et j’utilise le nombre de correspondances à chaque étape, mais vous avez l’idée. À ce stade, il n’ya toujours pas assez d’informations pour obtenir les 2 espaces, mais j’en ai assez pour les réduire à un petit nombre de combinaisons. À partir de ces combinaisons, je peux faire quelques requêtes spécifiques, ce qui réduira le nombre à une combinaison.

  • Premier mot - Obtenez un sous-dictionnaire, qui contient des mots de la bonne longueur. Le sous-dictionnaire a ses propres statistiques. Faites quelques suppositions avec les caractères les plus fréquents pour obtenir un décompte de ces caractères dans le mot. Réduisez à nouveau le dictionnaire en fonction de cette information. Créez un mot qui suppose les caractères les plus différents et utilisez-le. Chaque réponse entraîne une réduction dans le dictionnaire jusqu'à ce que nous ayons une correspondance exacte ou que le dictionnaire ait la taille 1.

  • Deuxième mot - semblable au premier mot

  • Troisième mot - c'est le plus différent des deux autres. Nous n'avons pas d'informations sur la taille, mais nous avons toutes les requêtes précédentes (que nous avons conservées). Ces requêtes vous permettent de réduire le dictionnaire. La logique est sur les lignes de:

 - requête abc a renvoyé un nombre de correspondances de 1
 - les mots 1 et 2 n'ont pas b ou c
 - Il est clair que b ou c ne peuvent pas faire partie du mot 3

Utilisez le dictionnaire réduit pour deviner, avec les caractères les plus divers, et continuez à réduire le dictionnaire à la taille 1 (comme dans les mots 1 et 2).

Les statistiques ressemblent à:

    Recherche d'espace: 7053
    Caractères du mot 1: 2502
    Mots du mot 1: 3864
    Mot 2 caractères: 2530
    Mots 2 mots: 3874
    Mot 3 caractères: 2781
    Mots 3 mots: 1503
    TOTAL: 24107
Sanjay Jain
la source
En réalité, vous pouvez connaître la longueur totale en une seule requête.
Ray
Merci @ Ray. Je l'ai fait par la suite, mais pas lors de mon premier passage à travers le problème Je n'ai tout simplement pas modifié mon message original.
Sanjay Jain
6

Go - Total: 29546

Semblable à d'autres, avec quelques optimisations.

  1. Obtenez la longueur totale en testant AAAAAAAABBBBBBBBCCCCCCCC...ZZZZZZZZ
  2. Déterminez la longueur réelle des trois mots en déplaçant des espaces à partir des deux extrémités.
  3. Filtrez chaque mot par nombre de lettres de certaines lettres courantes.
  4. Réduisez le nombre de candidats en testant une chaîne et en supprimant les autres candidats qui ne fournissent pas les mêmes résultats. Répétez jusqu'à ce que le gagnant soit trouvé.

Ce n'est pas particulièrement rapide.

package main

import (
    "bytes"
    "fmt"
    "strings"
)

var totalGuesses = 0
var currentGuesses = 0

func main() {
    for i, password := range passphrases {
        currentGuesses = 0
        fmt.Println("#", i)
        currentPassword = password
        GuessPassword()
    }
    fmt.Println(totalGuesses)
}

func GuessPassword() {
    length := GetLength()
    first, second, third := GetWordSizes(length)

    firstWords := GetWordsOfLength(first, "")
    secondWords := GetWordsOfLength(second, strings.Repeat(".", first+1))
    thirdWords := GetWordsOfLength(third, strings.Repeat(".", first+second+2))
    //tells us number of unique letters in solution. As good as any for an initial pruning mechanism.
    RecordGuess("abcdefghijklmnopqrstuvwxyz")
    candidates := []string{}
    for _, a := range firstWords {
        for _, b := range secondWords {
            for _, c := range thirdWords {
                candidate := a + " " + b + " " + c
                if MatchesLastGuess(candidate) {
                    candidates = append(candidates, candidate)
                }
            }
        }
    }

    for {
        //fmt.Println(len(candidates))
        RecordGuess(candidates[0])
        if lastExist == -1 {
            fmt.Println(lastGuess, currentGuesses)
            return
        }
        candidates = Prune(candidates[1:])
    }
}

var lastGuess string
var lastExist, lastExact int

func RecordGuess(g string) {
    a, b := MakeGuess(g)
    lastGuess = g
    lastExist = a
    lastExact = b
}
func Prune(candidates []string) []string {
    surviving := []string{}
    for _, x := range candidates {
        if MatchesLastGuess(x) {
            surviving = append(surviving, x)
        }
    }
    return surviving
}
func MatchesLastGuess(candidate string) bool {
    a, b := Compare(candidate, lastGuess)
    return a == lastExist && b == lastExact
}

func GetWordsOfLength(i int, prefix string) []string {
    candidates := []string{}
    guess := prefix + strings.Repeat("e", i)
    _, es := MakeGuess(guess)
    guess = prefix + strings.Repeat("a", i)
    _, as := MakeGuess(guess)
    guess = prefix + strings.Repeat("i", i)
    _, is := MakeGuess(guess)
    guess = prefix + strings.Repeat("s", i)
    _, ss := MakeGuess(guess)
    guess = prefix + strings.Repeat("r", i)
    _, ts := MakeGuess(guess)
    for _, x := range allWords {
        if len(x) == i && strings.Count(x, "e") == es &&
            strings.Count(x, "a") == as &&
            strings.Count(x, "i") == is &&
            strings.Count(x, "r") == ts &&
            strings.Count(x, "s") == ss {
            candidates = append(candidates, x)
        }
    }
    return candidates
}

func GetLength() int {
    all := "  "
    for i := 'a'; i <= 'z'; i++ {
        all = all + strings.Repeat(string(i), 8)
    }
    a, b := MakeGuess(all)
    return a + b
}

func GetWordSizes(length int) (first, second, third int) {
    first = 0
    second = 0
    third = 0
    guess := bytes.Repeat([]byte{'.'}, length)
    left := 1
    right := length - 2
    for {
        guess[left] = ' '
        guess[right] = ' '
        _, exact := MakeGuess(string(guess))
        guess[left] = '.'
        guess[right] = '.'
        if exact == 0 {
            left++
            right--
        } else if exact == 1 {
            break
        } else if exact == 2 {
            first = left
            second = right - first - 1
            third = length - first - second - 2
            return
        }
    }
    //one end is decided, the other is not
    //move right in to see
    right--
    guess[left] = ' '
    guess[right] = ' '
    _, exact := MakeGuess(string(guess))
    guess[left] = '.'
    guess[right] = '.'
    if exact == 2 {
        //match was on left. We got lucky and found other match too!
        first = left
        second = right - first - 1
        third = length - first - second - 2
        return
    } else if exact == 0 {
        //match was on right, but we lost it.
        //keep going on left
        right++
        left++
        guess[right] = ' '
        for {
            guess[left] = ' '
            _, exact = MakeGuess(string(guess))

            guess[left] = '.'
            if exact == 2 {
                first = left
                second = right - first - 1
                third = length - first - second - 2
                return
            }
            left++
        }
    } else if exact == 1 {
        //exact == 1. Match was on left and still is. Keep going on right
        right--
        guess[left] = ' '
        for {
            guess[right] = ' '
            _, exact = MakeGuess(string(guess))

            guess[right] = '.'
            if exact == 2 {
                first = left
                second = right - first - 1
                third = length - first - second - 2
                return
            }
            right--
        }
    }
    return first, second, third
}

var currentPassword string

func MakeGuess(guess string) (exist, exact int) {
    totalGuesses++
    currentGuesses++
    return Compare(currentPassword, guess)
}

func Compare(target, guess string) (exist, exact int) {

    if guess == target {
        return -1, len(target)
    }
    exist = 0
    exact = 0
    for i := 0; i < len(target) && i < len(guess); i++ {
        if target[i] == guess[i] {
            exact++
        }
    }
    for i := 0; i < len(guess); i++ {
        if strings.IndexByte(target, guess[i]) != -1 {
            exist++
            target = strings.Replace(target, string(guess[i]), "", 1)
        }
    }
    exist -= exact
    return
}
captncraig
la source
Je ne peux pas compiler ce code. Le compilateur a dit cela passphaseset allWordsn'est pas défini.
Ray
vous aurez besoin de ces deux fichiers: gist.github.com/captncraig/a136d0b9819d0ea948e6
captncraig
6

Java: 58 233

(programme de référence)

Un simple bot à battre pour tout le monde. Il utilise 26 suppositions initiales pour chaque phrase pour établir le nombre de caractères. Ensuite, il élimine tous les mots contenant des lettres non trouvées dans la phrase.

Vient ensuite une boucle O (n 3 ) massive sur les mots restants. Il vérifie d’abord chaque phrase candidate pour voir s’il s’agit d’un anagramme. Si c'est le cas, il le devinera, ignorant les résultats, à moins que ce ne soit une correspondance parfaite. Je l'ai vu utiliser entre 28 et 510 suppositions pour une phrase donnée jusqu'à présent.

C’est lent et tout dépend du nombre de mots pouvant être éliminés directement à partir des 26 suppositions initiales. La plupart du temps, il laisse entre 1000 et 4000 mots à boucler. Pour le moment, cela dure environ 14 heures, à un rythme de ~ 180 secondes / phrase. J’estime que cela prendra 50 heures et que le score sera mis à jour à ce moment-là. Vous devriez probablement faire quelque chose de plus intelligent ou de plus filant que cela.

(mise à jour) Il a finalement fini, avec un peu moins de 60k devins.

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;

public class Mastermind {

    String currentPassword;
    String[] tests;
    HashSet<String> dict;
    ArrayList<HashSet<String>> hasLetter;
    int maxLength = 0;
    int totalGuesses;

    public static void main(String[] args) {
        Mastermind master = new Mastermind();
        master.loadDict("dict-small");
        master.loadTests("passwords");
        System.out.println();
        master.run();
    }

    public Mastermind(){
        totalGuesses = 0;
        dict = new HashSet<String>();
        hasLetter = new ArrayList<HashSet<String>>(26);
        for(int i=0;i<26;i++)
            hasLetter.add(new HashSet<String>());
    }

    int run(){
        long start = System.currentTimeMillis();
        for(int i=0;i<tests.length;i++){
            long wordStart = System.currentTimeMillis();
            currentPassword = tests[i];
            int guesses = test();
            if(guesses < 0){
                System.out.println("Failed!");
                System.exit(0);
            }
            totalGuesses += guesses;
            long time = System.currentTimeMillis() - wordStart;
            System.out.println((i+1) + " found! " + guesses + " guesses, " + (time/1000) + "s ("+ ((System.currentTimeMillis()-start)/1000) +" total) : " + tests[i]);
        }
        System.out.println("\nTotal for " + tests.length + " tests: " + totalGuesses + " guesses, " + ((System.currentTimeMillis()-start)/1000) + " seconds total");
        return totalGuesses;
    }

    int[] guess(String in){
        int chars=0, positions=0;
        String pw = currentPassword;
        for(int i=0;i<in.length()&&i<pw.length();i++){
            if(in.charAt(i)==pw.charAt(i))
                positions++;
        }
        if(positions == pw.length() && pw.length()==in.length())
            return new int[]{-1,positions};
        for(int i=0;i<in.length();i++){
            String c = String.valueOf(in.charAt(i));
            if(pw.contains(c)){
                pw = pw.replaceFirst(c, "");
                chars++;
            }
        }
        chars -= positions;
        return new int[]{chars,positions};
    }

    int test(){
        int guesses = 0;
        HashSet<String> words = new HashSet<String>();
        words.addAll(dict);
        int[] counts = new int[26];
        for(int i=0;i<counts.length;i++){
            char[] chars = new char[maxLength];
            Arrays.fill(chars, (char)(i+97));
            int[] result = guess(new String(chars));
            counts[i] = result[0] + result[1];
            guesses++;
        }

        int length = 2;
        for(int i=0;i<counts.length;i++){
            length += counts[i];
            if(counts[i]==0)
                words.removeAll(hasLetter.get(i));
        }
        System.out.println(words.size() + ", " + Math.pow(words.size(),3));
        for(String a : words){
            for(String b : words){
                for(String c : words){
                    String check = a + " " + b + " " + c;
                    if(check.length() != length)
                        continue;
                    int[] letters = new int[26]; 
                    for(int i=0;i<check.length();i++){
                        if(check.charAt(i)!=' ')
                            letters[check.charAt(i)-97]++;
                    }
                    int matches = 0;
                    for(int i=0;i<letters.length;i++)
                        if(letters[i] == counts[i])
                            matches+=letters[i];
                    if(matches == check.length()-2){
                        guesses++;
                        int[] result = guess(check);
                        System.out.println(check + " : " + result[0] +", " + result[1]);
                        if(result[0] < 0)
                            return guesses;
                    }
                }
            }
        }
        return -guesses;
    }

    int loadDict(String filename){
        try {
            BufferedReader br = new BufferedReader(new FileReader(filename));
            String line;
            while ((line = br.readLine()) != null){
                if(line.length()*3+2 > maxLength)
                    maxLength = line.length()*3+2;
                dict.add(line);
                for(int i=0;i<line.length();i++){
                    hasLetter.get(line.charAt(i)-97).add(line);
                }
            }
            br.close();
        } catch (Exception e){};
        System.out.println("Loaded " + dict.size() + " words.");
        return dict.size();
    }

    int loadTests(String filename){
        ArrayList<String> tests = new ArrayList<String>();
        try {
            BufferedReader br = new BufferedReader(new FileReader(filename));
            String line;
            while ((line = br.readLine()) != null)
                if(line.length()>0)
                    tests.add(line);
            br.close();
        } catch (Exception e){};
        this.tests = tests.toArray(new String[tests.size()]);
        System.out.println("Loaded " + this.tests.length + " tests.");
        return this.tests.length;
    }
}
Géobits
la source
Publié: hier. Le titre comprend (toujours en cours). M'a fait rire, +1
Bryan Boettcher
@insta C'est vraiment ça. Je pense que 6-7 heures de plus devraient le faire. Estimation d'environ 58 000 suppositions.
Geobits
Je ne serais pas assez patient pour que cela dure si longtemps
Beta Decay
4

Java: 28 340 26 185

Min 15, Max 35, Temps 2.5s

Depuis que mon stupide bot a finalement fini de fonctionner, je voulais soumettre quelque chose un peu plus rapidement. Il fonctionne en quelques secondes, mais obtient un bon score (pas tout à fait gagnant> <).

Tout d'abord, il utilise une grosse chaîne de touches pour obtenir la longueur totale de la phrase. Puis recherche binaire pour trouver des espaces, semblables aux autres. Ce faisant, il commence également à vérifier les lettres une par une (dans l’ordre du pivot) afin d’éliminer les mots qui contiennent plus de lettres que la phrase entière.

Une fois qu'il a la longueur des mots, il utilise une étape de réduction binaire pour affiner le choix des listes de mots. Il choisit la liste la plus longue et une lettre dans environ la moitié des mots. Il devine un bloc de mots de cette lettre pour déterminer la moitié à jeter. Il utilise également les résultats pour supprimer les mots des autres listes contenant trop de lettres.

Une fois qu'une liste ne contient que des anagrammes, cela ne fonctionne pas. À ce stade, je les ai parcourues en boucle jusqu'à ce qu'il ne reste que deux (ou un si les autres mots ne sont pas connus).

Si j'ai un nombre total de mots de quatre (deux connues et une avec deux options), je passe les vérifications de réduction et d'anagramme et je devine l'une des options comme une phrase complète. Si cela ne fonctionne pas, alors il faut que ce soit l'autre, mais j'économise 50% du temps.

Voici un exemple montrant la première phrase en train d'être fissurée:

                                             aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbccccccccccccccccccccddddddddddddddddddddeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffgggggggggggggggggggghhhhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiijjjjjjjjjjjjjjjjjjjjkkkkkkkkkkkkkkkkkkkkllllllllllllllllllllmmmmmmmmmmmmmmmmmmmmnnnnnnnnnnnnnnnnnnnnooooooooooooooooooooppppppppppppppppppppqqqqqqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrrrssssssssssssssssssssttttttttttttttttttttuuuuuuuuuuuuuuuuuuuuvvvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwwwwwwwwwxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyzzzzzzzzzzzzzzzzzzzz
         ..................................................................oooooooooooooooooooo
                 ..................................................................tttttttttttttttttttt
             ..................................................................nnnnnnnnnnnnnnnnnnnn
           ..................................................................llllllllllllllllllll
            ..................................................................iiiiiiiiiiiiiiiiiiii
                    ..................................................................dddddddddddddddddddd
                 ..................................................................uuuuuuuuuuuuuuuuuuuu
                   ..................................................................ssssssssssssssssssss
                  ..................................................................yyyyyyyyyyyyyyyyyyyy
............rrrrrr
............ssssss
...................ttttttttt
............aaaaaa
...................aaaaaaaaa
............iiiiii
sssssssssss
...................lllllllll
............dddddd
............eeeeee
lllllllllll
ccccccccccc
...................ccccccccc
rrrrrrrrrrr
...................bbbbbbbbb
facilitates wisdom briefcase
facilitates widows briefcase

Et bien sûr, le code:

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Splitter {

    int crack(){
        int curGuesses = guesses;
        none = "";
        int[] lens = getLengths();
        List<Set<String>> words = new ArrayList<Set<String>>();
        for(int i=0;i<3;i++){
            words.add(getWordsOfLength(lens[i]));
            exclude[i] = "";

            for(int j=0;j<26;j++){
                if(pCounts[j]>=0)
                    removeWordsWithMoreThan(words.get(i), pivots.charAt(j), pCounts[j]);
            }
        }
        while(!checkSimple(words)){
            if(numWords(words)>4)
                reduce(words, lens);
            if(numWords(words)>4)
                findAnagrams(words, lens);
        }
        return guesses - curGuesses;
    }

    boolean checkSimple(List<Set<String>> words){
        int total = numWords(words);
        if(total - words.size() == 1){
            int big=0;
            for(int i=0;i<words.size();i++)
                if(words.get(i).size()>1)
                    big=i;
            String pass = getPhrase(words);
            if(guess(pass)[0]<0)
                return true;
            words.get(big).remove(pass.split(" ")[big]);
        }

        total = numWords(words);
        if(total==words.size()){
            String pass = getPhrase(words);
            if(guess(pass)[0]<0)
                return true;
        }
        return false;
    }

    boolean findAnagrams(List<Set<String>> words, int[] lens){
        String test;
        Set<String> out;
        for(int k=0;k<words.size();k++){
            if(words.get(k).size() < 8){
                String sorted = "";
                boolean anagram = true;
                for(String word : words.get(k)){
                    char[] chars = word.toCharArray();
                    Arrays.sort(chars);
                    String next = new String(chars);
                    if(sorted.length()>1 && !next.equals(sorted)){
                        anagram = false;
                        break;
                    }
                    sorted = next;
                }
                if(anagram){
                    test = "";
                    for(int i=0;i<k;i++){
                        for(int j=0;j<=lens[i];j++)
                            test += '.';
                    }                   
                    while(words.get(k).size()>(numWords(words)>4?1:2)){
                        out = new HashSet<String>();
                        for(String word : words.get(k)){
                            int correct = guess(test+word)[1];
                            if(correct == lens[k]){
                                words.set(k, new HashSet<String>());
                                words.get(k).add(word);
                                break;
                            }else{
                                out.add(word);
                                break;
                            }
                        }
                        words.get(k).removeAll(out);
                    }
                }
            }
        }
        return false;
    }

    int numWords(List<Set<String>> words){
        int total = 0;
        for(Set<String> set : words)
            total += set.size();
        return total;
    }

    String getPhrase(List<Set<String>> words){
        String out = "";
        for(Set<String> set : words)
            for(String word : set){
                out += word + " ";
                break;
            }
        return out.trim();
    }

    void reduce(List<Set<String>> words, int[] lens){
        int k = 0;
        for(int i=1;i<words.size();i++)
            if(words.get(i).size()>words.get(k).size())
                k=i;
        if(words.get(k).size()<2)
            return;

        char pivot = getPivot(words.get(k), exclude[k]);
        exclude[k] += pivot;
        String test = "";
        for(int i=0;i<k;i++){
            for(int j=0;j<=lens[i];j++)
                test += '.';
        }
        for(int i=0;i<lens[k];i++)
            test += pivot;
        int[] res = guess(test);

        Set<String> out = new HashSet<String>();
        for(String word : words.get(k)){
            int charCount=0;
            for(int i=0;i<word.length();i++)
                if(word.charAt(i)==pivot)
                    charCount++;
            if(charCount != res[1])
                out.add(word);
            if(res[1]==0 && charCount>0)
                out.add(word);
        }
        words.get(k).removeAll(out);

        if(lens[k]>2 && res[0]<lens[k]-res[1]){
            for(int l=0;l<words.size();l++)
                if(l!=k)
                    removeWordsWithMoreThan(words.get(l), pivot, res[0]);
        }
    }

    void removeWordsWithMoreThan(Set<String> words, char c, int num){
        Set<String> out = new HashSet<String>();
        for(String word : words){
            int count = 0;
            for(int i=0;i<word.length();i++)
                if(word.charAt(i)==c)
                    count++;
            if(count > num)
                out.add(word);
        }
        words.removeAll(out);
    }

    char getPivot(Set<String> words, String exclude){
        int[] count = new int[26];
        for(String word : words){
            for(int i=0;i<26;i++)
                if(word.indexOf((char)(i+'a'))>=0)
                    count[i]++;
        }
        double diff = 999;
        double pivotPoint = words.size()/1.64d;
        int pivot = 0;
        for(int i=0;i<26;i++){
            if(exclude.indexOf((char)(i+'a'))>=0)
                continue;
            if(Math.abs(count[i]-pivotPoint)<diff){
                diff = Math.abs(count[i]-pivotPoint);
                pivot = i;
            }
        }
        return (char)(pivot+'a');
    }

    Set<String> getWordsOfLength(int len){
        Set<String> words = new HashSet<String>();
        for(String word : dict)
            if(word.length()==len)
                words.add(word);
        return words;
    }

    int[] pCounts;
    int[] getLengths(){
        String test = "";
        int pivot = 0;
        pCounts = new int[27];
        for(int i=0;i<27;i++)
            pCounts[i]=-1;
        for(int i=0;i<45;i++)
            test += ' ';
        for(int i=0;i<26;i++){
            for(int j=0;j<20;j++){
                test += (char)(i+'a');
            }
        }
        int[] res = guess(test);
        int len = res[0]+res[1];
        int[] lens = new int[3];

        int[] min = {1,3};
        int[] max = {len-4,len-2};
        int p = (int)((max[0]-min[0])/3+min[0]);
        while(lens[0] == 0){
            if(max[0]==min[0]){
                lens[0] = min[0];
                break;
            }
            String g = "", h = "";
            for(int i=0;i<=p;i++)
                g+=' ';
            if(pivot < pivots.length()){
                h += pad;
                for(int i=0;i<20;i++)
                    h += pivots.charAt(pivot);
            }
            res = guess(g+h);
            if(res[1]==0){
                min[0] = p+1;
                min[1] = max[0];
                pCounts[pivot] = g.length()>1?res[0]-2:res[0]-1; 
            }else if(res[1]==2){
                max[0] = p-2;
                max[1] = p;
                pCounts[pivot] = res[0]; 
            }else if(res[1]==1){
                max[0] = p;
                min[1] = p+1;
                pCounts[pivot] = g.length()>1?res[0]-1:res[0]; 
            }
            p = (int)((max[0]-min[0])/2+min[0]);
            pivot++;
        }

        min[1] = Math.max(min[1], lens[0]+2);
        while(lens[1] == 0){
            p = (max[1]-min[1])/2+min[1];
            if(max[1]==min[1]){
                lens[1] = min[1] - lens[0] - 1;
                break;
            }
            String g = "", h = "";
            for(int i=0;i<=p;i++)
                g+=' ';
            if(pivot < pivots.length()){
                h += pad;
                for(int i=0;i<20;i++)
                    h += pivots.charAt(pivot);
            }
            res = guess(g+h);
            if(res[1]<2){
                min[1] = p+1;
                pCounts[pivot] = res[0]-1;
            }else if(res[1]==2){
                max[1] = p;
                pCounts[pivot] = res[0]; 
            }
            pivot++;
        }
        lens[2] = len - lens[0] - lens[1] - 2;  
        return lens;
    }

    int[] guess(String in){
        guesses++;
        int chars=0, positions=0;
        String pw = curPhrase;

        for(int i=0;i<in.length()&&i<pw.length();i++){
            if(in.charAt(i)==pw.charAt(i))
                positions++;
        }
        if(positions == pw.length() && pw.length()==in.length()){
            System.out.println(in);
            return new int[]{-1,positions};
        }

        for(int i=0;i<in.length();i++){
            String c = String.valueOf(in.charAt(i));
            if(pw.contains(c)){
                pw = pw.replaceFirst(c, "");
                chars++;
            }
        }
        System.out.println(in);
        chars -= positions;
        return new int[]{chars,positions};
    }

    void start(){
        long timer = System.currentTimeMillis();
        loadDict("dict-small");
        loadPhrases("passwords");
        exclude = new String[3];
        int min=999,max=0;
        for(String phrase : phrases){
            curPhrase = phrase;
            int tries = crack();
            min=tries<min?tries:min;
            max=tries>max?tries:max;
        }
        System.out.println("\nTotal: " + guesses);
        System.out.println("Min: " + min);
        System.out.println("Max: " + max);
        System.out.println("Time: " + ((System.currentTimeMillis()-timer)/1000d));
    }

    int loadPhrases(String filename){
        phrases = new ArrayList<String>(1000);
        try {
            BufferedReader br = new BufferedReader(new FileReader(filename));
            String line;
            while ((line = br.readLine()) != null)
                if(line.length()>0)
                    phrases.add(line);
            br.close();
        } catch (Exception e){};
        System.out.println("Loaded " + phrases.size() + " phrases.");
        return phrases.size();
    }

    int loadDict(String filename){  
        dict = new HashSet<String>(10000);
        try {
            BufferedReader br = new BufferedReader(new FileReader(filename));
            String line;
            while ((line = br.readLine()) != null)
                dict.add(line);
            br.close();
        } catch (Exception e){};
        System.out.println("Loaded " + dict.size() + " words");     
        return dict.size();
    }

    int guesses;
    double sum = 0;
    List<String> phrases;
    Set<String> dict;
    String curPhrase;
    String[] exclude;
    String none;
    String pivots = "otnlidusypcbwmvfgeahkqrxzj";   // 26185
    String pad = "..................................................................";
    public static void main(String[] args){
        new Splitter().start();
    }   
}
Géobits
la source
4

C # - 10649 (min 8, max 14, moyenne: 10.6) durée: ~ 12 heures

Voici à quoi ça ressemble:

    13, whiteface rends opposed, 00:00:00.1282731, 00:01:53.0087971, 00:00:09.4368140
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggghhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiijjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkklllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopppppppppp    pppppppppppppppppppppppppppppppppppppppppppppppppppppqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssstttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz  
.. . . .  . .  . .  .............................................rrrrrrrrrrrrrrrrrrssssssssssssssssssttttttttttttttttttiiiiiiiiiiiiiiiiiinnnnnnnnnnnnnnnnnnaaaaaaaaaaaaaaaaaa
. . .  . . . . . .  .............................................sssssssssssssssssslllllllllllllllllldddddddddddddddddduuuuuuuuuuuuuuuuuummmmmmmmmmmmmmmmmmrrrrrrrrrrrrrrrrrr
.. . .. ....... .................................................nnnnnnnnnnnnnnnnnnddddddddddddddddddiiiiiiiiiiiiiiiiiiggggggggggggggggggllllllllllllllllllffffffffffffffffff
.. . ............ ...............................................rrrrrrrrrrrrrrrrrrtttttttttttttttttthhhhhhhhhhhhhhhhhhddddddddddddddddddooooooooooooooooooffffffffffffffffff
....... . .......................................................ssssssssssssssssssttttttttttttttttttuuuuuuuuuuuuuuuuuuhhhhhhhhhhhhhhhhhhmmmmmmmmmmmmmmmmmmpppppppppppppppppp
....... ... .....................................................aaaaaaaaaaaaaaaaaa
......... ..... .................................................iiiiiiiiiiiiiiiiii
sheffield eject postwar
projected leigh gathers
portfolio felts escapee
fortescue ethyl affixes
whiteface rends opposed

Solveur

Il utilise un solveur prospectif. Avant de deviner, il estime le nombre de valeurs distinctes renvoyées par le cerveau, compte tenu des phrases de passe actuellement possibles. La supposition qui maximise le nombre de résultats distincts est celle utilisée.

Pour la phase de détermination de l'espace, seules les combinaisons possibles de "" et "." Sont envisagées. Pour la phase de définition des phrases, il crée la liste complète des phrases secrètes actuellement possibles (ce qui explique pourquoi il est si lent).

Lettre compte

Le décompte des lettres est ajouté à la recherche d'espace. Les séries de lettres ont été choisies par une recherche avide, en ajoutant une lettre à la fois et en échantillonnant des phrases de test aléatoires pour vérifier l'efficacité de la série.

Le code est ici: https://github.com/Tyler-Gelvin/MastermindContest

Aucune interface n'étant spécifiée, toutes les entrées sont codées en dur et les tests unitaires constituent la seule interface. Le test "principal" est SolverFixture.SolveParallelAll.

Tyler Gelvin
la source
Je ne trouve pas la Mainfonction dans votre code. At-il un?
Ray
Le test unitaire SolverFixture.SolveSerialAllcorrespond à ce que j’ai utilisé pour obtenir les résultats du test affichés ci-dessus et Solver.Solveconstitue le cœur du programme. C'est un projet de test unitaire sans point d'entrée officiel, donc aucune mainfonction.
Tyler Gelvin
3

C # - Total: 1000, Durée: 305 secondes, Moyenne: 24, Min: 14, Max: 32


Wow Avg <15 c'est assez bon, eh bien je ne peux pas battre ça mais j'ai essayé, alors voici mon approche. Je l'ai cassé mot par mot, puis je les ai résolus successivement. En déterminant la longueur des deux premiers mots et en faisant ensuite quelques suppositions stratégiques (filtrant chaque fois par le mot précédemment deviné), j'ai pu obtenir la réponse avec un nombre de conjectures relativement réduit. Au cours de la période de développement, j’ai pu optimiser la plupart des éléments afin de les préformer efficacement (nombre de suppositions), mais c’est le défaut de la décision de conception initiale de résoudre logiquement un mot à la fois; devine et / ou ne lance pas devine le plus efficacement possible, ce qui veut dire que je ne gagne pas celui-là; (.

Encore une conception intéressante (du moins, je le pense), une chose à noter avec le code inclus, dans certains cas, je peux déterminer la réponse sans même exécuter une supposition qui renvoie -1, si cela est nécessaire, décommentez simplement la ligne de code libellée "AJOUTER GUESS ICI (si nécessaire)" (et ajouter jusqu'à +1 à tous mes scores :()


Algorithme (Ma pensée code Sudo)

Donc, vraiment, il y a deux parties à cela, les deux premiers mots et le dernier mot. Cela n'a aucun sens pour personne d'autre que moi, mais j'ai essayé d'ajouter suffisamment de commentaires au code pour que cela ait plus de sens:

NextWord (l'un des deux premiers mots)

{

var lengthOfPossibleWord = Déterminer la longueur du mot (En code, voir: moyen efficace de trouver la longueur)

Liste des possibilités = Tous les mots de cette longueur (lengthOfPossibleWord)

Faire une proposition

possibilités = possibilités où (pour toutes les suppositions) {le nombre de caractères dans la même position est égal au mot possible

(si outOfPlace caractères est égal à 0) alors où tous les caractères sont différents du mot possible}

}

LastWord (après la résolution des deux premiers)

{

Liste des possibilités = Tous les mots filtrés par le nombre de caractères offPosition dans le second mot (En code, voir: helperWords)

Faire une proposition

possibilités = possibilités où (pour toutes les suppositions) {

Le nombre de caractères dans la même position est égal au mot possible

Somme des caractères in et out of position == mot possible (pour toutes les suppositions)

La longueur est égale ou supérieure à (somme des caractères insérés ou non), longueur du mot possible

(si outOfPlace caractères est égal à 0) alors où tous les caractères sont différents du mot possible

}

}


Code

Remarque: pour que cela fonctionne, vous devez inclure les fichiers ppcg_mastermind_dict.txt et ppcg_mastermind_passes.txt dans le répertoire en cours d'exécution (ou dans le VS dans le même répertoire et définissez "Copier dans le répertoire de sortie" sur true). Je m'excuse vraiment pour la qualité du code, mais il reste encore beaucoup de travail à faire, cela devrait marcher.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;

namespace MastermindHorseBatteryStaple
{
    class Program
    {
        static void Main(string[] args)
        {
            List<int> results = new List<int>();
            var Start = DateTime.UtcNow;
            foreach (var element in File.ReadAllLines(Directory.GetCurrentDirectory() + "\\ppcg_mastermind_passes.txt").ToArray())
            {
                var pas1 = new PassPhrase(element);
                var pasSolve = new PassPhraseCracker();
                var answer = pasSolve.Solve(pas1);
                Console.WriteLine("Answer(C): " + answer);
                Console.WriteLine("Answer(R): " + pas1.currentPassword);
                Console.WriteLine("Equal: " + answer.Equals(pas1.currentPassword));
                Console.WriteLine("Total Cost: " + pas1.count);
                Console.WriteLine();
                results.Add(pas1.count);
            }
            Console.WriteLine("Final Run Time(Seconds): " + (DateTime.UtcNow - Start).TotalSeconds);
            Console.WriteLine("Final Total Cost: " + results.Average());
            Console.WriteLine("Min: " + results.Min());
            Console.WriteLine("Max: " + results.Max());
            Console.ReadLine(); 
        }
    }

class PassPhrase
    {
        public List<string> Words { get; set; }
        public int count = 0;         
        public string currentPassword { get; set; }

        /// <summary>
        /// Declare if you want the class to generate a random password
        /// </summary>
        public PassPhrase()
        {            
            Words = File.ReadAllLines(Directory.GetCurrentDirectory() + "\\ppcg_mastermind_dict.txt").ToList();
            Random random = new Random();
            currentPassword = Words[random.Next(Words.Count())] + " " + Words[random.Next(Words.Count())] + " " + Words[random.Next(Words.Count())];
        }
        /// <summary>
        /// Use if you want to supply a password
        /// </summary>
        /// <param name="Password">The password to be guessed agianst</param>
        public PassPhrase(string Password)
        {
            Words = File.ReadAllLines(Directory.GetCurrentDirectory() + "\\ppcg_mastermind_dict.txt").ToList();
            currentPassword = Password;
        }

        public int[] Guess(String guess)
        {
            count++;
            return Test(guess, currentPassword);
        }
        /// <summary>
        /// This method compares two string and return -1 if equal, 
        /// otherwise it returns the number of character with the same index matching, 
        /// and number of characters matching but in the wrong position
        /// </summary>
        /// <param name="value1">First value to compare</param>
        /// <param name="value2">Second value to compare</param>
        /// <returns>Returns {-1, -1} if equal, 
        /// Two ints the first(0) being the number of chars matching but not in the right postion
        /// The second(1) being the number of chars that match and are in the right position
        /// </returns>
        public int[] Test(String value1, String value2)
        {
            if (String.Equals(value1, value2)) return new int[] { -1, -1 };

            var results = new int[2];
            results[0] = TestNumberOfOutOfPositionCharacters(value1, value2);
            results[1] = TestNumberOfInPositionCharacters(value1, value2);

            return results;
        }
        public int TestNumberOfInPositionCharacters(String value1, String value2)
        {
            var result = 0;
            var value1Collection = value1.ToCharArray();
            var value2Collection = value2.ToCharArray();

            for (int i = 0; i < value1Collection.Count(); i++)
            {
                if (value2Collection.Count() - 1 < i) continue;
                if (value2Collection[i] == value1Collection[i]) result++;
            }
            return result;
        }
        public int TestNumberOfOutOfPositionCharacters(String value1, String value2)
        {
            return CommonCharacters(value1, value2) - TestNumberOfInPositionCharacters(value1, value2);                   
        }

        private int CommonCharacters(string s1, string s2)
        {
            bool[] matchedFlag = new bool[s2.Length];

            for (int i1 = 0; i1 < s1.Length; i1++)
            {
                for (int i2 = 0; i2 < s2.Length; i2++)
                {
                    if (!matchedFlag[i2] && s1.ToCharArray()[i1] == s2.ToCharArray()[i2])
                    {
                        matchedFlag[i2] = true;
                        break;
                    }
                }
            }

            return matchedFlag.Count(u => u);
        }
        private string GetRandomPassword()
        {
            Random rand = new Random();
            return Words[rand.Next(Words.Count())] + " " + Words[rand.Next(Words.Count())] + " " + Words[rand.Next(Words.Count())];
        }        
    }

class PassPhraseCracker
    {
        public class LengthAttempt
        {
            public int Length { get; set; }
            public int Result { get; set; }
        }
        public class WordInformation
        {
            public string Word { get; set; }
            public int[] Result { get; set; }
        }

        public string Solve(PassPhrase pas)
        {
            //The helperWords is used in the final word to lower the number of starting possibilites 
            var helperWords = new List<WordInformation>();
            var first = GetNextWord(pas, "", ref helperWords);

            //TODO: I'm ignoring the helperWords from the first word, 
            //I should do some comparisions with the results of the seconds, this may make finding the last word slightly faster 
            helperWords = new List<WordInformation>();
            var second = GetNextWord(pas, first + " ", ref helperWords);

            //The final Word can be found much faster as we can say that letters in the wrong position are in this word
            var third = GetLastWord(pas, first + " " + second + " ", helperWords);

            return first + " " + second + " " + third;
        }

        private string GetNextWord(PassPhrase pas, string final, ref List<WordInformation> HelperWords)
        {
            var result = new int[] { 0, 0 };
            var currentGuess = final;
            Random random = new Random();
            var triedValues = new List<WordInformation>();

            //The most efficient way to find length of the word that I could come up with
            var triedLengths = new List<LengthAttempt>();
            var lengthAttempts = new List<LengthAttempt>();
            var lengthOptions = pas.Words.AsParallel().GroupBy(a => a.ToCharArray().Count()).OrderByDescending(a => a.Count()).ToArray();
            var length = 0;
            while (length == 0)
            {
                //Find most frequency number of character word between already guessed ones
                var options = lengthOptions.AsParallel().Where(a =>
                    (!lengthAttempts.Any(b => b.Result == 1) || a.Key < lengthAttempts.Where(b => b.Result == 1).Select(b => b.Length).Min()) &&
                    (!lengthAttempts.Any(b => b.Result == 0) || a.Key > lengthAttempts.Where(b => b.Result == 0).Select(b => b.Length).Max()));

                //Rare condition that occurs when the number of characters is equal to 20 and the counter
                //Guesses 18 and 20
                if (!options.Any())
                {
                    length = lengthAttempts.Where(a => a.Result == 1).OrderBy(a => a.Length).First().Length;
                    break;
                }

                var tryValue = options.First();

                //Guess with the current length, plus one space
                //TODO: I can append characters to this and make it a more efficient use of the Guess function, 
                //this would speed up the calculation of the final Word somewhat
                //but this really highlights the failing of this design as characters in the wrong positions can't be deterministically used until the final word
                result = pas.Guess(currentGuess + new String(' ', tryValue.Key) + " ");

                //This part looks at all the attempts and tries to determine the length of the word
                lengthAttempts.Add(new LengthAttempt { Length = tryValue.Key, Result = result[1] - final.Length });

                //For words with length 1
                if (lengthAttempts.Any(a => a.Length == 1 && a.Result == 1))
                    length = 1;

                //For words with the max length 
                if (lengthAttempts.Any(a => a.Length == lengthOptions.Select(b => b.Key).Max() && a.Result == 1))
                    length = lengthAttempts.Single(a => a.Length == lengthOptions.Select(b => b.Key).Max() && a.Result == 1).Length;

                else if (lengthAttempts
                    .Any(a =>
                        a.Result == 1 &&
                        lengthAttempts.Any(b => b.Length == a.Length - 1) &&
                        lengthAttempts.Single(b => b.Length == a.Length - 1).Result == 0))
                    length = lengthAttempts
                        .Single(a =>
                            a.Result == 1 &&
                            lengthAttempts.Any(b => b.Length == a.Length - 1) &&
                            lengthAttempts.Single(b => b.Length == a.Length - 1).Result == 0).Length;
            }

            //Filter by length
            var currentOptions = pas.Words.Where(a => a.Length == length).ToArray();

            //Now try a word, if not found then filter based on all words tried            
            while (result[1] != final.Length + length + 1)
            {
                //Get farthest value, or middle randomly
                //TODO: I've struggled with this allot, and tried many way to some up with the best value to try
                //This is the best I have for now, but there may be a better way of doing it
                var options = currentOptions.AsParallel().OrderByDescending(a => ComputeLevenshteinDistance(a, triedValues.Count() == 0 ? currentOptions[0] : triedValues.Last().Word)).ToList();
                if (random.Next(2) == 1)
                    currentGuess = options.First();
                else
                    currentGuess = options.Skip((int)Math.Round((double)(options.Count() / 2))).First();

                //try it
                result = pas.Guess(final + currentGuess + " ");

                //add it to attempts
                triedValues.Add(new WordInformation { Result = result, Word = currentGuess });

                //filter any future options to things with the same length and equal or more letters in the same position and equal or less letters in the wrong position
                currentOptions = currentOptions.Except(triedValues.Select(a => a.Word)).AsParallel()
                    .Where(a => triedValues.All(b => pas.TestNumberOfInPositionCharacters(a, b.Word) == b.Result[1] - 1 - final.Length))
                    //Special Zero Case
                    .Where(a => triedValues
                    .Where(b => b.Result[1] - 1 - final.Length == 0)
                    .All(b => pas.TestNumberOfInPositionCharacters(a, b.Word) == 0))
                    .ToArray();
            }

            //Add attempts to helper list
            HelperWords = HelperWords.Concat(triedValues.Where(a => a.Result[0] - pas.TestNumberOfOutOfPositionCharacters(a.Word, currentGuess) > 0)
                .Select(a => new WordInformation { Word = a.Word, Result = new int[] { a.Result[0] - pas.TestNumberOfOutOfPositionCharacters(a.Word, currentGuess), a.Result[1] } }).ToList()).ToList();
            return currentGuess;
        }

        private string GetLastWord(PassPhrase pas, string final, List<WordInformation> HelperWords)
        {
            Random rand = new Random();
            var triedList = new List<WordInformation>();
            var result = new int[] { 0, 0 };

            //This uses the helperList from the previous word to attempt help filter the initial possiblities of the last word before preforming the first check
            var currentOptions = pas.Words.AsParallel().Where(a => HelperWords
                .All(b => pas.TestNumberOfOutOfPositionCharacters(a, b.Word) + pas.TestNumberOfInPositionCharacters(a, b.Word) >= b.Result[0])).ToArray();
            var current = final;
            while (result[0] != -1)
            {
                //Here we know the final word but their is no reason to submit it to the guesser(that would cost one more), just return it
                if (currentOptions.Count() == 1)
                {
                    //ADD GUESS HERE(if required)
                    //pas.Guess(final + current);
                    return currentOptions[0];
                }

                //Get farthest value, or middle randomly
                var options = currentOptions.AsParallel()
                    .OrderByDescending(a => ComputeLevenshteinDistance(a, triedList.Count() == 0 ? currentOptions[0] : triedList.Last().Word)).ToList();

                //Get the next value to try
                if (rand.Next(2) == 1)
                    current = options.First();
                else
                    current = options.Skip((int)Math.Round((double)(options.Count() / 2))).First();

                //try it
                result = pas.Guess(final + current);

                //If its the right word return it
                if (result[0] == -1)                     
                    return current;

                //add it to attempts
                triedList.Add(new WordInformation { Result = result, Word = current });

                //filter any future options to things with the same length and equal or more letters in the same position and equal or less letters in the wrong position
                currentOptions = currentOptions.Except(triedList.Select(a => a.Word)).AsParallel()
                    .Where(a => triedList
                        .All(b => pas.TestNumberOfInPositionCharacters(a, b.Word) == b.Result[1] - final.Length &&
                            pas.TestNumberOfInPositionCharacters(a, b.Word) + pas.TestNumberOfOutOfPositionCharacters(a, b.Word) == b.Result[0] + b.Result[1] - final.Length &&
                            a.Length >= pas.TestNumberOfInPositionCharacters(a, b.Word) + pas.TestNumberOfOutOfPositionCharacters(a, b.Word) - final.Length))
                    //Special zero match condition
                    .Where(a => triedList
                    .Where(b => b.Result[1] - final.Length == 0)
                    .All(b => pas.TestNumberOfInPositionCharacters(a, b.Word) == 0)).ToArray();
            }

            return current;
        }

        /// <summary>
        /// http://www.dotnetperls.com/levenshtein
        /// Returns the number of character edits (removals, inserts, replacements) that must occur to get from string A to string B.
        /// </summary>
        /// <param name="s">First string to compare</param>
        /// <param name="t">Second string to compare</param>
        /// <returns>Number of edits needed to turn one string into another</returns>
        private static int ComputeLevenshteinDistance(string s, string t)
        {
            int n = s.Length;
            int m = t.Length;
            int[,] d = new int[n + 1, m + 1];

            // Step 1
            if (n == 0)
            {
                return m;
            }

            if (m == 0)
            {
                return n;
            }

            // Step 2
            for (int i = 0; i <= n; d[i, 0] = i++)
            {
            }

            for (int j = 0; j <= m; d[0, j] = j++)
            {
            }

            // Step 3
            for (int i = 1; i <= n; i++)
            {
                //Step 4
                for (int j = 1; j <= m; j++)
                {
                    // Step 5
                    int cost = (t[j - 1] == s[i - 1]) ? 0 : 1;

                    // Step 6
                    d[i, j] = Math.Min(
                        Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
                        d[i - 1, j - 1] + cost);
                }
            }
            // Step 7
            return d[n, m];
        }
    }
}
David Rogers
la source
2

Python - min: 87, max: 108, total: 96063, temps: 4s

C'est mon deuxième poste. Cette méthode utilise moins de temps mais son score est pire. Et il peut être exécuté en utilisant soit:

  • CPython 2
  • CPython 3
  • Pypy 2 (le plus rapide)
  • Pypy 3

Pas:

  • Trouvez les 2 premiers espaces en utilisant les estimations aiment . ...., .. ......
  • Comptez les fréquences de caractères pour chaque mot du mot de passe.
  • Devinez pour chaque combinaison valide après filtrage par longueur de mot et fréquence de caractères.

Cela coûte environ 90 suppositions pour chaque mot de passe.

from __future__ import print_function
import sys
import itertools
from collections import defaultdict


def run_checker(answer, guesser):
    guess_count = 0
    guesser = guesser()
    guess = next(guesser)
    while True:
        char_count = len(set(guess) & set(answer))
        pos_count = sum(x == y for x, y in zip(answer, guess))
        guess_count += 1
        if answer == guess:
            break
        guess = guesser.send((char_count, pos_count))
    try:
        guesser.send((-1, -1))
    except StopIteration:
        pass
    return guess_count


# Preprocessing
words = list(map(str.rstrip, open('dict.txt')))

M = 26
ord_a = ord('a')

def get_fingerprint(word):
    counts = [0] * M
    for i in map(ord, word):
        counts[i - ord_a] += 1
    return tuple(counts)

P = defaultdict(list)
for word in words:
    P[get_fingerprint(word)].append(word)

# End of preprocessing


def guesser2():
    max_word_len = max(map(len, words))
    max_len = max_word_len * 3 + 2
    spaces = []
    for i in range(1, max_len - 1):
        guess = '.' * i + ' '
        char_count, pos_count = yield guess
        if pos_count > 0:
            spaces.append(i)
            if len(spaces) == 2:
                break

    word_lens = [spaces[0], spaces[1] - spaces[0] - 1, max_word_len]
    C = []
    for i in range(3):
        char_counts = [0] * M
        for j in range(M):
            guess = chr(ord_a + j) * (i + sum(word_lens[:i + 1]))
            _, char_counts[j] = yield guess
        C.append(char_counts)
    for i in (2, 1):
        for j in range(M):
            C[i][j] -= C[i - 1][j]

    candidates = []
    for i in range(3):
        candidates.append(P[tuple(C[i])])
    for i in range(2):
        candidates[i] = [w for w in candidates[i] if word_lens[i] == len(w)]

    try_count = 0
    for result in itertools.product(*candidates):
        guess = ' '.join(result)
        char_count, pos_count = yield guess
        try_count += 1
        if char_count == -1:
            break


def test(test_file, guesser):
    scores = []
    for i, answer in enumerate(map(str.rstrip, open(test_file))):
        print('\r{}'.format(i), end='', file=sys.stderr)
        scores.append(run_checker(answer, guesser))
    print(scores)
    print('sum:{} max:{} min:{}'.format(sum(scores), max(scores), min(scores)))


if __name__ == '__main__':
    test(sys.argv[1], guesser2)
Rayon
la source
2

Perl (toujours en cours ... à partir de maintenant min / moy / max de 8 / 9,2 / 11, estimez-le à 1 500 300 heures de temps d'exécution total)

Mise à jour: modification des suppositions initiales pour l'accélérer quelque peu. Correction d'un bug.

Ce n'est probablement pas fini avant ce concours, mais je pourrais aussi le poster. Il ne détermine pas la longueur des mots, il doit donc vérifier le dictionnaire en entier, ce qui ... prend un certain temps.

Avec les deux premières suppositions, il détermine la longueur totale, le nombre de 'e' et le nombre de caractères différents qu'il contient.

Ensuite, il essaie toutes les combinaisons qui suffisent à ces statistiques ainsi que toutes les suppositions précédentes.

Cette version (et dernière) récente a ajouté mp et fonctionne actuellement sur un système à 24 cœurs.

use strict;
use POSIX ":sys_wait_h";

$| = 1;

my( $buckets );

open my $dict, "<", "dict.txt";
while( <$dict> )
{
  chomp;
  push( @{$buckets->{length($_)}}, [ split // ] );
};
close $dict;


open my $pass, "<", "pass.txt";

my( @pids );
my( $ind ) = 0;

for( my $i = 0; $i < 1000; $i++ )
{
  my $phrase = <$pass>; chomp( $phrase );

  my( $pid ) = fork();

  if( $pid != 0 )
  {
    $pids[$ind] = $pid;
    print join( "; ", @pids ), "\n";

    for( my $j = 0; $j < 18; ++$j, $j %= 18 )
    {
      waitpid( $pids[$j], WNOHANG ) and $ind=$j,last;
      sleep( 1 );
    };
  }
  else
  {
    my( $r ) = &guessPassPhrase( $phrase, $buckets );

    open my $out, ">>", "result.txt";
    print $out "'$phrase' => $r\n";
    close $out;
    exit;
  };
};

close $pass;


sub guessPassPhrase
{
  our( $pp, $buckets ) = @_;
  our( @log ) = undef;
  our( @ppa ) = split //, $pp;
  our( $trys ) = 0;
  our( $invers ) = 1;
  our( $best ) = 0;

  print "Next   : ", $pp, "\n";

  my( @pw1 ) = map { @{$buckets->{$_}} } ( sort { $b <=> $a } keys( %$buckets ));
  my( @pw2, $llt1 );
  my( @pw3, $llt2 );

  my( $t ) = [ (" ")x9,("-")x58,("a".."z") x 64 ];
  my( $y, $c ) = &oracleMeThis( $t );
  my( $l ) = $y + $c;
  push( @log, [ [(" ")x9], 2-$c, $c ] );

  $t = [("a".."z")];
  my( $y, $c ) = &oracleMeThis( $t );
  push( @log, [ $t, $y, $c ] );
  if( $best < ($y + $c) ) { $best = ($y + $c); };
  print "Guessed ($pp:$trys/$best/$l):", @$t, "=> $y/$c             \n";

  $t = [("e")x4];
  my( $y, $c ) = &oracleMeThis( $t );
  push( @log, [ $t, $y, $c ] );
  if( $best < ($y + $c) ) { $best = ($y + $c); };
  print "Guessed ($pp:$trys/$best/$l):", @$t, "=> $y/$c             \n";

  $t = [("i")x6];
  my( $y, $c ) = &oracleMeThis( $t );
  push( @log, [ $t, $y, $c ] );
  if( $best < ($y + $c) ) { $best = ($y + $c); };
  print "Guessed ($pp:$trys/$best/$l):", @$t, "=> $y/$c             \n";

  LOOP1: for my $w1 ( @pw1 )
  {
    my( $t ) = [ @$w1, " " ];

    print "Pondering: ", @$t, "($trys;$best/$l;",$::e1,",",$::e2,")   \r";

    &EliminatePartial( $t ) && ++$::e1 && next;

    if( $llt1 != @$t )
    {
      @pw2 = map { $_ < $l - @$t ? @{$buckets->{$_}} : () } ( sort { $b <=> $a } keys( %$buckets ));
      $llt1 = @$t;
    };

    $llt2 = 0;

    LOOP2: for my $w2 ( @pw2 )
    {
      my( $t ) = [ @$w1, " ", @$w2, " " ];

#      print "Pondering: ", @$t, "(",$::e1,",",$::e2,")                             \r";

      &EliminatePartial( $t ) && ++$::e2 && next;

      if( $llt2 != @$t )
      {
        @pw3 = map { $_ == $l - @$t ? @{$buckets->{$_}} : () } ( sort { $b <=> $a } keys( %$buckets ));
        $llt2 = @$t;
      };

      LOOP3: for my $w3 ( @pw3 )
      {
        my( $t ) = [ @$w1, " ", @$w2, " ", @$w3 ];

        &EliminatePartial( $t ) && next LOOP3;

        my( $y, $c ) = &oracleMeThis( $t );
        push( @log, [ $t, $y, $c ] );
        if( $best < ($y + $c) ) { $best = ($y + $c); };
        print "Guessed ($pp:$trys/$best/$l):", @$t, "=> $y/$c             \n";

        if( $c == $l ) { return( $trys ); };

        if( $c == 0 ) { @pw2 = (); next LOOP1; };
        if( $c == 1 ) { @pw3 = (); next LOOP2; };
        if( $c < @$w1 ) { next LOOP1; };
        if( $c < @$w1 + @$w2 ) { next LOOP2; };

      };
    };
  };

  die( "Failed To Guess" );

  sub EliminatePartial
  {
    my( $guessn ) = @_;

    for my $log ( @log )
    {
      next if !$log;
      my( $guesso, $yo, $co ) = @$log;
      my( $guessos ) = join( "", @$guesso );

      my( $cn ) = scalar( map { $$guesso[$_] eq $$guessn[$_] ? ( 1 ) : () } ( 0 .. ( @$guesso < @$guessn ? @$guesso : @$guessn ) - 1 ));
      my( $yn ) = scalar( map { $guessos =~ s/$_// ? ( 1 ) : () } ( @$guessn )) - $cn;

      return( 1 ) if( $cn > $co || $yn > $yo );
      return( 1 ) if(( $yo - $yn ) + ( $co - $cn ) > $l - @$guessn );
      return( 1 ) if( @$guesso <= @$guessn && $co != $cn );
    };

    return( 0 );
  };

  sub oracleMeThis
  {
    my( $guessn ) = @_;

    $trys++;

    my( $pph ) = $pp;

    my( $cn ) = scalar( map { $ppa[$_] eq $$guessn[$_] ? ( 1 ) : () } ( 0 .. @$guessn - 1 ));
    my( $yn ) = scalar( map { $pph =~ s/$_// ? ( 1 ) : () } ( @$guessn )) - $cn;

    return( $yn, $cn );
  };
};
Thaylon
la source
1

Java 10.026 (en 2.5 heures)

Voici mon code optimisé, maintenant multithread pour améliorer la vitesse:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class MastermindV4MT {

    /*
     * Total guesses: 10026
     * Took: 8461801 ms
     */

    // Order of characters to analyze:
    // eiasrntolcdupmghbyfvkwzxjq - 97
    private int[] lookup = new int[] { 4, 8, 0, 18, 17, 13, 19, 14, 11, 2, 3,
            20, 15, 12, 6, 7, 1, 24, 5, 21, 10, 22, 25, 23, 9, 16 };

    public static void main(String[] args) throws Exception {
        new MastermindV4MT().run();
    }

    int done = 0;
    int totalGuesses = 0;

    private void run() throws Exception {
        long beforeTime = System.currentTimeMillis();
        Map<Integer, List<char[]>> wordMap = createDictionary();
        List<String> passPhrases = createPassPhrases();

        ExecutorService executor = Executors.newFixedThreadPool(8);

        for(String phrase:passPhrases) {
            executor.execute(new Runnable() {
                public void run() {
                    int guesses = solve(wordMap, phrase);
                    totalGuesses+=guesses;
                    done++;
                    System.out.println("At "+done+" of "+passPhrases.size()+" just added "+guesses+" predicted score: "+((1.0*totalGuesses)/done)*passPhrases.size());
                };
            });
        }
        executor.shutdown();
        try {
            executor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS);
        } catch (InterruptedException e) {
        }
        System.out.println("Total guesses: " + totalGuesses);
        System.out.println("Took: " + (System.currentTimeMillis() - beforeTime) + " ms");
    }

    int[] guess(char[] in, char[] pw, char[] pwsorted) {
        int chars = 0, positions = 0;

        char[] inc = Arrays.copyOf(in, in.length);

        for (int i = 0; i < inc.length && i < pw.length; i++) {
            if (inc[i] == pw[i])
                positions++;
        }
        if (positions == pw.length && pw.length == inc.length)
            return new int[] { -1, positions };

        Arrays.sort(inc);
        int i1 = 0;
        int i2 = 0;
        while(i1 < pwsorted.length && i2 < inc.length) {
            if(inc[i2]==pwsorted[i1]) {
                i1++;
                i2++;
                chars++;
            } else if(inc[i2]<pwsorted[i1]) {
                i2++;
            } else {
                i1++;
            }
        }

        chars -= positions;
        return new int[] { chars, positions };
    }

    private int solve(Map<Integer, List<char[]>> wordMap, String password) {

        // Do one initial guess which gives us two things:
        // The amount of characters in total
        // The amount of e's

        char[] pw = password.toCharArray();
        char[] pwsorted = password.toCharArray();
        Arrays.sort(pwsorted);

        int[] initialResult = guess(Facts.INITIAL_GUESS.toCharArray(), pw, pwsorted);
        int guesses = 1;

        // Create the object that tracks all the known facts/bounds:
        Facts facts = new Facts(initialResult);

        // Determine a pivot and find the spaces (binary search)
        int center = ((initialResult[0] + initialResult[1]) / 2) + 1;
        guesses += findSpaces(center, facts, pw, pwsorted);

        // We know the first word length, the second might have some bounds, but
        // is unknown:
        // We can calculate the lengths:
        int minLength1 = facts.spaceBounds[0] - 1;
        int maxLength1 = facts.spaceBounds[1] - 1;

        char[] phraseBuilder = new char[facts.totalLength+2];

        for (int length1 = minLength1; length1 <= maxLength1;length1++) {

            if (wordMap.get(length1) == null) {
                continue;
            }

            for (char[] w1 : wordMap.get(length1)) {
                for(int i = 0; i<w1.length;i++) {
                    phraseBuilder[i] = w1[i];
                }
                phraseBuilder[w1.length] = ' ';

                if (facts.partialMatches(phraseBuilder, facts.totalLength+1-w1.length)) {

                    int minLength2 = (facts.spaceBounds[2] - length1 - 2);
                    int maxLength2 = (facts.spaceBounds[3] - length1 - 2);

                    for (int length2 = minLength2; length2 <= maxLength2;length2++) {

                        if (wordMap.get(length2) == null) {
                            continue;
                        }

                        for (char[] w2 : wordMap.get(length2)) {

                            // Continue if (according to our facts) this word is a
                            // partial match:
                            for(int i = 0; i<length2;i++) {
                                phraseBuilder[w1.length+1+i] = w2[i];
                            }
                            phraseBuilder[w1.length+w2.length+1] = ' ';

                            if (facts.partialMatches(phraseBuilder, facts.totalLength-(w1.length+w2.length))) {

                                if (wordMap.get(facts.totalLength - length2 - length1) == null) {
                                    continue;
                                }

                                int length3 = facts.totalLength - length2 - length1;
                                for (char[] w3 : wordMap.get(length3)) {

                                    for(int i = 0; i<length3;i++) {
                                        phraseBuilder[w1.length+w2.length+2+i] = w3[i];
                                    }

                                    if (facts.matches(phraseBuilder)) {
                                        int[] result = guess(phraseBuilder, pw, pwsorted);
                                        guesses++;

                                        //String possiblePhrase = new String(phraseBuilder);
                                        //System.out.println(possiblePhrase + " " + Arrays.toString(result));
                                        if (result[0] == -1) {
                                            return guesses;
                                        }
                                        // No match, update facts:
                                        facts.storeInvalid(phraseBuilder.clone(), result);
                                    }
                                }
                                for(int i = 0; i<phraseBuilder.length-(w1.length+2+w2.length);i++) {
                                    phraseBuilder[w1.length+w2.length+2+i] = '-';
                                }
                            }
                        }
                        for(int i = 0; i<phraseBuilder.length-(w1.length+1);i++) {
                            phraseBuilder[w1.length+1+i] = '-';
                        }

                    }
                }
            }
        }
        throw new IllegalArgumentException("Unable to solve!?");
    }

    private int findSpaces(int center, Facts facts, char[] pw, char[] pwsorted) {
        char[] testPhrase = new char[facts.totalLength + 2+facts.charBounds[lookup[facts.charPtr]]];
        // Place spaces for analysis:
        int ptr = 0;
        for (int i = 0; i < center; i++) {
            testPhrase[ptr++] = ' ';
        }
        while (ptr < (facts.totalLength + 2)) {
            testPhrase[ptr++] = '-';
        }

        // Append extra characters for added information early on:
        for (int i = 0; i < facts.charBounds[lookup[facts.charPtr]]; i++) {
            testPhrase[ptr++] = (char) (lookup[facts.charPtr] + 97);
        }

        // Update space lower and upper bounds:
        int[] answer = guess(testPhrase, pw, pwsorted);
        if (answer[1] == 0) {
            facts.spaceBounds[0] = Math.max(facts.spaceBounds[0], center + 1);
            facts.spaceBounds[2] = Math.max(facts.spaceBounds[2], center + 3);
        } else if (answer[1] == 1) {
            facts.spaceBounds[1] = Math.min(facts.spaceBounds[1], center);
            facts.spaceBounds[2] = Math.max(facts.spaceBounds[2], center + 1);
        } else {
            facts.spaceBounds[3] = Math.min(facts.spaceBounds[3], center);
            facts.spaceBounds[1] = Math.min(facts.spaceBounds[1], center - 2);
        }
        int correctAmountChars = (answer[0] + answer[1]) - 2;
        facts.updateCharBounds(correctAmountChars);
        // System.out.println(Arrays.toString(facts.spaceBounds));
        if (facts.spaceBounds[1]-facts.spaceBounds[0]<5) {
            // Only find the first space
            return 1;
            //if(facts.spaceBounds[3]-facts.spaceBounds[2]<4) return;
            //findSpaces(facts.spaceBounds[2] + ((facts.spaceBounds[3]-facts.spaceBounds[2])/3), facts, pw, pwsorted);
        } else {
            return 1+findSpaces((facts.spaceBounds[0] + facts.spaceBounds[1]) / 2, facts, pw, pwsorted);
        }
    }

    private class Facts {

        private static final String INITIAL_GUESS = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbccccccccccccccccccddddddddddddddddddffffffffffffffffffgggggggggggggggggghhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiijjjjjjjjjjjjjjjjjjkkkkkkkkkkkkkkkkkkllllllllllllllllllmmmmmmmmmmmmmmmmmmnnnnnnnnnnnnnnnnnnooooooooooooooooooppppppppppppppppppqqqqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrssssssssssssssssssttttttttttttttttttuuuuuuuuuuuuuuuuuuvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwwwwwwwxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyzzzzzzzzzzzzzzzzzz";
        private final int totalLength;
        private final int[] spaceBounds;
        // Pre-filled with maximum bounds obtained from dictionary:
        private final int[] charBounds = new int[] { 12, 9, 9, 9, 15, 9, 12, 9, 18, 6, 9, 12, 9, 12, 12, 9, 3, 12, 15, 9, 12, 6, 6, 3, 9, 6 };
        private int charPtr;

        public Facts(int[] initialResult) {

            totalLength = initialResult[0] + initialResult[1];
            spaceBounds = new int[] { 2, Math.min(totalLength - 2, 22), 4, Math.min(totalLength + 1, 43) };

            // Eliminate firsts
            charBounds[lookup[0]] = initialResult[1];
            // Adjust:
            for (int i = 1; i < charBounds.length; i++) {
                charBounds[lookup[i]] = Math.min(charBounds[lookup[i]], totalLength - initialResult[1]);
            }
            charPtr = 1;
        }

        private List<char[]> previousGuesses = new ArrayList<char[]>();
        private List<int[]> previousResults = new ArrayList<int[]>();

        public void storeInvalid(char[] phrase, int[] result) {
            previousGuesses.add(phrase);
            previousResults.add(result);
        }

        public void updateCharBounds(int correctAmountChars) {

            // Update the bounds we know for a certain character:
            int knownCharBounds = 0;
            charBounds[lookup[charPtr]] = correctAmountChars;
            for (int i = 0; i <= charPtr; i++) {
                knownCharBounds += charBounds[lookup[i]];
            }
            // Also update the ones we haven't checked yet, we might know
            // something about them now:
            for (int i = charPtr + 1; i < charBounds.length; i++) {
                charBounds[lookup[i]] = Math.min(charBounds[lookup[i]], totalLength - knownCharBounds);
            }
            charPtr++;
            while (charPtr < 26 && charBounds[lookup[charPtr]] == 0) {
                charPtr++;
            }
        }

        public boolean partialMatches(char[] phrase, int amountUnknown) {

            //Try to match a partial phrase, we can't be too picky because we don't know what else is next
            Arrays.fill(cUsed, 0);
            for(int i = 0; i<phrase.length; i++) {
                if(phrase[i]!=' ' && phrase[i]!='-'&&phrase[i]!=0) {
                    cUsed[phrase[i]-97]++;
                }
            }
            for(int i = 0; i<cUsed.length; i++) {
                //Only eliminate the phrases that definitely have wrong characters:
                if(cUsed[i] > charBounds[i]) {
                    return false;
                }
            }
            //Check again previous guesses:
            int cnt = 0;
            char[] phraseSorted = phrase.clone();
            Arrays.sort(phraseSorted);
            for(char[] previousGuess:previousGuesses) {
                // If the input phrase is the correct phrase it should score the same against previous tries:
                int[] result = guess(previousGuess, phrase, phraseSorted);
                int[] expectedResult = previousResults.get(cnt);

                //Some cases we can stop early:
                if(result[0]+result[1] > expectedResult[0]+expectedResult[1]) {
                    return false;
                }
                if(result[1]>expectedResult[1]) {
                    return false;
                }
                if(result[0]+amountUnknown<expectedResult[0]) {
                    return false;
                }
                if(result[1]+amountUnknown<expectedResult[1]) {
                    return false;
                }
                if(result[0]+result[1]+amountUnknown < expectedResult[1]+expectedResult[0]) {
                    return false;
                }
                cnt++;
            }
            return true;
        }

        int[] cUsed = new int[26];
        public boolean matches(char[] phrase) {

            // Try to match a complete phrase, we can now use all information:
            Arrays.fill(cUsed, 0);
            for (int i = 0; i < phrase.length; i++) {
                if(phrase[i]!=' ' && phrase[i]!='-'&&phrase[i]!=0) {
                    cUsed[phrase[i] - 97]++;
                }
            }

            for (int i = 0; i < cUsed.length; i++) {
                if (i < charPtr) {
                    if (cUsed[lookup[i]] != charBounds[lookup[i]]) {
                        return false;
                    }
                } else {
                    if (cUsed[lookup[i]] > charBounds[lookup[i]]) {
                        return false;
                    }
                }
            }

            // Check again previous guesses:
            char[] phraseSorted = phrase.clone();
            Arrays.sort(phraseSorted);
            int cnt = 0;
            for(char[] previousGuess:previousGuesses) {
                // If the input phrase is the correct phrase it should score the
                // same against previous tries:
                int[] result = guess(previousGuess, phrase, phraseSorted);
                int[] expectedResult = previousResults.get(cnt);
                if (!Arrays.equals(expectedResult, result)) {
                    return false;
                }
                cnt++;
            }
            return true;
        }
    }

    private List<String> createPassPhrases() throws Exception {
        BufferedReader reader = new BufferedReader(new FileReader(new File("pass.txt")));
        List<String> phrases = new ArrayList<String>();
        String input;
        while ((input = reader.readLine()) != null) {
            phrases.add(input);
        }
        return phrases;
    }

    private Map<Integer, List<char[]>> createDictionary() throws Exception {
        BufferedReader reader = new BufferedReader(new FileReader(new File("words.txt")));
        Map<Integer, List<char[]>> wordMap = new HashMap<Integer, List<char[]>>();
        String input;
        while ((input = reader.readLine()) != null) {
            List<char[]> words = wordMap.get(input.length());
            if (words == null) {
                words = new ArrayList<char[]>();
            }
            words.add(input.toCharArray());
            wordMap.put(input.length(), words);
        }
        return wordMap;
    }

}
Roy van Rijn
la source