4

I don't know if anyone is going to be interested in this (or if it the correct place to post it!). I spent a couple of hours trying to write a Script that would solve the 'Safe Cracker' puzzle from the newspaper. My code worked but as you will see, it is very clunky and not at all well written (plus some of my maths is suspect!). I'm not really looking for critique on the code because it was only a test to see if I could do it, but I wound be really interested to see if someone has done a better job!

The Puzzle


set ListNoZeros to {}
set ListNoRepeats1 to {}
set ListNoRepeats2 to {}
set ListNoRepeats3 to {}
set ListNoRepeats4 to {}
set ListNoRepeats5 to {}
set ListNoRepeats6 to {}
set ListNoRepeats7 to {}
set ListNoRepeats8 to {}
set ListNoRepeats9 to {}
set ListNoRepeats10 to {}
set ListNoRepeats11 to {}
set ListNoRepeats12 to {}
set ListNoRepeats13 to {}
set ListNoRepeats14 to {}
set ListNoRepeats15 to {}
set ListNoRepeats16 to {}
set ListAnswer to {}

repeat with a from 1234 to 9876
   
   --split the number into seperate digits
   set theNumber to a as string
   
   set String1 to characters 1 thru 1 of theNumber
   set String2 to characters 2 thru 2 of theNumber
   set String3 to characters 3 thru 3 of theNumber
   set String4 to characters 4 thru 4 of theNumber
   
   set Num1 to String1 as number
   set Num2 to String2 as number
   set Num3 to String3 as number
   set Num4 to String4 as number
   
   -- Remove all zeros
   if Num1 is not 0 and Num2 is not 0 and Num3 is not 0 and Num4 is not 0 then copy a to the end of ListNoZeros -- Number of posibilities = 6357
end repeat

-- Is each number different? (Testing 1 & 2)
repeat with b from 1 to length of ListNoZeros
   set theCurrentListItem to item b of ListNoZeros
   
   --split the number into seperate digits
   set theNumber to theCurrentListItem as string
   
   set String1 to characters 1 thru 1 of theNumber
   set String2 to characters 2 thru 2 of theNumber
   set String3 to characters 3 thru 3 of theNumber
   set String4 to characters 4 thru 4 of theNumber
   
   --Convert back to number
   set Num1 to String1 as number
   set Num2 to String2 as number
   set Num3 to String3 as number
   set Num4 to String4 as number
   
   if Num1 / Num2 is not equal to 1 then copy theCurrentListItem to the end of ListNoRepeats1 -- Number of posibilities = 5790
end repeat

-- Is each number different? (Testing 1 & 3)
repeat with c from 1 to length of ListNoRepeats1
   set theCurrentListItem to item c of ListNoRepeats1
   
   
   --split the number into seperate digits
   set theNumber to theCurrentListItem as string
   
   set String1 to characters 1 thru 1 of theNumber
   set String2 to characters 2 thru 2 of theNumber
   set String3 to characters 3 thru 3 of theNumber
   set String4 to characters 4 thru 4 of theNumber
   
   --Convert back to number
   set Num1 to String1 as number
   set Num2 to String2 as number
   set Num3 to String3 as number
   set Num4 to String4 as number
   
   if Num1 / Num3 is not equal to 1 then copy theCurrentListItem to the end of ListNoRepeats2 -- Number of posibilities = 5160
end repeat

-- Is each number different? (Testing 1 & 4)
repeat with d from 1 to length of ListNoRepeats2
   set theCurrentListItem to item d of ListNoRepeats2
   
   --split the number into seperate digits
   set theNumber to theCurrentListItem as string
   
   set String1 to characters 1 thru 1 of theNumber
   set String2 to characters 2 thru 2 of theNumber
   set String3 to characters 3 thru 3 of theNumber
   set String4 to characters 4 thru 4 of theNumber
   
   --Convert back to number
   set Num1 to String1 as number
   set Num2 to String2 as number
   set Num3 to String3 as number
   set Num4 to String4 as number
   
   if Num1 / Num4 is not equal to 1 then copy theCurrentListItem to the end of ListNoRepeats3 -- Number of posibilities = 4588
