Java 13.923 (min: 11, maks: 17)
Pembaruan: peningkatan skor (pecahkan <14 / crack avg!), Kode baru
- Memeriksa karakter yang dikenal sekarang lebih padat (sekarang ABABAB *, bukan -AAAA *)
- Saat tidak ada karakter yang dikenal tersedia, dua yang tidak dikenal akan dihitung dalam satu tebakan tunggal
- Tebakan yang salah disimpan dan digunakan untuk memeriksa kemungkinan kecocokan
- Beberapa tweaker konstan dengan logika baru di tempat
Pos asli
Saya telah memutuskan untuk sepenuhnya berfokus pada jumlah tebakan daripada kinerja (mengingat aturan). Ini menghasilkan program pintar yang sangat lambat.
Alih-alih mencuri dari program yang dikenal saya memutuskan untuk menulis semuanya dari awal, tetapi ternyata beberapa / kebanyakan ide adalah sama.
Algoritma
Beginilah cara kerja saya:
- Lakukan permintaan tunggal yang menghasilkan jumlah e dan karakter secara total
- Selanjutnya kita mencari spasi, menambahkan beberapa karakter yang tidak dikenal di akhir untuk mendapatkan jumlah karakter
- Setelah spasi ditemukan, kami masih ingin mengetahui jumlah karakter yang lebih banyak, sementara itu saya juga mendapatkan lebih banyak data tentang karakter yang dikenal (jika mereka berada pada posisi genap) yang akan membantu saya menghilangkan banyak frasa.
- Ketika kita mencapai batas tertentu (jejak / kesalahan) itu menghasilkan semua frase yang mungkin dan memulai pencarian biner, sebagian besar waktu masih menambahkan karakter yang tidak dikenal di akhir.
- Akhirnya kami melakukan beberapa tebakan!
Contoh tebakan
Ini adalah contoh aktual:
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
Karena kode saya tidak pernah benar-benar fokus pada satu kata dan hanya mengumpulkan informasi tentang frasa lengkap, itu harus menghasilkan banyak frasa sehingga sangat lambat.
Kode
Dan akhirnya di sini adalah kode (jelek), jangan coba-coba memahaminya, maaf:
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;
}
}