Натхненний кінь акумулятор штапел


48

Об'єктивна

Давши список трискладних парольних фраз, зламайте їх усі. Кожного разу, коли ви здогадаєтесь, вам буде надано підказку в стилі Mastermind , що зображує, скільки символів відповідають паролю, а скільки - у правильному положенні. Мета - мінімізувати загальну кількість здогадів щодо всіх тестових випадків.

Паролі

Зі списку слів за замовчуванням у моїй системі я випадковим чином вибрав 10000 різних слів, щоб скласти словник для цього завдання. Усі слова складаються a-zлише з. Цей словник можна знайти тут ( сировину ).

З цього словника я створив 1000 парольних фраз, що складаються з трьох випадкових розділених між собою проміжків слів ( apple jacks feverнаприклад,). Окремі слова можна повторно використовувати в межах кожної парольної фрази ( hungry hungry hippos). Ви можете знайти список парольних фраз тут ( необроблений ), по одному на рядок.

Ваша програма може використовувати / аналізувати словниковий файл, як тільки ви хочете. Ви не можете проаналізувати парольні фрази для оптимізації для цього конкретного списку. Ваш алгоритм все ще повинен працювати з іншим списком фраз

Відгадування

Щоб здогадатися, ви подаєте рядок у шашку. Він повинен повертатися лише :

  • Кількість символів у рядку також у фразі ( не в правильному положенні)
  • Кількість символів у правильному положенні

Якщо ваш рядок є ідеальним збігом, він може вивести щось, що вказує на це (моя використовується -1для першого значення).

Наприклад, якщо пароль the big catі ви здогадуєтесь tiger baby mauling, шашка повинна повернутися 7,1. 7 символів ( ige<space>ba<space>) знаходяться в обох рядках, але різні позиції, а 1 ( t) знаходиться в одному і тому ж положенні в обох. Зверніть увагу, що кількість пробілів.

Я написав на Java приклад (читайте: не оптимізований) функцію, але сміливо пишіть свою, доки вона дає лише необхідну інформацію.

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};
}

Оцінка балів

Ваш бал - це просто кількість здогадок, які ви подаєте до перевірки (підраховуючи остаточний, правильний) для всіх тестових фраз. Виграє найнижчий бал.

Ви повинні зламати всі фрази у списку. Якщо ваша програма не працює на будь-якому з них, вона недійсна.

Ваша програма повинна бути детермінованою. Якщо запустити двічі на одному наборі парольних фраз, він повинен повернути той самий результат.

У випадку, коли я зрівняю спочатку, я проведу зв'язані записи на своєму комп’ютері чотири рази кожен, і найменший середній час для вирішення всіх 1000 випадків виграє. На моєму комп’ютері працює Ubuntu 14.04, з i7-3770K і 16 ГБ оперативної пам’яті, на випадок , якщо це змінить вашу програму. З цієї причини і для полегшення тестування ваша відповідь повинна бути мовою, яка має компілятор / перекладач, який можна безкоштовно завантажити з Інтернету (не включаючи безкоштовні випробування) і не потребуючи реєстрації / реєстрації.

Назва адаптована з XKCD


1
Назва від xkcd.com/936
NinjaBearMonkey

Чи можу я ввести рядки, крім a..z та пробіл у рядку для подання?
Рей

@Ray Я не можу придумати причину, чому б не в даний момент, але я не впевнений, що це отримує від тебе. Іди до цього, мені цікаво.
Геобіт

3
Чи можуть люди подати себе? Почну: "Варто зняти"
AndoDaan

2
@AndoDaan Перша фраза? 9 0. Це може зайняти деякий час: P
Geobits

Відповіді:


13

Scala 9146 (хв. 7, макс. 15, сер. 9,15) час: 2000 секунд

Як і багато записів, я починаю з загальної довжини, потім знаходжу пробіли, отримую трохи більше інформації, зводячи до інших кандидатів, а потім відгадуючи фрази.

Надихнувшись оригінальним коміксом xkcd, я спробував застосувати своє рудиментарне розуміння теорії інформації. Є трильйон можливих фраз або трохи менше 40 біт ентропії. Я поставив за мету менше 10 здогадок на тестову фразу, а це означає, що нам потрібно вивчити в середньому майже 5 біт на запит (оскільки останній марний). З кожною здогадкою ми отримуємо назад два числа і, грубо кажучи, чим більше потенційний діапазон цих чисел, тим більше ми очікуємо дізнатися.