end repeat

-- Is each number different? (Testing 2 & 3)
repeat with e from 1 to length of ListNoRepeats3
   set theCurrentListItem to item e of ListNoRepeats3
   
   --split the number into seperate digits
   set theNumber to theCurrentListItem as string
   
   set String1 to characters 1 thru 1 of theNumber
   set String2 to characters 2 thru 2 of theNumber
   set String3 to characters 3 thru 3 of theNumber
   set String4 to characters 4 thru 4 of theNumber
   
   --Convert back to number
   set Num1 to String1 as number
   set Num2 to String2 as number
   set Num3 to String3 as number
   set Num4 to String4 as number
   
   if Num2 / Num3 is not equal to 1 then copy theCurrentListItem to the end of ListNoRepeats4 -- Number of posibilities = 4028
end repeat

-- Is each number different? (Testing 2 & 4)
repeat with f from 1 to length of ListNoRepeats4
   set theCurrentListItem to item f of ListNoRepeats4
   
   --split the number into seperate digits
   set theNumber to theCurrentListItem as string
   
   set String1 to characters 1 thru 1 of theNumber
   set String2 to characters 2 thru 2 of theNumber
   set String3 to characters 3 thru 3 of theNumber
   set String4 to characters 4 thru 4 of theNumber
   
   --Convert back to number
   set Num1 to String1 as number
   set Num2 to String2 as number
   set Num3 to String3 as number
   set Num4 to String4 as number
   
   if Num2 / Num4 is not equal to 1 then copy theCurrentListItem to the end of ListNoRepeats5 -- Number of posibilities = 3526
end repeat

-- Is each number different? (Testing 3 & 4)
repeat with g from 1 to length of ListNoRepeats5
   set theCurrentListItem to item g of ListNoRepeats5
   
   --split the number into seperate digits
   set theNumber to theCurrentListItem as string
   
   set String1 to characters 1 thru 1 of theNumber
   set String2 to characters 2 thru 2 of theNumber
   set String3 to characters 3 thru 3 of theNumber
   set String4 to characters 4 thru 4 of theNumber
   
   --Convert back to number
   set Num1 to String1 as number
   set Num2 to String2 as number
   set Num3 to String3 as number
   set Num4 to String4 as number
   
   if Num3 / Num4 is not equal to 1 then copy theCurrentListItem to the end of ListNoRepeats6 -- Number of posibilities = 3024
end repeat

-- The 4th is 3 more than the 3rd
repeat with h from 1 to length of ListNoRepeats6
   set theCurrentListItem to item h of ListNoRepeats6
   
   --split the number into seperate digits
   set theNumber to theCurrentListItem as string
   
   set String1 to characters 1 thru 1 of theNumber
   set String2 to characters 2 thru 2 of theNumber
   set String3 to characters 3 thru 3 of theNumber
   set String4 to characters 4 thru 4 of theNumber
   
   --Convert back to number
   set Num1 to String1 as number
   set Num2 to String2 as number
   set Num3 to String3 as number
   set Num4 to String4 as number
   
   if Num4 = Num3 + (3) then copy theCurrentListItem to the end of ListNoRepeats7 -- Number of posibilities = 252
end repeat

-- The 1st and the 3rd differ by 4. First (1st > 3rd)
repeat with i from 1 to length of ListNoRepeats7
   set theCurrentListItem to item i of ListNoRepeats7
   
   --split the number into seperate digits
   set theNumber to theCurrentListItem as string
   
   set String1 to characters 1 thru 1 of theNumber
   set String2 to characters 2 thru 2 of theNumber
   set String3 to characters 3 thru 3 of theNumber
   set String4 to characters 4 thru 4 of theNumber
   
   --Convert back to number
   set Num1 to String1 as number
   set Num2 to String2 as number
   set Num3 to String3 as number
   set Num4 to String4 as number
   
   if (Num3 - Num1) = 4 then copy theCurrentListItem to the end of ListNoRepeats8 -- Number of posibilities = 12
end repeat

