Java 13,923 (dak: 11, en çok: 17)
Güncelleme: iyileştirilmiş puan (<14 / crack avg! Kırdı), yeni kod
- Bilinen karakterlerin kontrol edilmesi artık daha yoğun (şimdi -AAAA * yerine ABABAB *)
- Bilinen bir karakter bulunmadığında, iki bilinmeyen tek bir tahminde sayılacak
- Yanlış tahminler saklanır ve olası eşleşmeleri kontrol etmek için kullanılır
- Yerinde yeni mantık ile bazı sabit tweaking
Orijinal yayın
Tamamen performans yerine tahmin miktarına odaklanmaya karar verdim (kurallara göre). Bu çok yavaş bir akıllı programla sonuçlandı .
Bilinen programlardan çalmak yerine, her şeyi sıfırdan yazmaya karar verdim, ancak bazı / çoğu fikirlerin aynı olduğu ortaya çıktı.
Algoritma
Maden böyle işler:
- Toplamda e ve karakter miktarına neden olan tek bir sorgu yapın.
- Sonra boşlukları arayacağız, sonunda karakter sayımı için bazı bilinmeyen karakterler ekleyeceğiz.
- Boşluklar bulunduktan sonra, hala daha fazla karakter sayısı bulmak istiyoruz, bu arada, bilinen karakterler hakkında daha fazla veri elde ediyorum (hatta konumlardaysa), bu çok fazla ifadeyi ortadan kaldırmama yardımcı olacak.
- Belli bir sınıra ulaştığımızda (iz / hata) olası tüm cümleleri üretir ve çoğu zaman sonunda hala bilinmeyen karakterleri ekleyen bir ikili arama başlatır.
- Sonunda bazı tahminler yaparız!
Örnek tahminler
İşte gerçek bir örnek:
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
Çünkü kodum hiçbir zaman tek kelimelere odaklanmıyor ve yalnızca tam ifadeyle ilgili bilgi toplıyor, bu ifadeleri çok yavaşlatıyor.
kod
Ve nihayet işte (çirkin) kod, anlamaya çalışmayın bile, üzgünüm:
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;
}
}