I have come across the following puzzle and couldn't formulate a solution in Picat:
You will generate 5-digit numbers, where each digit is in
1..5and different from the others, with the constraint that any three adjacent digits used in one number can’t be used in another number. How many different numbers can be obtained according to this rule?
For example, if we generated the number 12345, the other numbers CANNOT contain 123, 345, or 456, so all numbers of the following form are banned from the chain:
123AB, A123B, AB123,
234AB, A234B, AB234,
345AB, A345B, AB345,
I got really confused on how to store these "forbidden" sublists and how to check each number against them as I build the list of numbers.
My attempt:
I think I managed to generate the valid "candidate" for a given chain state, but I can't figure out how to generate the chain like this.
import cp.
import util.
valid(Ls, Cd) ?=>
% verify that the head of the chain is correct?
% so the chain consists of permutations of 12345
foreach (L in Ls)
len(L) = 5,
permutation(L, [1,2,3,4,5])
end,
% generate the candidate
Cd = new_list(5),
permutation(Cd, [1,2,3,4,5]),
% check the candidate against the head of the chain
foreach (L in Ls)
not sublist([L[1],L[2],L[3]], Cd),
not sublist([L[2],L[3],L[4]], Cd),
not sublist([L[3],L[4],L[5]], Cd)
end,
solve(Ls),
printf("Cd: %w\n", Cd),
fail,
nl.
% so that 3 element sublists of 12345 are 123,234 and 345.
sublist(X, S) =>
append(_, T , S),
append(X, _ , T),
X.len #>= 0.
% seems to work, the candidates don't have the banned triplets as sublists.
% so in this case the banned triplets would be
% 123,234,345,543,432,321
go => valid([[1,2,3,4,5], [5,4,3,2,1]], _).
main => go.
Comment: It is indeed very interesting that the situation is not symmetric. If we analyze the state:
[12345,12435,12534,13245,13425,13524,14235,
14325,14523,21543,24153,25413,35421,43152]
we see that the three candidates which are valid/can be appended to this chain are:
Cd1: [5,3,2,1,4]
Cd2: [4,5,3,1,2]
Cd3: [4,5,3,2,1]
Obviously, if we choose Cd3, since it contains both 453 and 532 it disallows us from choosing any candidate after it, so the chain ends at N=15.
If we choose Cd1, it excludes Cd3 but still keeps Cd2, so the chain goes on to N=16.
Similarly if we choose Cd2, it excludes Cd3 but still keeps Cd1, so again N=16 is possible.
So it seems that in general some candidates contain(and therefore exclude) others, and the length of the chain depends on whether we choose these candidates or not.
Here's the Picat model with the models in Update 4 and Update 5 and Update 6: http://hakank.org/picat/generating_numbers.pi
Update 6: This is probably the constraint model I would have written if not gotten astray from the beginning with wrong assumptions about the problem... It's a more direct approach (from a constraint programmer's perspective) and don't use
permutations/1etc.It is slightly slower than Update 5 (3.7s using the sat solver vs 3.3s for the Update 4 model). The cp solver is, however, much slower on this model. In the Picat program cited above it's model
go3/0. (The fastest model isgo/0.)The approach:
The model:
First solution (3.7s with sat):
Update 5 Here's a much faster approach: About 3.3s to find the first solutions, compared to 1min25s for the approach in Update 4.
The approach here is:
Ps), build a 120 x 120 matrixAof 0/1 whereA[P1,P2] = 1means thatPs[P1]andPs[P2]are compatible, i.e. that they have no common tripletXof length 120, whereX[I] = 1means that the permutationsPs[I]should be in the sequence (or rather "set" since the order of the permutations don't make a difference).X[I]*X[J] #= 1 #=> A[I,J]is a "strange" way of saying that bothX[I]andX[J]should be in the sequence ifA[I,J] #= 1.The cp solver takes about 3.3s to find the first length 20 solution. The sat solver is slower for this model: 4.8s (so it's still much faster than the Update 4 version).
Here the complete model:
Here's the first solution:
Update 4 As mentioned in the comments, here's a constraint model which find an sequence of length 20.
A seq of 20 is optimal with the following reasoning: There are 60 possible triplets in the collection of the 120 permutations of 1..5. Each number consists of 3 unique triplets each. Thus, there can not be more than 60 / 3 = 20 numbers in such a sequence.
Here's a 20 number sequence:
This model using the sat solver takes about 1min25 to first this sequence. It's a little more elaborated than the "simple" use of list handling in the previous versions which use backtracking, and that was the problem in these approaches to get a sequence of maximum length.
Some comments:
matrix_element/4is used to connect the triplets in theYmatrix and the numbers inZ.Z) and thus we can make sure that they are distinct.cpmodule is faster on simpler instances (e.g. lengths up to 16), but for larger instances (>16) thensattends to be much better.The model:
And I still think that there is some algorithmic approach which solves this problem in notime...
Update3 Sigh, the program in Update2 was still wrong since it only picked numbers that were later in the permutation list. This third version use
permutation(1..5,Next)so all numbers has a change to be picked.The first solution is of length 16:
The next solution (via backtracking) is - however - of length 15:
So I'm - still - not sure if 16 is the maximum length.
Update2: The version in Update was not completely correct (in fact it was dead wrong), since I forgot to add the triplet to
Forbiddenin the loop (add_forbidden_triplets(Forbidden, Triplets). The program is updated below.The first solution with 12345 are start number is:
And now it's getting interesting since the length of the other sequences (with different start numbers) are around 12..17 numbers. And that's contra intuitive since these things should be symmetric, shouldn't it?
Update: Since I first missed one important constraint in the instructions, here's an adjusted program based on the first approach. It yield a sequence of length 107. The basic - and quite simple - change is that the forbidden triplets are now saved in the hash table
Forbidden. The sequence is finished when there's not any available number (whenFoundis false).Here's the first solution:
Here's my original answer:
Your program generates 106+1 numbers (using initial number to just 12345), not all 120 that my programs below generates. Perhaps I have missed some requirement in the problem? By the way, you don't need
solve/1in your program since there aren't any constraints.Below are two of my approaches: both generate a sequence of length 120, i.e. all numbers can be "chained". Both use
permutations/1(fromutilmodule) to first generate all the 120 permutations (5!=120) and the select non-deterministically some of the permutations that are left (usingselect/3). The checking of the allowed successor is done usingtri/2to generate all triplets andcheck/2to check that there no common triplets.Since I found out early that all number can be used (unless I've missed something), the control when the program is done is when there are no permutations available. This is probably a shortcoming of my approach.
Here's the output of
go/0(converted to numbers):