วัตถุดิบสำหรับแบตเตอรี่บงการม้า


48

วัตถุประสงค์

รับรายการวลีรหัสผ่านสามคำถอดรหัสพวกเขาทั้งหมด ทุกครั้งที่คุณเดาคุณจะได้รับเบาะแสในสไตล์ของMastermindโดยแสดงจำนวนอักขระที่ตรงกับรหัสผ่านและจำนวนที่อยู่ในตำแหน่งที่ถูกต้อง วัตถุประสงค์คือเพื่อลดจำนวนการเดาทั้งหมดในทุกกรณีทดสอบ

วลีรหัสผ่าน

จากรายการคำเริ่มต้นของระบบของฉันฉันสุ่มเลือก 10,000 คำที่แตกต่างกันเพื่อสร้างพจนานุกรมสำหรับความท้าทายนี้ คำทั้งหมดประกอบด้วยa-zเพียง พจนานุกรมนี้อยู่ที่นี่ ( ดิบ )

จากพจนานุกรมนี้ฉันสร้างวลีรหัสผ่าน 1,000 รายการซึ่งประกอบด้วยคำที่คั่นด้วยช่องว่างสามคำแต่ละคำ ( apple jacks feverตัวอย่างเช่น) แต่ละคำสามารถนำมาใช้ซ้ำได้ในแต่ละวลีรหัสผ่าน ( hungry hungry hippos) คุณสามารถค้นหารายการวลีรหัสผ่านที่นี่ ( ดิบ ) โดยมีหนึ่งรายการต่อบรรทัด

โปรแกรมของคุณสามารถใช้ / วิเคราะห์ไฟล์พจนานุกรมได้ตามต้องการ คุณไม่สามารถวิเคราะห์ข้อความรหัสผ่านเพื่อเพิ่มประสิทธิภาพสำหรับรายการเฉพาะนี้ อัลกอริทึมของคุณยังควรได้รับรายชื่อวลีที่แตกต่างกัน

คาดเดา

ในการคาดเดาคุณส่งสตริงไปยังตัวตรวจสอบ มันควรจะกลับมาเท่านั้น :

  • จำนวนอักขระในสตริงของคุณยังอยู่ในวลีรหัสผ่าน ( ไม่อยู่ในตำแหน่งที่ถูกต้อง)
  • จำนวนอักขระในตำแหน่งที่ถูกต้อง

หากสตริงของคุณเป็นคู่ที่สมบูรณ์แบบมันอาจส่งออกบางสิ่งที่ระบุว่า (ฉันใช้-1สำหรับค่าแรก)

ตัวอย่างเช่นถ้าเป็นรหัสผ่านthe big catและคุณเดาตรวจสอบควรกลับtiger baby mauling 7,17 ตัวอักษร ( 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};
}

เกณฑ์การให้คะแนน

คะแนนของคุณเป็นเพียงจำนวนการเดาที่คุณส่งให้กับผู้ตรวจสอบ (นับคะแนนสุดท้ายแก้ไขให้ถูกต้อง) สำหรับวลีทดสอบทั้งหมด คะแนนต่ำสุดชนะ

คุณต้องถอดรหัสวลีทั้งหมดในรายการ หากโปรแกรมของคุณล้มเหลวโปรแกรมใดโปรแกรมหนึ่งจะไม่สามารถใช้งานได้

โปรแกรมของคุณจะต้องกำหนดไว้ล่วงหน้า หากทำงานสองครั้งในวลีรหัสผ่านชุดเดียวกันก็ควรส่งคืนผลลัพธ์เดียวกัน

ในกรณีที่เสมอกันฉันจะรันรายการที่ผูกไว้ในคอมพิวเตอร์ของฉันสี่ครั้งต่อครั้งและเวลาเฉลี่ยต่ำสุดเพื่อแก้ปัญหาผู้ชนะ 1,000 ราย คอมพิวเตอร์ของฉันใช้งาน Ubuntu 14.04 โดยใช้i7-3770Kและ 16GB ของ RAM บางประเภทในกรณีที่สร้างความแตกต่างให้กับโปรแกรมของคุณ ด้วยเหตุผลนั้นและเพื่อความสะดวกในการทดสอบคำตอบของคุณควรเป็นภาษาที่มีผู้แปล / ล่ามที่สามารถดาวน์โหลดได้จากเว็บโดยไม่เสียค่าใช้จ่าย (ไม่รวมการทดลองฟรี) และไม่จำเป็นต้องลงทะเบียน / ลงทะเบียน

ดัดแปลงมาจากชื่อXKCD


1
ชื่อเรื่องจากxkcd.com/936
NinjaBearMonkey

ฉันสามารถใส่อักขระอื่นนอกเหนือจาก a. z และเว้นวรรคในสตริงเพื่อส่งหรือไม่
เรย์

@ เรย์ฉันไม่สามารถนึกถึงเหตุผลที่ทำไมไม่ได้ในขณะนี้ แต่ฉันไม่แน่ใจว่าสิ่งที่จะได้รับคุณ ไปหามันฉันอยากรู้
Geobits

3
มนุษย์สามารถยอมแพ้ได้หรือไม่? ฉันจะเริ่มต้น: "คุ้มค่ากับการยิง '
AndoDaan