-- The 1st and the 3rd differ by 4. Second (1st < 3rd)
repeat with j from 1 to length of ListNoRepeats7 -- Using this list twice.....
   set theCurrentListItem to item j of ListNoRepeats7
   
   --split the number into seperate digits
   set theNumber to theCurrentListItem as string
   
   set String1 to characters 1 thru 1 of theNumber
   set String2 to characters 2 thru 2 of theNumber
   set String3 to characters 3 thru 3 of theNumber
   set String4 to characters 4 thru 4 of theNumber
   
   --Convert back to number
   set Num1 to String1 as number
   set Num2 to String2 as number
   set Num3 to String3 as number
   set Num4 to String4 as number
   
   if (Num1 - Num3) = 4 then copy theCurrentListItem to the end of ListNoRepeats9 -- Number of posibilities = 30
end repeat

-- Join these 2 lists together
set ListNoRepeats10 to ListNoRepeats8 & ListNoRepeats9 -- Number of posibilities = 42

-- The sum of the middle two is divisible by 5
repeat with k from 1 to length of ListNoRepeats10
   set theCurrentListItem to item k of ListNoRepeats10
   
   --split the number into seperate digits
   set theNumber to theCurrentListItem as string
   
   set String1 to characters 1 thru 1 of theNumber
   set String2 to characters 2 thru 2 of theNumber
   set String3 to characters 3 thru 3 of theNumber
   set String4 to characters 4 thru 4 of theNumber
   
   --Convert back to number
   set Num1 to String1 as number
   set Num2 to String2 as number
   set Num3 to String3 as number
   set Num4 to String4 as number
   
   if (Num2 + Num3) = 5 or (Num2 + Num3) = 10 then copy theCurrentListItem to the end of ListNoRepeats11 -- Number of posibilities = 7
end repeat

-- Exactly one of the first 2 is odd (First & Second)
repeat with l from 1 to length of ListNoRepeats11
   set theCurrentListItem to item l of ListNoRepeats11
   
   --split the number into seperate digits
   set theNumber to theCurrentListItem as string
   
   set String1 to characters 1 thru 1 of theNumber
   set String2 to characters 2 thru 2 of theNumber
   set String3 to characters 3 thru 3 of theNumber
   set String4 to characters 4 thru 4 of theNumber
   
   --Convert back to number
   set Num1 to String1 as number
   set Num2 to String2 as number
   set Num3 to String3 as number
   set Num4 to String4 as number
   
   if Num1 mod 2 = 0 and Num2 mod 2 = 1 then copy theCurrentListItem to the end of ListNoRepeats12
   
   if Num1 mod 2 = 1 and Num2 mod 2 = 0 then copy theCurrentListItem to the end of ListNoRepeats13
   
   -- Join these 2 lists together
   set ListNoRepeats14 to ListNoRepeats12 & ListNoRepeats13 -- Number of posibilities = 3
end repeat

-- 2nd or 4th are square (but not both)
repeat with m from 1 to length of ListNoRepeats14
   set theCurrentListItem to item m of ListNoRepeats14
   
   set theNumber to theCurrentListItem as string
   
   set String1 to characters 1 thru 1 of theNumber
   set String2 to characters 2 thru 2 of theNumber
   set String3 to characters 3 thru 3 of theNumber
   set String4 to characters 4 thru 4 of theNumber
   
   --Convert back to number
   set Num1 to String1 as number
   set Num2 to String2 as number
   set Num3 to String3 as number
   set Num4 to String4 as number
   
   if Num2 = 1 or Num2 = 4 or Num2 = 9 then copy theCurrentListItem to the end of ListNoRepeats15
   
   if Num4 = 1 or Num4 = 4 or Num4 = 9 then copy theCurrentListItem to the end of ListNoRepeats16
   
   -- Join these 2 lists together
   set ListAnswer to ListNoRepeats15 & ListNoRepeats16
end repeat

