जावा 13,923 (न्यूनतम: 11, अधिकतम: 17)
अद्यतन: सुधार स्कोर (<14 / दरार एवीजी!), नए कोड को तोड़ा
- अब -AAAA * के बजाय ज्ञात पात्रों की जाँच अब सघन (अब ABABAB *)
- जब कोई ज्ञात वर्ण उपलब्ध नहीं होता है, तो दो अज्ञात को एक ही अनुमान में गिना जाएगा
- गलत अनुमानों को संग्रहीत किया जाता है और संभावित मैचों की जांच करने के लिए उपयोग किया जाता है
- जगह में नए तर्क के साथ कुछ निरंतर tweaking
मूल पोस्ट
मैंने प्रदर्शन (नियमों को देखते हुए) के बजाय अनुमानों की मात्रा पर पूरी तरह से ध्यान केंद्रित करने का निर्णय लिया है। इससे एक बहुत ही धीमी गति से स्मार्ट प्रोग्राम हुआ।
ज्ञात कार्यक्रमों से चोरी करने के बजाय मैंने खरोंच से सब कुछ लिखने का फैसला किया, लेकिन यह पता चला कि कुछ / अधिकांश विचार समान हैं।
कलन विधि
यह मेरा काम करता है:
- एक एकल क्वेरी करें, जिसके परिणामस्वरूप ई की मात्रा और कुल अक्षर होते हैं
- अगला हम रिक्त स्थान की तलाश करते हैं, चरित्र गणना प्राप्त करने के लिए अंत में कुछ अज्ञात पात्रों को जोड़ते हैं
- एक बार रिक्त स्थान मिल जाने के बाद भी हम अधिक वर्ण गणना ज्ञात करना चाहते हैं, इस दौरान मुझे ज्ञात वर्णों पर अधिक डेटा मिलता है (यदि वे पदों पर भी हैं) जो मुझे बहुत सारे वाक्यांशों को खत्म करने में मदद करेंगे।
- जब हम एक निश्चित सीमा (निशान / त्रुटि) तक पहुँचते हैं, तो यह सभी संभावित वाक्यांशों को उत्पन्न करता है और एक द्विआधारी खोज शुरू करता है, अधिकांश समय अभी भी अंत में अज्ञात पात्रों को जोड़ता है।
- अंत में हम कुछ अनुमान लगाते हैं!
उदाहरण अनुमान
यहाँ एक वास्तविक उदाहरण है:
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;
}
}