0

I would like to solve the following lock challenge using Alloy.

My main issue is how to model the integers representing the digit keys.

I created a quick draft:

sig Digit, Position{}

sig Lock {
 d: Digit one -> lone Position
}

run {} for exactly 1 Lock, exactly 3 Position, 10 Digit

In this context, could you please:

  • tell me if Alloy seems to you suitable to solve this kind of problem?
  • give me some pointers regarding the way I could model the key digits (without using Ints)?

Thank you.

enter image description here

Peter Kriens
  • 15,196
  • 1
  • 37
  • 55
Guyomel
  • 3
  • 2

5 Answers5

1

A simple way to get started, you do not always need sig's. The solution found is probably not the intended solution but that is because the requirements are ambiguous, took a shortcut.

pred lock[ a,b,c : Int ] {
    a=6 || b=8 || c= 2
    a in 1+4 || b in 6+4 || c in 6+1
    a in 0+6 || b in 2+6 || c in 2+0
    a != 7 && b != 3 && c != 8
    a = 7 || b=8 || c=0 
}

run lock for 6 int

Look in the Text view for the answer.

upate we had a discussion on the Alloy list and I'd like to amend my solution with a more readable version:

let sq[a,b,c]       = 0->a + 1->b + 2->c
let digit       = { n : Int | n>=0 and n <10 }

fun correct[ lck : seq digit, a, b, c : digit ] : Int    { # (Int.lck & (a+b+c)) }
fun wellPlaced[ lck : seq digit, a, b, c : digit ] : Int { # (lck & sq[a,b,c])   }

pred lock[ a, b, c : digit ] {
    let lck = sq[a,b,c] {
        1 = correct[ lck, 6,8,2] and 1 = wellPlaced[ lck, 6,8,2]        
        1 = correct[ lck, 6,1,4] and 0 = wellPlaced[ lck, 6,1,4]
        2 = correct[ lck, 2,0,6] and 0 = wellPlaced[ lck, 2,0,6]
        0 = correct[ lck, 7,3,8]
        1 = correct[ lck, 7,8,0] and 0 = wellPlaced[ lck, 7,8,0]
    }
}

run lock for 6 Int
Peter Kriens
  • 15,196
  • 1
  • 37
  • 55
1

My frame of this puzzle is:

enum Digit { N0,N1,N2,N3,N4,N5,N6,N7,N8,N9 }
one sig Code {a,b,c:Digit}

pred hint(h1,h2,h3:Digit, matched,wellPlaced:Int) {
    matched = #(XXXX)        // fix XXXX
    wellPlaced = #(XXXX)     // fix XXXX
}

fact {
    hint[N6,N8,N2, 1,1]
    hint[N6,N1,N4, 1,0]
    hint[N2,N0,N6, 2,0]
    hint[N7,N3,N8, 0,0]
    hint[N7,N8,N0, 1,0]
}

run {}
naimdjon
  • 3,162
  • 1
  • 20
  • 41
  • I like the fact that you are using `enum`s. Your approach seems quite intuitive. – Guyomel Apr 01 '20 at 12:39
  • if you want to check in future, my answer is in [Gist](https://gist.github.com/jpwgad/9ba475945f44b2fb0cd6e2ce3ae443e6) or [Qiita(Japanese)](https://qiita.com/jpwgad/items/38ffe3d31633df677185). – Takashi Nomura Apr 02 '20 at 22:36
1

When you think solve complete, let's examine whether the solution is generic.

Here is another lock.
If you can’t solve this in same form, your solution may not enough.

  • Hint1: (1,2,3) - Nothing is correct.
  • Hint2: (4,5,6) - Nothing is correct.
  • Hint3: (7,8,9) - One number is correct but wrong placed.
  • Hint4: (9,0,0) - All numbers are correct, with one well placed.
0

Yes, I think Alloy is suitable for this kind of problem.

Regarding digits, you don't need integers at all: in fact, it is a bit irrelevant for this particular purpose if they are digits or any set of 10 different identifiers (no arithmetic is performed with them). You can use singleton signatures to declare the digits, all extending signature Digit, which should be marked as abstract. Something like:

abstract sig Digit {}
one sig Zero, One, ..., Nine extends Digit {}

A similar strategy can be used to declare the three different positions of the lock. And btw since you have exactly one lock you can also declare Lock as singleton signature.

Alcino Cunha
  • 186
  • 2
0

I like the Nomura solution on this page. I made a slight modification of the predicate and the fact to solve.

enum Digit { N0,N1,N2,N3,N4,N5,N6,N7,N8,N9 }
one sig Code {a,b,c: Digit}

pred hint(code: Code, d1,d2,d3: Digit, correct, wellPlaced:Int) {
    correct = #((code.a + code.b + code.c)&(d1 + d2 + d3))
    wellPlaced = #((0->code.a + 1->code.b + 2->code.c)&(0->d1 + 1->d2 + 2->d3))
}

fact {
    some code: Code | 
    hint[code, N6,N8,N2, 1,1] and
    hint[code, N6,N1,N4, 1,0] and
    hint[code, N2,N0,N6, 2,0] and
    hint[code, N7,N3,N8, 0,0] and
    hint[code, N7,N8,N0, 1,0]
}

run {}

Update (2020-12-29): The new puzzle presented by Nomura (https://stackoverflow.com/a/61022419/5005552) demonstrates a weakness in the original solution: it does not account for multiple uses of a digit within a code. A modification to the expression for "correct" fixes this. Intersect each guessed digit with the union of the digits from the passed code and sum them for the true cardinality. I encapsulated the matching in a function, which will return 0 or 1 for each digit.

enum Digit {N0,N1,N2,N3,N4,N5,N6,N7,N8,N9}

let sequence[a,b,c] = 0->a + 1->b + 2->c

one sig Code {c1, c2, c3: Digit}

fun match[code: Code, d: Digit]: Int { #((code.c1 + code.c2 + code.c3) & d) }

pred hint(code: Code, d1,d2,d3: Digit, correct, wellPlaced:Int) {   
    // The intersection of each guessed digit with the code (unordered) tells us 
    // whether any of the digits match each other and how many
    correct = match[code,d1].plus[match[code,d2]].plus[match[code,d3]]
    // The intersection of the sequences of digits (ordered) tells us whether 
    // any of the digits are correct AND in the right place in the sequence
    wellPlaced = #(sequence[code.c1,code.c2,code.c3] & sequence[d1, d2, d3])
}

pred originalLock {
    some code: Code | 
    hint[code, N6,N8,N2, 1,1] and
    hint[code, N6,N1,N4, 1,0] and
    hint[code, N2,N0,N6, 2,0] and
    hint[code, N7,N3,N8, 0,0] and
    hint[code, N7,N8,N0, 1,0]
}

pred newLock {
    some code: Code | 
    hint[code, N1,N2,N3, 0,0] and 
    hint[code, N4,N5,N6, 0,0] and
    hint[code, N7,N8,N9, 1,0] and
    hint[code, N9,N0,N0, 3,1]
}

run originalLock
run newLock
run test {some code: Code | hint[code, N9,N0,N0, 3,1]}