2
@AndoDaan สำหรับวลีแรก 9 0. อาจใช้เวลาสักครู่: P
Geobits

คำตอบ:


13

เวลาสกาล่า 9146 (ขั้นต่ำ 7, สูงสุด 15, เฉลี่ย 9.15): 2,000 วินาที

เช่นเดียวกับหลายรายการที่ฉันเริ่มต้นด้วยการรับความยาวทั้งหมดจากนั้นค้นหาช่องว่างรับข้อมูลเพิ่มขึ้นอีกเล็กน้อยลดขนาดลงเหลือผู้สมัครที่เหลือ

แรงบันดาลใจจากการ์ตูน 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 บิตต่อข้อความค้นหา (อย่างน้อยในช่วงแรก ๆ ก่อนที่ข้อมูลจะซ้ำซ้อน) เมื่อทราบระยะห่างฉันใช้สตริงสุ่มของตัวอักษรที่พบบ่อยที่สุดทางด้านซ้ายเลือกเพื่อไม่ให้ขัดแย้งกับด้านขวา

รวมผู้สมัครที่เหลือ

เกมนี้เป็นเกมที่คำนวณความเร็วและลดประสิทธิภาพของการค้นหาและการแจงนับผู้สมัครที่เหลือสามารถใช้เวลานานมากโดยไม่มีข้อมูลที่มีโครงสร้างเช่นตัวละครที่เฉพาะเจาะจง ฉันเพิ่มประสิทธิภาพส่วนนี้โดยการรวบรวมข้อมูลส่วนใหญ่ที่ไม่แปรผันกับการเรียงลำดับคำซึ่งทำให้ฉันคำนวณจำนวนชุดอักขระสำหรับแต่ละคำล่วงหน้าและเปรียบเทียบกับจำนวนที่เรียนรู้จากแบบสอบถาม ฉันแพ็คการนับเหล่านี้เป็นจำนวนเต็มแบบยาวโดยใช้ตัวเปรียบเทียบความเท่าเทียมกันของเครื่องจักรและตัวบวกเพื่อทดสอบตัวละครทั้งหมดของฉันนับเป็น parralel นี่เป็นชัยชนะครั้งใหญ่ ฉันสามารถบรรจุได้ถึง 9 จำนวนใน Long แต่ฉันพบว่าการรวบรวมข้อมูลเพิ่มเติมไม่คุ้มค่าและตัดสินในวันที่ 6 ถึง 7

หลังจากทราบผู้สมัครที่เหลืออยู่ถ้าชุดมีขนาดเล็กพอสมควรฉันเลือกหนึ่งชุดพร้อมบันทึกที่คาดหวังต่ำสุดของผู้สมัครที่เหลือ หากชุดมีขนาดใหญ่พอที่จะใช้เวลานี้ฉันเลือกชุดตัวอย่างขนาดเล็ก

ขอบคุณทุกคน นี่เป็นเกมที่สนุกและชวนให้ฉันลงทะเบียนกับเว็บไซต์

อัปเดต:ทำความสะอาดโค้ดเพื่อความง่ายและอ่านง่ายโดยมีการปรับแต่งเล็กน้อยสำหรับอัลกอริทึมทำให้คะแนนดีขึ้น
คะแนนเดิม: 9447 (ต่ำสุด 7, สูงสุด 13, เฉลี่ย 9.45) เวลา: 1876 วินาที

รหัสใหม่คือ Scala 278 บรรทัดด้านล่าง

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
ยินดีต้อนรับสู่เว็บไซต์และขอแสดงความยินดี! คุณไม่ได้ทำให้ความโปรดปรานลดลงมากนัก แต่คุณทำได้ มีจุดอินเทอร์เน็ตในจินตนาการ!
Geobits

เรียบง่าย

ทางออกที่ยอดเยี่ยม! เป็นเครื่องหมายเดียวใต้เครื่องหมาย 10,000!
Sanjay Jain

15

C - ทั้งหมด: 37171, นาที: 24, สูงสุด: 55, เวลา: ประมาณ 10 วินาที

ฉันยืมความคิดของ Rayเพื่อหาความยาวของแต่ละคำด้วยการคาดเดาด้วยช่องว่างเว้นเสียแต่ว่าฉันจะทำการค้นหาแบบไบนารี่แทนที่จะเป็นแบบเชิงเส้นซึ่งช่วยฉันเดาได้มาก

เมื่อฉันกำหนดความยาวของคำฉันเดาด้วยคำแรกที่ตรงกับความยาวและฉันบันทึกจำนวนตำแหน่งที่ถูกต้อง จากนั้นฉันเลือกคำแรกจากชุดคำทั้งหมดที่ใช้จำนวนตำแหน่งเท่ากันโดยเดาคำแรกของฉันว่าเป็นคำลึกลับ สำหรับการเดาครั้งที่สามของฉันฉันเลือกคำแรกจากชุดของคำทั้งหมดที่ใช้จำนวนตำแหน่งเดียวกันกับการเดาครั้งแรกของฉันเป็นคำลึกลับและจำนวนตำแหน่งเดียวกันกับการเดาครั้งที่สองของฉันกับคำลึกลับ ฯลฯ