display dialog "The Correct Code is - " & ListAnswer ```
5
  • What does “square” means? Like square of number (0,1,4,9) or like even (0, 2, 4, 6, 8) – Aivar Paalberg Dec 27 '20 at 13:08
  • 1
    It's the 1,4 & 9 sequence. – Niscors Dec 27 '20 at 13:32
  • 2
    Not sure whether AD is a good place for code improvement discussions. If you keep getting downvotes/no answers, please flag for mod attention and we can migrate the question to StackOverflow for you. – nohillside Dec 27 '20 at 14:00
  • As for the code itself, I wonder why you first build a list of possible numbers and then test each condition in separate loops. Can't you just run one loop and do all tests in it directly? – nohillside Dec 27 '20 at 14:04
  • 5
    @nohillside Maybe codereview.SE would also be a good location for this question. – BalinKingOfMoria Reinstate CMs Dec 27 '20 at 23:32
5

Ignoring my own comment about this being borderline offtopc, here is a (rather brute-force) Swift version looping over all possible combinations once.

func is_square(_ i: Int) -> Bool {
    return (i == 1) || (i == 4) || (i == 9)
}

func is_odd(_ i: Int) -> Bool {
    return (i % 2) == 1
}

func xor(_ b1: Bool, _ b2: Bool) -> Bool {
     return ((b1 && !b2) || (!b1 && b2))
}

for first in 1...9 {
    for second in 1...9 {
        for third in 1...9 {
            for fourth in 1...9 {
                if xor(is_square(second), is_square(fourth)) // 1 Either the second or fourth is square, but not both
                   && (third + 3 == fourth)                  // 2 The fourth is three more than the third
                   && xor(is_odd(first), is_odd(second))     // 3 Exactly one of the first two is odd
                   && (abs(first - third) == 4)              // 4 The first and third differ by four
                   && ((second + third) % 5 == 0)            // 5 The sum of the middle two is divisible by five
                {
                    print("\(first)\(second)\(third)\(fourth)")
                }
            }
        }
    }
}

Or you start with the list of all combinations and then apply a filter to determine the valid one (without error checking in this case as we already know there will be exactly one solution).

func digits(_ i: Int) -> (Int, Int, Int, Int) {
     let d1 = (i / 1000)
     let d2 = (i / 100) % 10
     let d3 = (i / 10) % 10
     let d4 = i % 10
     return (d1, d2, d3, d4)
}

let result = Array(1111...9999).filter({ (code: Int) -> Bool in
    let (first, second, third, fourth) = digits(code)
    return xor(is_square(second), is_square(fourth))
           && (third + 3 == fourth)
           && xor(is_odd(first), is_odd(second))
           && (abs(first - third) == 4)
           && ((second + third) % 5 == 0)
})[0]
print("\(result)")

PS: I didn't add a check for digit difference because I was too lazy :-) (but from looking at the rules, a combination where digits are the same doesn't match anyway)

3

Doing one loop and exiting early, with improvements to condition checking. AppleScript is definitely not the language one would commonly choose for this sort of problem!

set squarenumbers to {1, 4, 9}
repeat with n from 1234 to 9876
try
        -- split digits
        set nstr to n as string
        set n1 to character 1 of nstr as number
        set n2 to character 2 of nstr as number
        set n3 to character 3 of nstr as number
        set n4 to character 4 of nstr as number
        -- check distinct digits and for conditions 1 through 5
        if ( ¬
                n1 = n2 or n1 = n3 or n1 = n4 or n2 = n3 or n2 = n3 or n3 = n4 or ¬
                not ((squarenumbers contains n2 and squarenumbers does not contain n4) or (squarenumbers does not contain n2 and squarenumbers contains n4)) or ¬
                not (n3 + 3 = n4) or ¬
                not ((n1 mod 2 = 0 and n2 mod 2 = 1) or (n1 mod 2 = 1 and n2 mod 2 = 0)) or ¬
                not ((n1 - n3) = 4 or (n3 - n1) = 4) or ¬
                not ((n2 + n3) mod 5 = 0) ¬
        ) then
                error 0
        end if
        -- found
        return n
end try
end repeat

Replacing display dialog with return in your solution and using time osascript file shows 12.23 seconds for the solution in the question and 1.25 seconds for my solution. More optimisations in the iteration are possible.

6
  • We seem to like a small challenge :-) Where do you test for "Exactly one of the first two is odd"? – nohillside Dec 27 '20 at 15:04
  • 1
    @nohillside Haha I only noticed you combined the ifs after I came back to edit my answer with that improvement! Looks like I've completely missed that condition oops, thanks I'll add that in – grg Dec 27 '20 at 15:06
  • Well, it is the same code, basically, with the little detail of execution speed :-) – nohillside Dec 27 '20 at 15:09
  • 1
    Was more thinking about AppleScript vs Swift (0.006 seconds overall) in general. – nohillside Dec 27 '20 at 15:15
  • 1
    @nohillside Oh yes absolutely Swift is the way to go! Maybe I should try a JSX answer later, someone else can do Objective-C! – grg Dec 27 '20 at 15:22
2

I feel that this is one of the examples where writing code will take much longer than solving it on the paper :-). I think that this kind of solution is expected for solving puzzles in newspapers.

Paper and pencil algorithm:

determine possible third and fourth numbers

fourth is three more than third

9 is largest number possible, so maximum third number is 9-3 = 6 so possible third and fourth numbers are six pairs: (1, 4), (2, 5), (3, 6), (4, 7), (5, 8), (6, 9)

determine possible second number

either second or fourth is square but not both

sum of middel two (second and third) is divisible by 5

four different digits

possible squares are 1, 4, 9. Go through 6 pairs and if fourth number is square find if there is a number which makes sum divisible by 5; if fourth not square second number must be on of squares and find whether one of them delivers divisibility by 5.

This leaves one triplet which meets the conditions (1, 4, 7)

determine first number

exactly on of the first two is odd

the first and third differ by four

As third number is 4 and 0 is not allowed then it must be 8. As second number is odd, then 8 satisfies the condition.

EDIT: added algorithm implementation in Python language:

squares = {1, 4, 9}
digits = set(range(1, 10))
for third, fourth in ((i, i+3) for i in range(1, 7)):   
    for second in digits - {third, fourth}:             
        if (second + third) % 5 == 0:
            if fourth in squares and second in squares:
                continue
            if squares.intersection({second, fourth}):
                for first in digits - {second, third, fourth}:
                    if abs(first - third) == 4 and (first + second) % 2 != 0:
                        print(f'{first}{second}{third}{fourth}')
5
  • 2
    Where is the fun in that? – red_menace Dec 28 '20 at 15:04
  • Added fun part too (not in Applescript which I am not familiar with but Python). – Aivar Paalberg Dec 28 '20 at 23:14
  • In Python range does: "Return an object that produces a sequence of integers from start (inclusive) to stop (exclusive)" – Aivar Paalberg Dec 29 '20 at 7:07
  • The python code produces "SyntaxError: invalid syntax" on the print(f'{first}{second}{third}{fourth}') line of code. – user3439894 Dec 30 '20 at 17:52
  • f-strings require 3.6 <= Python – Aivar Paalberg Dec 30 '20 at 18:20
2

Here's a method written in Python which is inefficient but simple to understand because there are no nested loops - it tries random numbers between 1111 and 9999 until it finds the correct answer:

from random import randint

# Loop forever:
while True:  
 # Assign a random number between 1 and 9 to the variables a, b, c, and d:
 a,b,c,d = [randint(1,9) for i in range(4)]

 # "5 The sum of the middle two is divisible by five."
 if (b + c) % 5 != 0:  
  continue

 # "2 The fourth is three more than the third."
 if c + 3 != d:  
  continue

 # "3 Exactly one of the first two is odd."
 if (a % 2 == 0 and b % 2 == 0) or (a % 2 == 1 and b % 2 == 1):
  continue

 # "1 Either the second or the fourth is square, but not both."
 if (b in [1,4,9] and d in [1,4,9]) or (b not in [1,4,9] and d not in [1,4,9]):
  continue

 # "4 The first and third differ by four."
 if (abs(a-c) != 4):
  continue

 # Print the answer:
 print(a,b,c,d)

 # Exit the loop:
 break

(logical AND could be used for the odd/even check, and XOR could be used for the squares check, but this way is easier for non-programmers to read)

1
  • 1
    A random walk down to the safe. Nice :-) – nohillside Dec 30 '20 at 17:18

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .