Code Guesser (36) - I have no idea why that solution is working

Back to General discussions forum

Chrisislearning     2025-01-27 21:33:52

I spent last 3 days trying to solve this problem by mimicking the logic of a human player and I got a wall of code that is useless because it doesn't give correct answer:

with open("data.txt", "r") as f:
        data = f.read().splitlines()

new_data = []
_num_of_guesses = int(data.pop(0))
solution = ["X","X","X","X"]
first_num = ["0","1","2","3","4","5","6","7","8","9"]
second_num = ["0","1","2","3","4","5","6","7","8","9"]
third_num = ["0","1","2","3","4","5","6","7","8","9"]
fourth_num = ["0","1","2","3","4","5","6","7","8","9"]
counter = 0

for line in data:
    guess, answer = line.split()
    guess, answer = list(guess), int(answer)

    if answer == 0:
        if guess[0] in first_num:
            first_num.remove(guess[0])
        if guess[1] in second_num:
            second_num.remove(guess[1])
        if guess[2] in third_num:
            third_num.remove(guess[2])
        if guess[3] in fourth_num:
            fourth_num.remove(guess[3])
    else:
        new_data.append(line)

print("first_num  ", first_num)
print("second_num ", second_num)
print("third_num  ", third_num)
print("fourth_num ", fourth_num)

for line in new_data:
    guess, answer = line.split()
    guess, answer = list(guess), int(answer)

def is_one_and_only(x):
    if x == guess[0]:
        if guess[0] in first_num and guess[0] == solution[0]:
            first_num.remove(guess[0])
        if {guess[0] in first_num and
            (guess[1] not in second_num or solution[1] != "X") and
            (guess[2] not in third_num or solution[2] != "X") and
            (guess[3] not in fourth_num or solution[3] != "X")}:
            return True
    elif x == guess[1]:
        if guess[1] in second_num and guess[1] == solution[1]:
            second_num.remove(guess[1])
        if {guess[1] in second_num and
            (guess[0] not in first_num or solution[0] != "X") and
            (guess[2] not in third_num or solution[2] != "X") and
            (guess[3] not in fourth_num or solution[3] != "X")}:
            return True
    elif x == guess[2]:
        if guess[2] in third_num and guess[2] == solution[2]:
            third_num.remove(guess[2])
        if {guess[2] in third_num and
            (guess[0] not in first_num or solution[0] != "X") and
            (guess[1] not in second_num or solution[1] != "X") and
            (guess[3] not in fourth_num or solution[3] != "X")}:
            return True
    elif x == guess[3]:
        if guess[3] in fourth_num and guess[3] == solution[3]:
            fourth_num.remove(guess[3])
        if {guess[3] in fourth_num and
            (guess[0] not in first_num or solution[0] != "X") and
            (guess[1] not in second_num or solution[1] != "X") and
            (guess[2] not in third_num or solution[2] != "X")}:
            return True


while "X" in solution and counter < 20:
    counter += 1

    for n in range(0,3):
        if is_one_and_only(guess[n]):
            solution[n] = guess[n]

    if guess[0] == solution[0]:
        if guess[1] in second_num:
            second_num.remove(guess[1])
        if guess[2] in third_num:
            third_num.remove(guess[2])
        if guess[3] in fourth_num:
            fourth_num.remove(guess[3])

    if guess[1] == solution[1]:
        if guess[0] in first_num:
            first_num.remove(guess[0])
        if guess[2] in third_num:
            third_num.remove(guess[2])
        if guess[3] in fourth_num:
            fourth_num.remove(guess[3])

    if guess[2] == solution[2]:
        if guess[0] in first_num:
            first_num.remove(guess[0])    
        if guess[1] in second_num:
            second_num.remove(guess[1])
        if guess[3] in fourth_num:
            fourth_num.remove(guess[3])

    if guess[3] == solution[3]:
        if guess[0] in first_num:
            first_num.remove(guess[0])    
        if guess[1] in second_num:
            second_num.remove(guess[1])
        if guess[2] in third_num:
            third_num.remove(guess[2])

    temp = guess

print(solution)

I saw solution of somebody else and my heart sank because it's so short:

def compare(G, answer):
    count=0
    for i in range(len(G)): 
        if G[i]==answer[i]: count+=1
    return count
def search(numbers):
    All = [str(num).zfill(4) for num in range(10000)]
    for G, A in numbers:
        All = [num for num in All if compare(G, num) == A]
    return All[0]


n = int(input())
numbers=[]
for _ in range(n):
    G, A  = input().split()
    numbers.append((G, int(A)))
print(search(numbers))

and also because I don't understand why it works. Why is there only one position in the list "All"? I get extra confused when I replace that one-liner of list comprehension with this:

    for num in All:
        if compare(G, num) == A:
            All.append(num)
return All[0]

And I get different result. Please help, I get really frustrated by this magic.

gardengnome     2025-01-27 21:56:15
User avatar

The list comprehension All = [num for num in All if compare(G, num) == A] works as follows: first, [num for num in All if compare(G, num) == A] is computed, and afterwards the results is assigned to All overwriting whatever it was before.

Your code appends new numbers to All: All.append(num) while iterating over All itself, i.e. the original content of All always remains. You would need to create a new list All_new = [], append to it All_new.append(num) and finally set All = All_new to mimic the original code.

PS Appending new elements to a list while iterating over it is a bit obscure but absolutely fine in Python; in fact it allows for a nice BFS implementation.

Chrisislearning     2025-01-27 22:57:37

My bad, I copied earlier version. I already did what you said:

def compare(G, answer):
    count=0
    for i in range(len(G)):
        if G[i]==answer[i]: count+=1
    return count

newAll =[]
def search(numbers):
    All = [str(num).zfill(4) for num in range(10000)]          #all possible numbers from 0001 to 9999
    for G, A in numbers:
#         All = [num for num in All if compare(G, num) == A]
        for num in All:
            if compare(G, num) == A:
                newAll.append(num)
    All = newAll
    print(len(All))
    return All


with open("data.txt", "r") as f:
        data = f.read().splitlines()
_num_of_guesses = int(data.pop(0))

numbers=[]
for line in data:
    G, A  = line.split()                                       #make a list of tuples (guess, hint)
    A = int(A)
    numbers.append((G, int(A)))

print(search(numbers))

But still the result is completely different. Instead of one, I get 69984 different results. 7 times more than possible combinations. In the original version with list comprehension, there is only one. How does that code get to this conclusion, is beyond me.

zelevin     2025-01-28 00:11:32

The All = newAll line is in the wrong scope. It should be inside the for G, A ... loop.

If you need a hint re: how this code works, feel free to ask. But also please don't post solutions in the forum (especially other people's solutions); we can see your latest submitted code.

Chrisislearning     2025-01-28 07:34:41

Man, I'm all over the place! Sorry. List comprehension aside, I think my main problem is that compare function. With this input I've been working with:

9009 0 2465 1 9058 0 7764 0 8901 0 7839 1 8041 0 3544 1 0817 0 0526 1 2146 1 3570 1 7117 0 0519 0

It takes each guess and compares it with every one of 10 000 numbers on the list: if G[i]==num[i]: count+=1. Then, if the A == 1, so only one digit in that guess is the correct digit, it keeps that guess in the All list. If it works, for example, with guess 2465, how does it know which digit is correct? In my code I started by eliminating all digits that I knew were INcorrect (because for example a guess 9009 was marked wih 0, so we know the first digit can't be 9), but this code doesn't do that. It checks that 2465 guess, then it should return every number that is 2XXX, every number that is X4XX and so on.

Unless. Unless it DOES do that, but then takes the next guess, 7839, and compares that guess only with numbers that are still in the list after iterating through 2465? Sorry, it's difficult to wrap your head around this combination (and mine is fried now after my own fruitless attempts at this problem) but I think I finally got it. Yes. It sort of makes a list of possible matches for every guess and then compares those lists of possible matches with each other.

gardengnome     2025-01-28 08:16:37
User avatar

Let's look at the shorter example with three digits. There are 10^3 = 1000 candidate solutions. The first guess in the input data states 402 0. Now if the solution was 000, then you would get a match of 1 digit (both have a 0 as the second digit but differ in the first and last digit). What can you conclude about candidate 000? Can this principle be extended to all candidate solutions utilising all guesses provided in the input? And what has Sherlock Holmes' guiding principle to do with it?

Please login and solve 5 problems to be able to post at forum