Для спрощення логіки я використовую кожен запит як ефективне два окремих питання, тому кожен рядок відгадки - це дві частини, лівий бік зацікавлений у кількості правильних позицій (чорні кілочки в натхненнику) та правий бік зацікавлений у кількості правильних знаків ( загальні кілочки). Ось типова гра:

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

Відгадування пробілів

Кожна космічна здогадка може повернути не більше 2 чорних кілочків; Я намагався побудувати здогади, щоб повернути 0,1, 2 кілочки з ймовірністю 1/4,1 / 2 та 1/4 відповідно. Я вважаю, що це найкраще, що можна зробити для очікуваних 1,5 біт інформації. Я зупинився на змінній рядку для першого здогаду, за яким послідували випадково згенеровані, хоча, як виявляється, зазвичай варто лише починати здогадуватися з другої чи третьої спроби, оскільки ми знаємо частоти слова довжини.

Навчається набір символів

Для правого припущення я вибираю випадкові (завжди 2 з e / i / a / s) набори символів, так що очікуване число, що повертається, становить половину довжини фрази. Більш висока дисперсія означає більше інформації, і зі сторінки вікіпедії на біноміальному розподілі я оцінюю приблизно 3,5 біта на запит (принаймні, для перших кількох, перш ніж інформація стане зайвою). Після того, як буде відомий інтервал, я використовую випадкові рядки найпоширеніших літер з лівого боку, вибрані так, щоб не суперечити правим.

Об'єднання решти кандидатів

Ця гра є компромісом швидкості / ефективності запитів, і перерахування решти кандидатів може зайняти дуже багато часу без структурованої інформації, як конкретні символи. Я оптимізував цю частину, в основному збираючи інформацію, інваріантну для порядку слів, яка дозволяє мені попередньо обчислити набір символів для кожного окремого слова і порівняти їх з підрахунками, отриманими за запитами. Я пакую ці підрахунки у довге ціле число, використовуючи порівняльник машинної рівності та суматор, щоб перевірити всі мої підрахунки символів паралельно. Це був величезний виграш. Я можу спакувати до 9 рахунків у Довгому, але я виявив, що збір додаткової інформації не вартий цього, і я зупинився на 6 - 7.

Після того, як решта кандидатів будуть відомі, якщо набір досить невеликий, я вибираю той, у якого найменший очікуваний журнал решти кандидатів. Якщо набір досить великий, щоб зробити це забираючи багато часу, я вибираю з невеликого набору зразків.

Дякую всім. Ця забавна гра і спонукала мене зареєструватися на сайті.

Оновлення: очищений код для простоти та читабельності, з незначними налаштуваннями алгоритму, що призводить до покращення балів.
Оригінальна оцінка: 9447 (хв. 7, макс. 13, серп. 9,45) час: 1876 секунд

Новий код - 278 рядків Scala, нижче

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

2
Ласкаво просимо на сайт і вітаємо! Ви багато чого не зробили, а зробили це. Майте кілька уявних точок Інтернету!
Геобіт

Просто яскраво.

Дивовижне рішення! Це єдиний під знаком 10 000!
Санджай Джайн

15

C - всього: 37171, хв: 24, макс .: 55, час: близько 10 секунд

Я запозичив ідею Рея знайти довжину кожного слова, вгадуючи пробіли, за винятком того, що я роблю двійковий пошук, а не лінійний, що економить мені багато здогадок.

Як тільки я визначаю довжину слова, я здогадуюсь з першим словом, яке відповідає його довжині, і я записую кількість правильних позицій. Тоді я вибираю перше слово з набору всіх слів, які поділяють однакову кількість позицій з моїм першим здогадком, як і загадкове слово. Для моєї третьої здогадки я вибираю перше слово з набору всіх слів, які поділяють однакову кількість позицій з моїм першим здогадком, як і слово таємниці, і таку ж кількість позицій, як і друге здогадка із словом таємниці тощо.

Використовуючи цей метод, я можу вгадати кожне слово, по одному, приблизно в 5-10 здогадів. Очевидно, що третє слово я повинен зробити трохи інакше, тому що я не знаю його довжини, але метод схожий. Я використовую файл, який містить матрицю кількості позицій, якими ділиться кожне слово, яке я попередньо обчислював. Більшість часу виконання відбувається під час завантаження попередньо обчислених даних. Ви можете завантажити все тут .

#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;
}

Також цікаво спостерігати, як це звужується словами:

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.

1
Мені подобається цей, інтуїтивно зрозумілим наступним кроком може бути переконання, що список слів, який ви використовуєте для здогадок, упорядкований потужним чином. Принаймні переконайтеся, що у вас є гарне початкове слово для кожної кількості букв.
Денніс Джахеруддін

Це хороша ідея. Дякуємо за відгук.
Орбі

15

Python 3.4 - хв: 21, макс .: 29, всього: 25146, час: 20хв

хв: 30, макс .: 235, всього: 41636, час: 4хв

Оновлення:

  1. Використовуйте двійковий пошук, щоб знайти простір. Ідея запозичена з відповіді Орбі . Одне місце, яке я оптимізував, - це те, що якщо ви знайшли 2 пробіли в діапазоні під час пошуку першого місця, ви можете звузити область пошуку другого простору.
  2. Збережіть неправильні здогадки разом з їх результатом. Порівняйте їх з наступними здогадами. Це може зекономити багато.
  3. Зменшіть кількість літер до 12, завдяки оновленню №2.

введіть тут опис зображення


Цей меход не використовує випадковості, тому оцінка не зміниться.

Спочатку використовуйте здогадки на зразок наступного, щоб знайти перший та другий пробіли у відповіді.

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

Потім він рахує появу кожної літери, здогадуючись aaaaa..., bbbbb....... Після цього це обійдеться приблизно в 40 кроків. Насправді нам не потрібно пробувати всі букви, і ми можемо їх спробувати в довільному порядку. У більшості випадків достатньо спробувати близько 20 букв. Тут я обираю 21.

Тепер він знає довжину першого і другого слова, щоб він міг відфільтрувати список кандидатів на ці два слова. Зазвичай в ньому залишиться близько 100 кандидатів.

Тоді воно просто перераховує перше і друге слово. Після перерахунку перших двох слів ми можемо зробити висновок про все дійсне третє слово, оскільки ми знаємо, що це число символів.

Щоб оптимізувати швидкість, я використовую concurrent.futuresдля додавання в програму багатопроцесорної обробки. Тож вам потрібен Python 3, щоб запустити його, і я перевірив його на Python 3.4 на моєму вікні Linux. Також вам потрібно встановити 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)

1
Мені справді важко отримати це нижче. Хороша робота.
Орбі

1
Як ви створили графік?
Бета-розпад

1
@BetaDecay Сценарій, що використовує matplotlib.
Рей

1
@DennisJaheruddin Так, це дуже некрасиво. Виправлено зараз.
Рей

1
Я вважаю, що ви повинні використовувати matplotlibs xkcdify для графіка matplotlib.org/xkcd/examples/showcase/xkcd.html
MrLemon

14

Java 13 923 (хв: 11, макс .: 17)

Оновлення: покращений бал (зламав <14 / crack avg!), Новий код

  • Перевірка відомих персонажів тепер щільніше (тепер ABABAB *, а не -AAAA *)
  • Якщо жодних відомих символів немає, дві невідомі будуть зараховані в одну здогадку
  • Неправильні здогадки зберігаються та використовуються для перевірки можливих відповідностей
  • Постійне налаштування з новою логікою

Оригінальна публікація

Я вирішив повністю зосередитися на кількості здогадів замість продуктивності (з урахуванням правил). Це призвело до дуже повільної розумної програми.

Замість крадіжок з відомих програм я вирішив написати все з нуля, але, виявляється, деякі / більшість ідей однакові.

Алгоритм

Ось як працює шахта:

  1. Зробіть єдиний запит, який призводить до кількості e та символів усього
  2. Далі шукаємо пробіли, додаючи в кінці невідомі символи, щоб отримати кількість символів
  3. Коли ми знайдемо пробіли, ми все ще хочемо дізнатись більше кількості символів, я в цей час також отримаю більше даних про відомих персонажів (якщо вони є на рівних позиціях), які допоможуть мені усунути багато фраз.
  4. Коли ми досягнемо певної межі (слід / помилка), вона генерує всі можливі фрази та починає двійковий пошук, більшу частину часу все ще додаючи невідомі символи в кінці.
  5. Нарешті ми робимо кілька здогадок!

Приклад здогадок

Ось фактичний приклад:

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

Оскільки мій код ніколи насправді не фокусується на одних словах і лише збирає інформацію про повну фразу, він повинен генерувати безліч цих фраз, роблячи це дуже повільним.

Код

І нарешті ось (потворний) код, навіть не намагайтеся його зрозуміти, вибачте:

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;
    }

}

Ви, такі розумні, хлопці.
Рей