ใช้วิธีนี้ฉันสามารถเดาแต่ละคำทีละครั้งในประมาณ 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
ฉันชอบอันนี้ขั้นตอนต่อไปที่ใช้งานง่ายอาจทำให้แน่ใจได้ว่ารายการคำศัพท์ที่คุณใช้สำหรับการเดานั้นเรียงลำดับตามวิธีที่มีประสิทธิภาพ อย่างน้อยต้องแน่ใจว่าคุณมีคำเริ่มต้นที่ดีสำหรับตัวอักษรแต่ละตัว
Dennis Jaheruddin

นั่นเป็นความคิดที่ดี. ขอบคุณสำหรับความคิดเห็น.
Orby

15

Python 3.4 - ขั้นต่ำ: 21, สูงสุด: 29, ทั้งหมด: 25146, เวลา: 20 นาที

ขั้นต่ำ: 30, สูงสุด: 235, ทั้งหมด: 41636, เวลา: 4 นาที

ปรับปรุง:

  1. ใช้การค้นหาแบบไบนารีเพื่อค้นหาพื้นที่ ความคิดที่จะยืมมาจากคำตอบของ Orby จุดหนึ่งที่ฉันปรับให้เหมาะสมคือถ้าคุณพบช่องว่าง 2 อันในช่วงเมื่อค้นหาช่องว่างแรกคุณสามารถ จำกัด ช่วงการค้นหาของช่องว่างที่สอง
  2. บันทึกการทายผิดพร้อมกับผลลัพธ์ เปรียบเทียบกับพวกเขาในการเดาดังต่อไปนี้ สิ่งนี้สามารถประหยัดได้มาก
  3. ลดจำนวนตัวอักษรนับเป็น 12 ขอบคุณที่ปรับปรุง # 2

ป้อนคำอธิบายรูปภาพที่นี่


mehod นี้ไม่ได้ใช้แบบสุ่มดังนั้นคะแนนจะไม่เปลี่ยนแปลง

ก่อนอื่นให้ใช้การทายดังต่อไปนี้เพื่อค้นหาช่องว่างที่หนึ่งและสองในคำตอบ

. ......................
.. .....................
... ....................
.... ...................
# 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
ฉันมีช่วงเวลาที่ยากมากที่จะได้ตะโกนสิ่งนี้ งานที่ดี.
Orby

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 / เฉลี่ย!) ใหม่

  • การตรวจสอบตัวละครที่รู้จักกันในตอนนี้จะแน่นขึ้น (ตอนนี้ ABABAB * แทนที่จะเป็น -AAAA *)
  • เมื่อไม่มีตัวละครที่รู้จักมีสองนิรนามจะถูกนับในการเดาเดียว
  • การทายผิดจะถูกเก็บไว้และใช้ในการตรวจสอบการแข่งขัน
  • มีการปรับเปลี่ยนอย่างต่อเนื่องพร้อมตรรกะใหม่เข้าที่

โพสต์ต้นฉบับ

ฉันตัดสินใจที่จะมุ่งเน้นไปที่ปริมาณการคาดเดาแทนการแสดงอย่างสมบูรณ์ (ตามกฎ) สิ่งนี้ส่งผลให้โปรแกรมสมาร์ทช้ามาก

แทนที่จะขโมยจากโปรแกรมที่รู้จักกันฉันตัดสินใจที่จะเขียนทุกอย่างตั้งแต่เริ่มต้น แต่กลับกลายเป็นว่าแนวคิดบางส่วน / ส่วนใหญ่เหมือนกัน

ขั้นตอนวิธี

นี่คือวิธีการทำงานของฉัน:

  1. ทำแบบสอบถามเดียวซึ่งส่งผลให้จำนวนของอีและตัวละครรวม
  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

Rev 1. มีให้ที่http://pastebin.com/PSXU2bga

Rev 2. มีให้ที่http://pastebin.com/gRJjpbbu

การแก้ไขครั้งที่สองของฉัน ฉันหวังว่าจะทำลายสิ่งกีดขวาง 11K เพื่อให้ได้รางวัล แต่ฉันหมดเวลาเพื่อเพิ่มประสิทธิภาพสัตว์ร้ายตัวนี้

มันทำงานบนหลักการที่แยกต่างหากอย่างสิ้นเชิงจากสองเวอร์ชันก่อนหน้านี้ (และใช้เวลาในการรันประมาณ 3,500 ครั้ง) หลักการทั่วไปคือการใช้พื้นที่และแม้แต่ / ตัวอักษรแปลก ๆ sieving เพื่อลดรายชื่อผู้สมัครให้มีขนาดที่สามารถจัดการได้ (ปกติระหว่าง 2-8 ล้าน) แล้วดำเนินการค้นหาซ้ำกับอำนาจการเลือกปฏิบัติสูงสุด (เช่นการกระจายผลผลิตมีเอนโทรปีสูงสุด)

ไม่ใช่ความเร็ว แต่หน่วยความจำคือข้อ จำกัด หลัก Java VM ของฉันจะไม่ให้ฉันจองฮีปขนาดใหญ่กว่า 1,200 MB ด้วยเหตุผลบางอย่างที่คลุมเครือ (อาจเป็น Windows 7) และฉันปรับพารามิเตอร์เพื่อให้โซลูชันที่ดีที่สุดเท่าที่จะทำได้ มันทำให้ฉันหงุดหงิดว่าการทำงานที่ถูกต้องกับพารามิเตอร์ที่เหมาะสมจะทำให้ 11K แตกสลายโดยไม่มีความหมายเพิ่มขึ้นในเวลาดำเนินการ ฉันต้องการคอมพิวเตอร์เครื่องใหม่ : P

สิ่งที่ทำให้ฉันรู้สึกแย่มากคือ 982 ของข้อความค้นหาในการใช้งานนี้เป็นข้อความค้นหา "การตรวจสอบ" ที่ไร้ประโยชน์ พวกเขาไม่มีจุดประสงค์อื่นนอกจากเพื่อให้เป็นไปตามกฎที่ว่า Oracle ต้องส่งคืนค่าพิเศษ "ที่คุณได้รับ" ในบางจุดแม้ว่าในการนำไปใช้ของฉันคำตอบที่ถูกต้องได้รับการอนุมานด้วยความมั่นใจก่อนการสอบถามนี้ 98.2% การส่ง sub-11K อื่น ๆ ส่วนใหญ่นั้นอาศัยเทคนิคการกรองที่ใช้สตริงตัวเลือกเป็นสตริงการสืบค้นและด้วยเหตุนี้จึงไม่ได้รับโทษแบบเดียวกัน

ด้วยเหตุนี้แม้ว่าจำนวนการค้นหาอย่างเป็นทางการของฉันคือ 11,077 (ย่อมาจากผู้นำโดยมีรหัสของพวกเขาพิสูจน์ได้ว่าเป็นไปตามข้อกำหนดจริง ฯลฯ ) แต่ฉันก็กล้าระบุว่ารหัสของฉันทำให้การสืบค้นมีประสิทธิภาพ 10,095 หมาย จำเป็นจริง ๆ เพื่อกำหนดวลีรหัสผ่านทั้งหมดด้วยความมั่นใจ 100% ฉันไม่แน่ใจว่าการใช้งานอื่น ๆ จะตรงกับนั้นดังนั้นฉันจะคิดว่ามันเป็นชัยชนะของฉัน ;)


ZPC เป็นสิ่งที่ดีรายการอื่น ๆ ก็ใช้มันเช่นกัน .ผมคิดว่าที่พบมากที่สุดคือ
Geobits

รหัสปัจจุบันไม่รวมแบบสอบถาม "ตรวจสอบ" ฉันจะเพิ่มอีกหนึ่งตอนนี้
COTO

ฉันได้รับการปรับปรุงเพื่อ rev 1 ซึ่งรวมถึงแบบสอบถามการตรวจสอบ ไม่น่าแปลกใจที่จำนวนคำค้นหามีค่ามากกว่า 1,000 รุ่นก่อนหน้าอย่างแน่นอน
COTO

1
นี่เป็นสิ่งที่ดีมาก Java ของคุณเป็น Java-y มันเจ็บ ฉันไม่เคยเห็นรหัสเช่นนี้ในเว็บไซต์นี้: 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 วินาที

ฉันไปด้วยความเร็วแทนที่จะคาดเดาจำนวนต่ำสุดแม้ว่าฉันจะคิดว่าจำนวนการเดาของฉันยังต่ำกว่าตัวอย่างเช่นการโจมตีพจนานุกรมตรงไปตรงมา ฉันไม่ได้คำนวณจำนวนตัวอักษรในรหัสผ่าน แต่ในสถานที่ที่ไม่ถูกต้องเนื่องจากวิธีการของฉันไม่ได้ใช้ แต่ถ้าคุณรู้สึกว่านี่ทำให้ฉันได้เปรียบอย่างไม่เป็นธรรมฉันจะใช้มัน ฉันเพียงแค่เริ่มต้นด้วยสตริงการเดาที่ว่างเปล่าและเพิ่มคำต่อท้ายอักขระเดียวซึ่งเพิ่มขึ้นในรายการตัวอักษรของฉันการตรวจสอบผลลัพธ์ของ 'การตรวจสอบ' เพื่อดูว่าจำนวนอักขระที่ถูกต้องเท่ากับความยาวของการเดา ตัวอย่างเช่นหากรหัสผ่านไม่ดีฉันจะเดาว่า:

A, B

a

เอบีซีดี

ฉันพยายามเรียงลำดับตัวอักษรด้วยความถี่ตัวอักษรภาษาอังกฤษซึ่งลดจำนวนการเดาได้ประมาณ 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

@Geobits แก้ไขข้อบกพร่องฉันมี -1 ในคำสั่ง if ใน nextPass () และ +1 ในขณะที่ลูปใน main () ทั้งคู่จากการทดสอบซ้ำก่อนหน้านี้ ตอนนี้พิมพ์รหัสผ่านแต่ละครั้งเพียงครั้งเดียวหากคุณเก็บบรรทัดที่ 65 ไว้
โต

7

C ++ - 11383 10989 ตรงกัน!

ปรับปรุง

แก้ไขการรั่วไหลของหน่วยความจำและลบความพยายามอีก 1 ครั้งเพื่อลดขนาดพจนานุกรมคำแต่ละคำ ใช้เวลาประมาณ 50 นาทีสำหรับ mac pro รหัสที่อัปเดตอยู่ใน GitHub


ฉันเปลี่ยนไปใช้กลยุทธ์การจับคู่วลีและทำรหัสใหม่และอัปเดตบน github https://github.com/snjyjn/mastermind

ด้วยการจับคู่แบบอิงวลีเราจะลดลงเหลือ 11383 ครั้ง มันมีราคาแพงในแง่ของการคำนวณ! ฉันยังไม่ชอบโครงสร้างรหัส! และยังคงเป็นวิธีที่อยู่เบื้องหลังคนอื่น ๆ :-(

นี่คือวิธีที่ฉันทำ:

  1. วัดความยาวของวลี - ใช้สตริงที่มีอักขระสูงสุด 26 ตัว (สูงสุด = 3 * maxwordlen + 2) และเว้นวรรค 2 ตัว อักขระ maxlen ตัวแรกเป็นอักขระที่พบบ่อยที่สุดในพจนานุกรมเช่น e
  2. ใช้กลยุทธ์ประเภทไบนารีตะแกรงเพื่อระบุช่องว่าง - ทำตามจำนวนครั้งที่กำหนดและระบุคู่ของช่องว่างที่อาจเกิดขึ้น สร้างสตริงการทดสอบที่เฉพาะเจาะจงเพื่อลดคู่เดียว
  3. ในแบบคู่ขนานผนวกสายการทดสอบ 'crafted' เพื่อรับข้อมูลเพิ่มเติมเกี่ยวกับวลี กลยุทธ์ปัจจุบันมีดังนี้:

    ใช้ตัวอักษรตามลำดับความถี่ในพจนานุกรม

    ข เรารู้แล้วว่ามีการนับบ่อยที่สุด

    ค 1st Test string = 5 ตัวอักษรถัดไป นี่ทำให้เรานับจำนวนตัวอักษรเหล่านี้ในวลี

    d สตริงการทดสอบ 3 รายการถัดไป = 5 ตัวอักษรถัดไปแต่ละตัวครอบคลุมทั้งหมด 20 อักขระใน 4 ครั้งนอกเหนือจาก 1 อักขระแรก สิ่งนี้ทำให้เรานับสำหรับตัวละคร 5 ตัวสุดท้ายนี้เช่นกัน ชุดที่มีจำนวน 0 เหมาะสำหรับการลดขนาดพจนานุกรม!

    อี ตอนนี้สำหรับการทดสอบก่อนหน้านี้ที่มีจำนวนน้อยที่สุดไม่ใช่ศูนย์ให้แบ่งสตริงออกเป็น 2 และใช้ 1 สำหรับการทดสอบ จำนวนผลลัพธ์ที่ได้บอกเราเกี่ยวกับการแยกอื่นเช่นกัน

    ฉ ตอนนี้ทำซ้ำการทดสอบด้วยตัวละคร (ตาม 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 พจนานุกรมสำหรับแต่ละคำ

  2. ทีนี้ลองเดาคำศัพท์แต่ละคำแล้วทดสอบดู
    ใช้ผลลัพธ์เหล่านี้เพื่อลดขนาดพจนานุกรมแต่ละรายการ
    ตกแต่งด้วยตัวละครทดสอบเช่นกัน (หลังจากความยาว) เพื่อรับข้อ จำกัด เพิ่มเติมเกี่ยวกับวลี! ฉันใช้การทาย 3 ครั้งในรุ่นสุดท้าย - 2 สำหรับคำ 1 และ 1 สำหรับคำ 2

  3. สิ่งนี้ทำให้พจนานุกรมมีขนาดที่สามารถจัดการได้ ดำเนินการข้ามผลิตภัณฑ์โดยใช้ข้อ จำกัด ทั้งหมดเหมือนก่อนเพื่อสร้างพจนานุกรมวลี

  4. แก้ปัญหาพจนานุกรมวลีผ่านชุดการเดา - คราวนี้ใช้ทั้งข้อมูลตำแหน่งและการจับคู่อักขระ

  5. วิธีการนี้นำเราสู่ความพยายามต่ำกว่า 11383 ครั้ง:

    สถิติการจับคู่
    ------------------
    ความยาว: 1,000
    ช่องว่าง: 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 ข้อให้ลองใช้ มีหนึ่งความแตกต่างที่สำคัญหนึ่งจากสิ่งที่ฉันทำเมื่อวานนี้:

ลบบุคคลที่คาดเดาสำหรับอักขระตามอักขระความถี่สูงในพจนานุกรมสำหรับคำที่ 1 & 2 และฉันใช้สตริงที่อิงกับอักขระความถี่สูงสุดสำหรับตำแหน่งนั้น

สถิติตอนนี้ดูเหมือนว่า:

ช่องว่าง: 6862
คำ 1: 5960
คำ 2: 5907
Word 3: 2953
ทั้งสิ้น: 21682

โพสต์ต้นฉบับ

ขอโทษสำหรับ 'คำตอบ' แต่ฉันเพิ่งสร้างบัญชีและไม่มีชื่อเสียงเพียงพอที่จะเพิ่มความคิดเห็น

ฉันมีโปรแกรม c ++ ซึ่งใช้เวลาประมาณ 6.5 วินาทีและการจับคู่ 24107 ครั้ง มันคือประมาณ 1,400 บรรทัดของ c ++ ฉันไม่มีความสุขเกี่ยวกับคุณภาพของรหัสและจะทำความสะอาดก่อนที่จะวางในอีกวัน แต่เพื่อประโยชน์ของชุมชนและมีส่วนร่วมในการอภิปรายนี่คือสิ่งที่ฉันทำ:

  • อ่านพจนานุกรมรับข้อมูลพื้นฐานเกี่ยวกับเรื่องนี้ - ความยาวคำต่ำสุด / สูงสุด, ความถี่ของตัวอักษร, ฯลฯ

  • First ระบุช่องว่าง - นี่มี 2 แบ่งเท่า ๆ กันส่วนแรกคือชุดของแบบสอบถามที่ยังคงแบ่งพาร์ติชันพื้นที่ (คล้ายกับหนึ่ง C. Chafouin):

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

สิ่งนี้ไม่ถูกต้องอย่างแน่นอนเนื่องจากฉันใช้ความยาวคำต่ำสุด / สูงสุดและฉันใช้การจับคู่การนับในแต่ละด่าน แต่คุณได้รับความคิด ณ จุดนี้ยังมีข้อมูลไม่เพียงพอที่จะรับ 2 ช่องว่าง แต่ฉันมีเพียงพอที่จะลดลงเป็นชุดค่าผสมจำนวนน้อย จากชุดค่าผสมเหล่านั้นฉันสามารถสร้างแบบสอบถามที่เฉพาะเจาะจงสองสามข้อซึ่งจะทำให้แคบลงเป็นชุดค่าผสม 1 ชุด

  • First Word - รับ Subdictionary ซึ่งมีคำที่เหมาะสม Subictionary มีสถิติของตัวเอง อย่าเดาตัวละครที่มีบ่อยที่สุดดังนั้นคุณจะได้รับจำนวนอักขระเหล่านี้ในคำนั้น ลดพจนานุกรมอีกครั้งโดยพิจารณาจากข้อมูลนี้ สร้างคำที่เดาซึ่งมีอักขระแตกต่างกันมากที่สุดและใช้คำนั้น การตอบสนองแต่ละครั้งทำให้เกิดการลดลงของพจนานุกรมจนกว่าเราจะมีการจับคู่ที่ตรงกันหรือพจนานุกรมมีขนาด 1

  • คำที่สอง - คล้ายกับคำแรก

  • คำที่สาม - นี่แตกต่างจากที่อื่นที่สุด 2 เราไม่มีข้อมูลขนาดสำหรับสิ่งนี้ แต่เรามีข้อความค้นหาก่อนหน้าทั้งหมด (ซึ่งเราเก็บไว้) ข้อความค้นหาเหล่านี้ช่วยให้คุณลดพจนานุกรม ตรรกะอยู่ในบรรทัดของ:

 - เคียวรี abc ส่งคืนการจับคู่ของ 1
 - คำที่ 1 และ 2 ไม่มี b หรือ c
 - เป็นที่ชัดเจนว่า b หรือ c ไม่สามารถเป็นส่วนหนึ่งของคำ 3 ได้

ใช้พจนานุกรมที่ลดลงเพื่อคาดเดาด้วยตัวอักษรที่หลากหลายที่สุดและลดพจนานุกรมต่อไปจนถึงขนาด 1 (ตามคำ 1 และ 2)

สถิติมีลักษณะดังนี้:

    การค้นพบอวกาศ: 7053
    ตัวอักษร Word 1: 2502
    คำ 1 คำ: 3864
    ตัวอักษร Word 2: 2530
    คำ 2 คำ: 3874
    ตัวอักษร Word 3: 2781
    คำ 3 คำ: 1503
    ทั้งสิ้น: 24107

ที่จริงคุณสามารถทราบความยาวทั้งหมดด้วยแบบสอบถามเดียว
เรย์

ขอบคุณ @ เรย์ ในที่สุดฉันก็ทำเช่นนั้น แต่ไม่ใช่ในการผ่านครั้งแรกของปัญหา ฉันไม่ได้แก้ไขโพสต์ดั้งเดิมของฉัน
Sanjay Jain

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

Java: 58,233

(โปรแกรมอ้างอิง)

บอง่าย ๆ สำหรับทุกคนที่จะต้องเอาชนะ มันใช้การเดา 26 ครั้งแรกสำหรับแต่ละวลีเพื่อสร้างการนับจำนวนตัวอักษร จากนั้นจะกำจัดคำทั้งหมดที่มีตัวอักษรที่ไม่พบในวลี

จากนั้นมีวง O (n 3 ) ขนาดใหญ่มาทับคำที่เหลืออยู่ ก่อนอื่นตรวจสอบวลีของผู้สมัครแต่ละคนเพื่อดูว่าเป็นแอนนาแกรมหรือไม่ ถ้าเป็นเช่นนั้นก็จะเดาได้โดยไม่สนใจผลลัพธ์เว้นแต่จะเป็นการจับคู่ที่สมบูรณ์แบบ ฉันเคยเห็นมันใช้ระหว่าง 28-510 เดาสำหรับวลีที่กำหนดใด ๆ จนถึงขณะนี้

นี่คือช้าและมันทั้งหมดขึ้นอยู่กับวิธีการหลายคำที่สามารถตัดตรงจากเริ่มต้น 26 คาดเดา เวลาส่วนใหญ่จะอยู่ระหว่าง 1,000-4,000 คำเพื่อวนซ้ำ ตอนนี้ทำงานประมาณ 14 ชั่วโมงด้วยอัตรา ~ 180s / วลี ฉันคาดว่าจะใช้เวลา 50 ชั่วโมงจึงจะเสร็จสมบูรณ์และจะอัปเดตคะแนนในเวลานั้น คุณควรอาจจะทำบางสิ่งบางอย่างชาญฉลาดหรือ thready มากไปกว่านี้

(อัปเดต) ในที่สุดก็เสร็จสิ้นโดยคาดเดาได้ไม่ถึง 60k

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
Bryan Boettcher

@insta มันคือเรื่องจริง ฉันคิดว่าอีก 6-7 ชั่วโมงน่าจะทำ ประมาณ ~ 58k คาดเดา
Geobits

ฉันจะไม่อดทนพอที่จะปล่อยให้เรื่องนี้ดำเนินไปเป็นเวลานาน
Beta Decay

4

Java: 28,340 26,185

ขั้นต่ำ 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

Solver

มันใช้ตัวแก้การคาดการณ์ล่วงหน้า ก่อนที่จะทำการคาดเดามันจะประมาณจำนวนค่าที่แตกต่างที่ส่งคืนจากผู้บงการที่ได้รับข้อความรหัสผ่านที่เป็นไปได้ในปัจจุบัน การเดาที่เพิ่มจำนวนผลลัพธ์ที่แตกต่างให้มากที่สุดคือสิ่งที่ใช้

สำหรับขั้นตอนการคาดเดาพื้นที่นั้นจะพิจารณาเพียงชุดค่าผสมที่เป็นไปได้ของ "" และ "เท่านั้น" สำหรับขั้นตอนการคาดเดาวลีมันจะสร้างรายการวลีรหัสผ่านที่เป็นไปได้ทั้งหมดในปัจจุบัน (ซึ่งเป็นสาเหตุที่ทำให้ข้อความช้า)

การนับจดหมาย

การนับจดหมายจะถูกโยนเข้าไปพร้อมกับการค้นหาพื้นที่ ชุดจดหมายถูกเลือกโดยการค้นหาโลภเพิ่มจดหมายทีละตัวและสุ่มวลีทดสอบสุ่มเพื่อดูว่าชุดนั้นมีประสิทธิภาพเพียงใด

รหัสอยู่ที่นี่: https://github.com/Tyler-Gelvin/MastermindContest

ไม่มีการระบุอินเตอร์เฟสดังนั้นอินพุตทั้งหมดจึงเป็นฮาร์ดโค้ดและการทดสอบหน่วยเป็นอินเทอร์เฟซเดียว การทดสอบ "หลัก" คือ SolverFixture.SolveParallelAll


ฉันไม่พบMainฟังก์ชันในรหัสของคุณ มันมีหรือไม่
เรย์

การทดสอบหน่วยSolverFixture.SolveSerialAllเป็นสิ่งที่ฉันใช้เพื่อให้ได้ผลการทดสอบที่โพสต์ไว้ด้านบนและSolver.Solveเป็นแกนหลักของโปรแกรม มันเป็นโครงการทดสอบหน่วยที่ไม่มีจุดเข้าอย่างเป็นทางการเดียวดังนั้นจึงไม่มีmainฟังก์ชั่น
Tyler Gelvin

3

C # - ทั้งหมด: 1,000, เวลารัน: 305 วินาที, ราคาเฉลี่ย: 24, ต่ำสุด: 14, สูงสุด: 32


Wow เฉลี่ยของ <15 นั้นค่อนข้างดีฉันไม่สามารถเอาชนะได้ แต่ฉันได้แทงมันดังนั้นนี่คือวิธีการของฉัน ฉันทำลายมันทีละคำจากนั้นแก้ไขพวกเขาอย่างต่อเนื่อง โดยการกำหนดความยาวของสองคำแรกจากนั้นทำการเดากลยุทธ์สองสามครั้ง (แต่ละครั้งที่กรองโดยคำที่เดาไว้ก่อนหน้านี้) ฉันสามารถได้คำตอบด้วยการเดาที่ค่อนข้างน้อย ในช่วงเวลาที่ฉันพัฒนาสิ่งนี้ฉันสามารถเพิ่มประสิทธิภาพส่วนใหญ่ของมันให้ preform ได้อย่างมีประสิทธิภาพ (ในการเดาตัวเลข) แต่ความผิดที่เกิดขึ้นกับการตัดสินใจออกแบบเบื้องต้นเพื่อแก้ปัญหาครั้งละหนึ่งเหตุผลทำให้ฉันละทิ้งส่วนต่างๆของ คาดเดาและ / หรือไม่เรียกใช้การเดาอย่างมีประสิทธิภาพมากที่สุดซึ่งหมายความว่าฉันไม่ชนะรางวัลนี้ (

ยังมีการออกแบบที่น่าสนใจ (อย่างน้อยฉันก็คิดอย่างนั้น) สิ่งหนึ่งที่ควรทราบด้วยรหัสที่รวมอยู่ในบางกรณีฉันสามารถกำหนดคำตอบได้โดยไม่ต้องคาดเดาว่าจะได้รับผลตอบแทน -1 หากจำเป็นต้องใส่รหัสบรรทัด "เพิ่มที่นี่ (ถ้าจำเป็น)" (และเพิ่ม +1 ของคะแนนทั้งหมดของฉันได้ :()


อัลกอริทึม (การคิดรหัส Sudo ของฉัน)

จริงๆแล้วมันมีสองส่วนสำหรับคำนี้สองคำแรกและคำสุดท้าย สิ่งนี้อาจไม่สมเหตุสมผลกับใครเลย แต่ฉัน แต่ฉันพยายามเพิ่มความคิดเห็นลงในรหัสให้มากพอดังนั้นอาจจะมีเหตุผลมากกว่านี้:

คำต่อไป (คำใดคำหนึ่งจากสองคำแรก)

{

var lengthOfPossibleWord = กำหนดความยาวของคำ (ในรหัสดู: วิธีที่มีประสิทธิภาพในการค้นหาความยาว)

รายการความเป็นไปได้ = คำทั้งหมดของความยาวนั้น (lengthOfPossibleWord)

คาดเดา

ความเป็นไปได้ = ความเป็นไปได้ที่ (สำหรับการเดาทั้งหมด) {จำนวนอักขระในตำแหน่งเดียวกันเท่ากับคำที่เป็นไปได้

(ถ้าอักขระ outOfPlace มีค่าเท่ากับ 0) ดังนั้นที่อักขระทั้งหมดแตกต่างจากคำที่เป็นไปได้}

}

LastWord (หลังจากแก้ไขสองคำแรก)

{

รายการความเป็นไปได้ = คำทั้งหมดที่กรองตามจำนวนของอักขระตำแหน่งในคำที่สอง (ในรหัสดู: helperWords)

คาดเดา

ความเป็นไปได้ = ความเป็นไปได้ที่ (สำหรับการเดาทั้งหมด) {

จำนวนอักขระในตำแหน่งเดียวกันเท่ากับคำที่เป็นไปได้

ผลรวมของอักขระเข้าและออกจากตำแหน่ง == คำที่เป็นไปได้ (สำหรับการเดาทั้งหมด)

ความยาวเท่ากับความยาวของคำที่เป็นไปได้ (ผลรวมของเข้าและออกจากตำแหน่ง)

(ถ้าอักขระ outOfPlace มีค่าเท่ากับ 0) ดังนั้นที่อักขระทั้งหมดแตกต่างจากคำที่เป็นไปได้

}

}


รหัส

หมายเหตุสำหรับสิ่งนี้ในการทำงานคุณต้องรวม ppcg_mastermind_dict.txt และ ppcg_mastermind_passes.txt ในไดเรกทอรีที่กำลังทำงานอยู่ (หรือใน VS ในไดเรกทอรีเดียวกันและตั้งค่า "คัดลอกไปยังไดเรกทอรีไดเรกทอรี" เป็นจริง) ฉันขอโทษสำหรับคุณภาพของรหัสที่ยังคงมีงานต้องทำในเรื่องนี้มันควรจะทำงาน

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

Python - ต่ำสุด: 87, สูงสุด: 108, ทั้งหมด: 96063, เวลา: 4s

นี่คือโพสต์ที่สองของฉัน วิธีนี้ใช้เวลาน้อยลง แต่ให้คะแนนแย่ลง และสามารถเรียกใช้โดยใช้:

  • CPython 2
  • CPython 3
  • Pypy 2 (เร็วที่สุด)
  • Pypy 3

ขั้นตอน:

  • ค้นหา 2 คันแรกที่ใช้การคาดเดาเช่น. ...., .. ......
  • นับ freqencies อักขระสำหรับแต่ละคำในรหัสผ่าน
  • เดาชุดค่าผสมที่ถูกต้องแต่ละครั้งหลังจากกรองตามความยาวของคำและความยาวของอักขระ

มีค่าใช้จ่ายประมาณ 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 (ยังคงทำงานอยู่ ... ณ ตอนนี้ min / avg / max ของ 8 / 9,2 / 11, ประมาณที่1500 300 ชั่วโมงรวมรันไทม์)

อัปเดต: เปลี่ยนการคาดเดาเริ่มต้นเพื่อเพิ่มความเร็วขึ้นบ้าง แก้ไขข้อบกพร่อง

มันอาจจะไม่เสร็จก่อนการแข่งขันนี้จะทำ แต่ฉันก็อาจจะโพสต์มัน ไม่ได้กำหนดความยาวของคำแต่ละคำดังนั้นจึงต้องตรวจสอบทั้งพจนานุกรมซึ่ง ... ใช้เวลาพอสมควร

เมื่อเดาสองครั้งแรกมันจะเป็นตัวกำหนดความยาวทั้งหมดจำนวนของ 'e' และจำนวนอักขระที่แตกต่างกัน

จากนั้นจะลองชุดค่าผสมทั้งหมดที่พอเพียงกับสถิติเหล่านั้นรวมถึงการเดาก่อนหน้าทั้งหมด

รุ่นล่าสุด (และล่าสุด) นี้ได้เพิ่ม 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;
    }

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.