2
Це геніальна ідея підраховувати частотні символи паралельно з пошуком пробілів.
Промінь

1
Треба сказати, що я навіть не можу почати обертати голову навколо вашої нечесної техніки, ні абстрактним мисленням, ні зворотною інженерією. Я також не розумію, як ви викликаєте функцію узгодження пароля, не рахуючи зайвих здогадок. Трохи пояснень буде вітатися.

12

Java - 18 708 запитів; 2,4 секунди 11,077 запитів; 125 хв.

Мінімум: 8, Макс: 13, Ефективних запитів: 10 095

Я занадто довго витрачав на це. : P

Код доступний на веб-сайті http://pastebin.com/7n9a50NM

Випуск 1. доступний на веб-сайті http://pastebin.com/PSXU2bga

Випуск 2. доступний на веб-сайті http://pastebin.com/gRJjpbbu

Моя друга редакція. Я сподівався зламати бар'єр 11К, щоб виграти приз, але мені не вистачило часу на оптимізацію цього звіра.

Він працює за цілком окремим принципом від двох попередніх версій (і запускає приблизно 3500 разів). Загальний принцип полягає у використанні просіювання простору та парних / непарних символів для зменшення списку кандидатів до керованого розміру (як правило, між 2-8 мільйонами), а потім виконувати повторні запити з максимальною можливістю дискримінації (тобто розподіл вихідних даних максимізував ентропію).

Не швидкість, а пам'ять - головне обмеження. Моя програма Java VM не дозволить мені резервувати купу, більшу за 1200 МБ, з незрозумілих причин (можливо, Windows 7), і я налаштував параметри, щоб отримати найкраще рішення, яке не вичерпує цю межу. Мене дратує те, що правильний запуск з належними параметрами би зламав 11K без значного збільшення часу виконання. Мені потрібен новий комп’ютер. : P

Мене так само дратує те, що 982 запитів у цій реалізації є марними запитами "перевірки". Вони не мають іншого призначення, ніж задовольнити правило про те, що оракул повинен якось повернути особливе значення "ви його отримали", хоча в моєму виконанні правильна відповідь була виведена з певністю перед цим запитом у 98,2% випадків. Більшість інших заявок на під-11К покладаються на методи фільтрації, які використовують рядки-кандидати в якості рядків запитів, і, отже, вони не зазнають однакового штрафу.

З цієї причини, хоча кількість моїх офіційних запитів становить 11 077 (за винятком лідерів, за умови, що їх код виявляється сумісним, відповідає дійсності та ін.), Я сміливо заявляю, що мій код складає 10 095 ефективних запитів, тобто лише 10 095 запитів Фактично необхідно визначити всі фразові фрази зі 100% визначеністю. Я не впевнений, що будь-яка інша реалізація відповідатиме цьому, тому я вважаю, що це моя перемога. ;)


ZPC - це добре, інші записи також використовують їх. Я думаю, що найпоширеніше ..
Геобіт

Поточний код не включає запит "перевірки". Я зараз додам його.
COTO

Я оновив до вер. 1, який включає запит перевірки. Не дивно, що кількість запитів точно на 1000 перевищує попередню версію.
COTO

1
Це дуже приємно. Ваша Java настільки Java-у, це боляче. Я не звик бачити такий код на цьому сайті: D
Geobits

+1 як епічні, так і"perpetually exhausting pool"
cjfaure

8

Java - хв: 22, макс .: 41, всього: 28353, час: 4 секунди

Програма відгадує пароль у 3 етапи:

  1. знайти місця пробілу за допомогою двійкового пошуку
  2. порахуйте кількість найчастіших символів у 3 словах
  3. знайдіть слова, починаючи зліва, використовуючи інформацію, зібрану вище

Він також обробляє набір "поганих символів", які повертають нульовий результат у пошуку, і набір "хороших символів", які розміщені десь ще в парольній фразі.

Нижче прикладу значень, що послідовно надсилаються для відгадування, ви можете побачити 3 кроки:

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

Код:

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;
    }
}

7

PYTHON 2.7 - 156821 здогади, 0,6 секунди

Я пішов на швидкість, а не на найменшу кількість здогадок, хоча я вважаю, що кількість моїх здогадок все ще менша, ніж, наприклад, атака словника прямо. Я не обчислюю кількість букв у паролі, але в неправильному місці, оскільки мій метод не використовує його, але якщо ви вважаєте, що це дає мені несправедливу перевагу, я його застосую. Я просто починаю з порожнього рядка відгадок і додаю до нього один суфікс символів, який збільшується над моїм списком символів, перевіряючи результат "перевірки", щоб побачити, чи кількість правильних символів дорівнює довжині здогадки. Наприклад, якби пароль був "поганим", я б здогадався:

а, б

а

а Б В Г

Я також спробував сортувати букви за частотою англійських букв, яка збривала приблизно 35% від кількості здогадок, а також часу. Я зламав усі паролі за 0,82 секунди. Статистика друкується в кінці.

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

РЕДАКТУВАННЯ: Вилучено заблоковані +1 і -1 з двох циклів у той час як попередні ітерації тестування, також додано додаткові статистичні дані для найменших здогадок та більшості здогадок для індивідуального пароля.

EDIT2: додана таблиця пошуку для найпоширенішого "наступного" листа за буквою. Значно збільшена швидкість і зменшена кількість здогадів


2
Хоча це дуже швидко, це, безумовно, використовує купу здогадок. Ви, можливо, зможете трохи покращитись, використовуючи частоту букв файлу dict, а не звичайну англійську.
Геобіць

@Geobits Виправлені помилки, у мене був -1 у операторі if у nextPass () та +1 у циклі while у main (), обидва з попередніх ітераційних тестувань. Тепер виводить кожен пароль один раз, якщо ви будете тримати рядок 65
дюймів

7

C ++ - 11383 10989 Матчі!

Оновлення

Виправлено витоки пам’яті та вилучено ще 1 спробу зменшити розмір словника окремих слів. Проходить на моєму комп'ютері Mac приблизно 50 хвилин. Оновлений код знаходиться на github.


Я перейшов до стратегії фразової відповідності та переробив код і оновив його на github https://github.com/snjyjn/mastermind

Завдяки відповідності на основі фрази ми знизимося до 11383 спроб! Це дорого з точки зору обчислення! Мені також не подобається структура коду! І це все ще відстає від інших :-(

Ось як я це роблю:

  1. Виміряйте довжину фрази - використовуючи рядок з усіма максимум 26 символами (max = 3 * maxwordlen + 2) та 2 пробілами. Перші символи макслена є найчастішими у словнику, тобто e
  2. Використовуйте стратегію бінарного сита для ідентифікації пробілів - зробіть безліч спроб та визначте потенційні пари пробілів. Створіть конкретні тестові рядки, щоб зменшити їх до однієї пари.
  3. Паралельно додайте "створені" тестові рядки, щоб отримати більше інформації про фразу. Поточна стратегія така:

    а. Використовуйте символи в порядку словника за їх частотою.

    б. Ми вже знаємо кількість найчастіших

    c. 1-й рядок тесту = наступні 5 символів. Це дає нам кількість цих символів у фразі.

    г. наступні 3 тестових рядка = наступні 5 символів кожна, що охоплює 20 символів у 4 спробах на додаток до перших 1 знаків. Це дає нам і кількість останніх 5 символів. набори з 0 підрахунками чудово підходять для зменшення розміру словника!

    е. Тепер для попереднього тесту, який мав найменший, ненульовий підрахунок, розділіть рядок на 2 та використовуйте 1 для тестування. Отриманий підрахунок говорить нам і про інший розкол.

    f. Тепер повторіть тести з символами (на основі 0),

       1,6,11,16,21
       2,7,12,17,22
       3,8,13,18,23
       4,9,14,19,24
       Це повинно дати нам 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. Після того, як пробіли будуть визначені, використовуйте обмеження дотепер (стільки тестів, скільки можна було зробити в цих спробах), щоб зменшити розмір словника. Створіть також 3 під словники, по 1 для кожного слова.

  2. Тепер зробіть кілька здогадок для кожного слова та випробуйте його.
    Використовуйте ці результати для зменшення розмірів окремих словників.
    Прикрасьте це і тестовими символами (за довжиною), щоб отримати більше обмежень щодо фрази! Я використав 3 здогадки в остаточній версії - 2 для слова 1, і 1 для слова 2

  3. Це наближає словник до керованого розміру. Виконайте перехресний добуток, застосовуючи всі обмеження, як і раніше, щоб створити словник фрази.

  4. Вирішіть для словника фрази за допомогою ряду здогадок - на цей раз, використовуючи як інформацію про положення, так і відповідність символів.

  5. Цей підхід приводить нас до 11383 спроб:

    Статистика матчів
    ------------------
    Довжина: 1000
    Місця: 6375
    Слово 1: 1996
    Слово 2: 999
    Фраза: 1013
    ВСЬОГО: 11383

    Статистика словників
    слово 0 6517
    слово 1 780 221 92
    слово 2 791 233
    слово 3 772
    фраза 186 20 4 2

    Час рішення: 20 хвилин на моїй програмі macbook pro.

Попереднє повідомлення

Я очистив код і завантажив його на https://github.com/snjyjn/mastermind У процесі роботи я вдосконалив його, і ще є ще одна ідея, щоб спробувати. Є одна основна відмінність від того, що я робив вчора:

Вилучено індивідуальне відгадування символів на основі символів високої частоти у словнику для слів 1 і 2, і замість цього я використовую рядок на основі символу найвищої частоти для цієї позиції.

Зараз статистика виглядає так:

Місця: 6862
Слово 1: 5960
Слово 2: 5907
Слово 3: 2953
ВСЬОГО: 21682

Оригінальна публікація

Вибачте за "відповідь", але я щойно створив обліковий запис і не маю достатньої репутації, щоб додати коментар.

У мене є програма c ++, яка займає близько 6,5 секунд і 24107 спроб відповідності. Це приблизно 1400 рядків c ++. Я не задоволений якістю коду, і очищую його, перш ніж викладати його в інший день. Але в інтересах громади та сприяю обговоренню, я це роблю:

  • Прочитайте словник, отримайте основну інформацію про нього - min / max довжина слова, частота символів тощо.

  • Спочатку визначте пробіли - це 2 половини, перша - це набір запитів, які продовжують розділяти простір (подібний до одного C. Chafouin):

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

Це не зовсім точно, оскільки я використовую мінімальну / максимальну довжину слова, і я використовую підрахунок відповідності на кожному етапі, але ви розумієте. На даний момент ще не вистачає інформації, щоб отримати 2 пробіли, але мені достатньо, щоб зменшити її до невеликої кількості комбінацій. З цих комбінацій я можу зробити декілька конкретних запитів, які звузять його до 1 комбінації.

  • Перше слово - Отримайте підручник, у якому слова потрібної довжини. У підручнику є своя статистика. Зробіть кілька здогадок з найчастішими символами, так що ви отримаєте кількість цих символів у слові. Знову зменшіть словник на основі цієї інфорамки. Створіть слово для здогадок, у якому є найрізноманітніші символи, і використовуйте це. Кожна відповідь викликає скорочення словника до тих пір, поки ми не матимемо точну відповідність, або словник має розмір 1.

  • Друге слово - схоже на Перше слово

  • Третє слово - це найбільше відрізняється від інших 2. Ми не маємо для цього інформації про розмір, але у нас є всі попередні запити (які ми зберегли). Ці запити дозволяють зменшити словник. Логіка полягає у рядках:

 - запит abc повернув кількість матчів 1
 - слова 1 і 2 не мають b або c
 - Зрозуміло, що b або c не можуть бути частиною слова 3

Використовуйте скорочений словник, щоб здогадатися з найрізноманітнішими символами та продовжуйте зменшувати словник до розміру 1 (як у словах 1 та 2).

Статистика виглядає так:

    Космічна знахідка: 7053
    Слово 1 символів: 2502
    Слова 1 слова: 3864
    Слово 2 символи: 2530
    Слово 2 слова: 3874
    Слово 3 символи: 2781
    Слово 3 слова: 1503
    ВСЕ: 24107

Насправді ви можете дізнатися загальну довжину за допомогою одного запиту.
Промінь

Дякую @Ray Я зробив це в кінцевому підсумку, але не вперше пройшов проблему. Я просто не редагував свою первісну публікацію.
Санджай Джайн

6

Перехід - Всього: 29546

Як і деякі інші, з деякими оптимізаціями.

  1. Отримайте загальну довжину шляхом тестування AAAAAAAABBBBBBBBCCCCCCCC...ZZZZZZZZ
  2. Визначте фактичну довжину всіх трьох слів, переміщаючи пробіли з обох кінців.
  3. Фільтруйте кожне слово за літерними підрахунками деяких звичайних літер.
  4. Скоротіть набір кандидатів шляхом тестування рядка та видалення інших кандидатів, які не дають однакових результатів. Повторюйте, поки переможець не знайдеться.

Це не особливо швидко.

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
}

Я не можу скласти цей код. Укладач сказав, що passphasesі allWordsне визначено.
Рей

вам знадобляться ці два файли: gist.github.com/captncraig/a136d0b9819d0ea948e6
captncraig

6

Ява: 58,233

(довідкова програма)

Простий бот, який кожен може бити. Він використовує початкові 26 здогадок для кожної фрази, щоб встановити кількість символів. Потім він усуває всі слова, які містять букви, не знайдені у фразі.

Потім йде масивна петля O (n 3 ) над рештою слів. Спочатку він перевіряє кожну фразу кандидата, щоб побачити, чи це анаграма. Якщо так, то він здогадується, ігноруючи результати, якщо це не ідеальна відповідність. Я бачив, як це використовує між 28-510 здогадами для будь-якої фрази досі.

Це повільно , і це повністю залежить від того, скільки слів можна усунути прямо з початкових 26 здогадок. Більшу частину часу залишає від 1000 до 4000 слів, щоб перетворити цикл. Зараз він працює десь близько 14 годин зі швидкістю ~ 180s / фраза. Я вважаю, що це займе 50 годин, і оновлюватиметься оцінка на той час. Вам, мабуть, варто зробити щось розумніше або більш спритне, ніж це.

(оновлення) Це, нарешті, закінчилось, трохи менше 60-ти здогадок.

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;
    }
}

Опубліковано: вчора. Заголовок включає (ще працює). Змусив мене сміятися, +1
Брайан Боттчер

@insta Це дійсно так. Я думаю, що приблизно ще 6-7 годин повинні це зробити. Оцінка ~ 58k здогадів.
Геобіт

Я не був би терплячим, щоб дозволити цьому тривати так довго
Beta Decay

4

Java: 28340 26185

Хв 15, Макс 35, Час 2,5 с

Так як мій дурний бот , нарешті , закінчить роботу, я хотів би представити що - то трохи швидше. Він запускається всього за кілька секунд, але отримує хороший бал (не зовсім виграшний> <).

Спочатку використовується велика колодка для отримання загальної довжини фрази. Потім двійковий пошук, щоб знайти пробіли, схожі на інші. Роблячи це, він також починає перевіряти букви по черзі (у зворотному порядку), щоб він міг усунути слова, які містять більше будь-якої літери, ніж вся фраза.

Коли він має довжину слова, він використовує крок бінарного скорочення, щоб звузити вибір списків слів. Він вибирає найбільший список і букву, яка становить приблизно половину слів. Він здогадується прокладку довжиною слова цієї букви, щоб визначити, яку половину викинути. Він також використовує результати, щоб позбутися слів у інших списках, у яких занадто багато букви.

Після того, як список складається лише з анаграм, це не працює. У цей момент я просто перебираю їх, поки залишаються лише два (або одне, якщо інші слова невідомі).

Якщо у мене загальна кількість слів - чотири (два відомих та одне з двома варіантами), я пропускаю перевірки скорочення та анаграми та просто здогадуюсь про один із варіантів як повну фразу. Якщо це не працює, то треба бути іншим, але я економлю 50% часу.

Ось приклад, показує, що перша фраза зламається:

                                             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

І звичайно, код:

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();
    }   
}

4

C # - 10649 (хв. 8, макс. 14, сер .: 10,6) час: ~ 12 годин

Ось як це виглядає:

    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

Сольвер

Він використовує перспективний вирішувач. Перш ніж зробити здогад, він оцінює кількість виразних значень, повернених з натурника, з огляду на можливі на даний момент парольні фрази. Здогадка, що максимізує кількість чітких результатів, - це використаний.

На етапі відгадування простору він розглядає лише можливі комбінації "" і ".". Для фази відгадування фрази вона створює весь список можливих на даний момент парольових фраз (саме тому так повільно).

Лист рахується

Підрахунок букв підводиться до знаходження місця. Набір букв було вибрано жадібним пошуком, додаючи по одній букві за один раз та відбираючи вибіркові випадкові тестові фрази, щоб побачити, наскільки ефективний набір.

Код тут: https://github.com/Tyler-Gelvin/MastermindContest

Інтерфейс не вказаний, тому весь вхід жорстко закодований, а тестові одиниці - єдиний інтерфейс. "Основним" тестом є SolverFixture.SolveParallelAll.


Я не можу знайти Mainфункцію у вашому коді. У нього є?
Промінь

Тест на одиницю SolverFixture.SolveSerialAll- це те, що я використовував для отримання результатів тесту, розміщених вище, і Solver.Solveє основою програми. Це одиничний тестовий проект, що не має жодної офіційної точки входу, тому немає mainфункції.
Тайлер Гельвін

3

C # - Разом: 1000, Час виконання: 305 секунд, Сер .: 24, Хв: 14, Макс: 32


Нічого собі Avg <15, це досить добре, добре, я не можу це перемогти, але я зробив удар у нього, так що ось мій підхід. Я розбивав це слово за словом, потім вирішував їх послідовно. Визначивши довжину перших двох слів, а потім зробивши кілька стратегічних здогадок (щоразу фільтруючи за раніше відгаданим словом), я зміг отримати відповідь відносно невеликою кількістю здогадок. За період, що я розробив це, я зміг оптимізувати більшість його частин для ефективної підготовки (за кількістю припущень), але вина в цьому полягає в початковому проектному рішенні логічно вирішувати одне слово за один раз, це змушує мене відкидати частини здогадки та / чи не виконувати здогадки якомога ефективніше, а це, в свою чергу, означає, що я не виграю цього;

І все ж цікавий дизайн (принаймні я так думаю), одне, що слід зазначити із включеним кодом, у певних випадках я можу визначити відповідь, не запускаючи жодного разу здогадку, що повертає -1, якщо це потрібно, просто прокоментуйте рядок коду з позначкою "ДОДАТИ ГОЛОСУ ТУТ (якщо потрібно)" (і додайте до +1 до всіх моїх балів :()


Алгоритм (міркування про мій суддо)

Так що насправді є дві частини цього, перші два слова і останнє слово. Це може не мати сенсу для когось, крім мене, але я намагався додати достатньо коментарів до коду, так що, можливо, це матиме більше сенсу:

NextWord (одне з двох перших двох слів)

{

var lengthOfPossibleWord = Визначити довжину слова (У коді див .: ефективний спосіб пошуку довжини)

Можливості списку = Усі слова такої довжини (lengthOfPossibleWord)

Зробіть здогадку

Можливості = можливості, де (для всіх здогадок) {Кількість символів в одній позиції дорівнює можливому слову

(якщо символів outOfPlace дорівнює 0), де всі символи відрізняються від можливого слова}

}

LastWord (Після вирішення перших двох)

{

Можливості списку = Усі слова, відфільтровані за кількістю символів offPosition у другому слові (У коді див .: helperWords)

Зробіть здогадку

можливості = можливості де (для всіх здогадок) {

Кількість символів в одному положенні дорівнює можливому слову

Сума символів позиції та поза == можливе слово (для всіх здогадок)

Довжина дорівнює довжині можливого слова (сума суми символів у позиції та поза)

(якщо символів outOfPlace дорівнює 0), де всі символи відрізняються від можливого слова

}

}


Код

Примітка. Для цього вам потрібно включити ppcg_mastermind_dict.txt та ppcg_mastermind_passes.txt у запущений каталог (або в VS в тому ж каталозі та встановити "Copy to Output Directory" в true). Я дійсно вибачаюсь за якість коду, але над цим ще багато роботи, але це повинно працювати.

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];
        }
    }
}

2

Пітон - хв: 87, макс .: 108, всього: 96063, час: 4с

Це моя друга посада. Цей метод використовує менше часу, але гірший результат. І його можна запустити за допомогою будь-якого:

  • CPython 2
  • CPython 3
  • Pypy 2 (найшвидший)
  • Pypy 3

Кроки:

  • Знайти перші 2 місця з допомогою припущень подобається . ...., .. ......
  • Порахуйте частоти символів для кожного слова в паролі.
  • Відгадайте для кожної дійсної комбінації після фільтрації за довжиною слова та частотою знаків.

Коштує близько 90 здогадів за кожен пароль.

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)

2

Perl (все ще працює ... станом на хвилину / avg / max 8 / 9,2 / 11, оцініть його на 1500 300 годин загальної тривалості)

Оновлення: Змінено початкові здогадки, щоб дещо прискорити його. Виправлена ​​помилка.

Напевно, це не закінчиться до цього конкурсу, але я, можливо, також розміщу його. Він не визначає окремих довжин слів, тому він повинен перевірити весь словник, який ... займає певний час.

За допомогою перших двох здогадок він визначає загальну довжину, кількість 'е' та кількість різних символів.

Потім він спробує всі комбінації, яких вистачає цієї статистики, а також усі попередні здогадки.

Ця остання (і остання) версія додала mp і в даний час працює в 24-ядерній системі.

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 );
  };
};

1

Java 10.026 (за 2,5 години)

Ось мій оптимізований код, тепер багатопотоковий для підвищення швидкості:

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;
    }

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.