/* Two Cube Calendar as well as Three Cube Month Calendar in Picat. Martin Gardner (April 1969): Construct two cubes which can be used as a calendar, i.e. together can represent the numbers 01..31. One cube has the sides 1,2 and the other 3,4,5. Answer: cube1 = [0,1,2,6,7,8] cube2 = [0,1,2,3,4,5] Another solution is (by swithing 6->9) cube1 = [0,1,2,7,8,9] cube2 = [0,1,2,3,4,5] I.e. 6 must be used as both 6 and 9. Also, see https://en.wikipedia.org/wiki/Two-cube_calendar Here we also construct a Three Cube Month Calendar, based on the same principle (read: "trick") as for the Two Cube Calenar, i.e. one have to use one digit/character in two ways. See below for comments. Following is a description of the go* predicates. go/0: This is a a simpler version of g1/0, though a little slower when using SAT. (It's faster than g1/0 when using CP.) The configuration of the two cubes: cube1 = [0,1,2,6,7,8] cube2 = [0,1,2,3,4,5] Testing how we construct the different days with the two cubes: 01: [0,from,dice1,1,from,dice2] 02: [0,from,dice1,2,from,dice2] 03: [0,from,dice1,3,from,dice2] 04: [0,from,dice1,4,from,dice2] 05: [0,from,dice1,5,from,dice2] 06: [0,from,dice2,6,from,dice1] 07: [0,from,dice2,7,from,dice1] 08: [0,from,dice2,8,from,dice1] 09: [0,from,dice2,6,from,dice1] 10: [1,from,dice1,0,from,dice2] 11: [1,from,dice1,1,from,dice2] 12: [1,from,dice1,2,from,dice2] 13: [1,from,dice1,3,from,dice2] 14: [1,from,dice1,4,from,dice2] 15: [1,from,dice1,5,from,dice2] 16: [1,from,dice2,6,from,dice1] 17: [1,from,dice2,7,from,dice1] 18: [1,from,dice2,8,from,dice1] 19: [1,from,dice2,6,from,dice1] 20: [2,from,dice1,0,from,dice2] 21: [2,from,dice1,1,from,dice2] 22: [2,from,dice1,2,from,dice2] 23: [2,from,dice1,3,from,dice2] 24: [2,from,dice1,4,from,dice2] 25: [2,from,dice1,5,from,dice2] 26: [2,from,dice2,6,from,dice1] 27: [2,from,dice2,7,from,dice1] 28: [2,from,dice2,8,from,dice1] 29: [2,from,dice2,6,from,dice1] 30: [3,from,dice2,0,from,dice1] 31: [3,from,dice2,1,from,dice1] * go1/0 This is actually my first version of Two Cube Calendar, and it's a bit messy (and that's why I rewrote it in go/0). It give the same solution of the cube configuration as go/0: cube1 = [0,1,2,6,7,8] cube2 = [0,1,2,3,4,5] Trick: We will use 6 as both 6 and 9. * go2/0: The month calendar with 3 dice and English month names (abbreviations), i.e. "jan","feb","mar",..."dec". We use the same trick as for the days, i.e. inversion of some characters, here u and n (u->n) d and p (d->p) Here is one solution: Dice: acfnsv blmnpt egjory jan: j(3) a(1) n(2) % the number in parenthesis is the die number feb: f(1) e(3) b(2) mar: m(2) a(1) r(3) apr: a(1) p(2) r(3) may: m(2) a(1) y(3) jun: j(3) n(1) n(2) % note: here we use 'n' as 'u' jul: j(3) n(1) l(2) aug: a(1) n(2) g(3) sep: s(1) e(3) p(2) oct: o(3) c(1) t(2) nov: n(2) o(3) v(1) dec: p(2) e(3) c(1) % 'p' as 'd' Some alterative solutions (remove the comment for "% fail" to generate all solutions). aelntv bcnrsy fgjmop aelntv bgjmop cfnrsy abcnsv elnrty fgjmop acfnsv blmnpt egjory * go3/0: A variant with Swedish calendar names (abbreviations), i.e. using "maj" and "okt", otherwise the same names. (It's the same for Danish). One solution: Dice: acfnos blmnpt egjkrv jan: j(3) a(1) n(2) feb: f(1) e(3) b(2) mar: m(2) a(1) r(3) apr: a(1) p(2) r(3) maj: m(2) a(1) j(3) jun: j(3) n(1) n(2) jul: j(3) n(1) l(2) aug: a(1) n(2) g(3) sep: s(1) e(3) p(2) okt: o(1) k(3) t(2) nov: n(2) o(1) v(3) dec: p(2) e(3) c(1) * go4: Norwegian month abbreviations with these differences (compared to English): may -> mai oct -> okt dec -> des One solution: Dice: abklns einrtv fgjmop jan: j(3) a(1) n(2) feb: f(3) e(2) b(1) mar: m(3) a(1) r(2) apr: a(1) p(3) r(2) mai: m(3) a(1) i(2) jun: j(3) n(1) n(2) jul: j(3) n(2) l(1) aug: a(1) n(2) g(3) sep: s(1) e(2) p(3) okt: o(3) k(1) t(2) nov: n(1) o(3) v(2) des: p(3) e(2) s(1) This Picat model was created by Hakan Kjellerstrand, hakank@gmail.com See also my Picat page: http://www.hakank.org/picat/ */ import util. import sat. % Generally much faster than CP (except for go/0.) % import cp. % CP is faster for go/0. main => go. % % Solving the Two Cube Calendar. % % This version is actually (much) faster using CP than SAT. % CP: 0.0s % SAT: 0.135s % However, for the other problems SAT is _much_ faster than CP. % go ?=> nolog, N = 6, % The days to cover % It's perhaps cheating to include 0 ([0,0]) to enforce two 0's % Note: using 1..31 (instead of 0..31) is not a problem for CP, % but it's much slower for SAT. Days = [ [I div 10, I mod 10] : I in 0..31], C1 = new_list(N), C1 :: 0..9, C2 = new_list(N), C2 :: 0..9, % "One cube has the sides 1,2 and the other 3,4,5." % cube1: element(_,C1,1), element(_,C1,2), % cube2: element(_,C2,3), element(_,C2,4), element(_,C2,5), % symmetry breaking etc all_distinct(C1), all_distinct(C2), increasing(C1), increasing(C2), % lex_lt(C2,C1), % slower foreach([D1,DD2] in Days) % Use 6 as 9 and 6 ("The Trick") if DD2 == 9 then D2 = 6 else D2 = DD2 end, ( (member(D1,C1), member(D2,C2)) ; (member(D2,C1), member(D1,C2)) ) end, % solve Vars = C1 ++ C2, solve([ffd,split],Vars), println(cube1=C1), println(cube2=C2), % How to reach all combinations of 00..31 foreach([D1,DD2] in Days) printf("%d%d: ",D1,DD2), if DD2 == 9 then D2 = 6 else D2 = DD2 end, ( (nth(I1,C1,D1), nth(I2,C2,D2),X=[D1,from,dice1,D2,from,dice2]) ; (nth(I2,C1,D2), nth(I1,C2,D1),X=[D1,from,dice2,D2,from,dice1]) ), println(X) end, nl, % fail, nl. go => true. % % This was the original version of Two Cube Calender, generating two dice for the days 1..31 % but it's a bit messy. go/0 is a lot simpler. % go1 ?=> nolog, N = 6, M = 31, C1 = new_list(N), C1 :: 0..9, C2 = new_list(N), C2 :: 0..9, A = new_array(M,3), A :: 1..N, % "One cube has the sides 1,2 and the other 3,4,5." % cube1: element(_,C1,1), element(_,C1,2), % cube2: element(_,C2,3), element(_,C2,4), element(_,C2,5), Tmp = [], foreach(I in 1..M) T = I div 10, D = I mod 10, element(J,C1,V1), element(K,C2,V2), A[I,1] #= J, A[I,2] #= K, if I mod 10 != 9 then (V1 #= T #/\ V2 #= D #/\ A[I,3] #= 1) #\/ (V2 #= T #/\ V1 #= D #/\ A[I,3] #= 2) else % 6 as 9 (V2 #= 6 #/\ V1 #= T #/\ A[I,3] #= 3) #\/ (V1 #= 6 #/\ V2 #= T #/\ A[I,3] #= 4) end, Tmp := Tmp ++ [J,K,V1,V2] end, % symmetry breaking etc all_different(C1), all_different(C2), increasing(C1), increasing(C2), % solve Vars = Tmp ++ A.vars() ++ C1 ++ C2, solve([split],Vars), println(cube1=C1), println(cube2=C2), foreach(I in 1..M) Type = A[I,3], if Type = 1 then Num = [C1[A[I,1]],C2[A[I,2]]] elseif Type = 2 then Num = [C2[A[I,2]],C1[A[I,1]]] elseif Type = 3 then Num = [9,C1[A[I,1]]] else Num = [C2[A[I,2]],9] end, Num := Num[1].to_string() ++ Num[2].to_string(), printf("I: %2d %w |[%d,%d] type:%d\n", I, Num, A[I,1], A[I,2], A[I,3]) end, nl, % fail, nl. go1 => true. % % Also, create the labeling on three dice for the month names. % jan % feb % mar % apr % may % jun % jul % aug % sep % oct % nov % dec % % https://en.wikipedia.org/wiki/Two-cube_calendar % """ % A variation with three cubes providing English abbreviations for the twelve months is discussed % in a Scientific American column in December 1977.[6] One solution of this variation allows displaying % the first three letters of any month and relies on the fact that lower-case letters % u and n % and also % p and d % are inverses of each other. % [source: Martin Gardner (1985). The Magic Numbers of Dr. Matrix.] % """ % go2 ?=> nolog, Months = ["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"], make_month_calendar(Months,eng), nl. % % Swedish calendar month abbreviations. % % Create the labeling on three dice for the month names (abbreviation), % which has "okt" and "maj" instead of "oct" and "may". % % jan % feb % mar % apr % maj <- % jun % jul % aug % sep % okt <- % nov % dec % % Same inversions though % u->n % d->p % go3 ?=> nolog, % Swedish month abbreviations. Months = ["jan","feb","mar","apr","maj","jun","jul","aug","sep","okt","nov","dec"], make_month_calendar(Months,swe). % % Norwegian month names (according to https://en.wikibooks.org/wiki/Norwegian/Months_and_Weekdays) % jan % feb % mar % apr % mai <- % jun % jul % aug % sep % okt <- % nov % des <- % go4 ?=> nolog, Months = ["jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des"], make_month_calendar(Months,nor). % % make_month_calendar(Months, Language) % % Months: Month names (abbreviations) % Language: The language (swe|eng), % used for some symmetry breaking optimization % make_month_calendar(Months, Language) => nolog, % There are 19 different characters which don't make room on % 3 dice = 3*6 = 18 sides Chars = Months.flatten.sort_remove_dups, println(all_chars=Chars), println(len=Chars.len), % So we will fix this by the same inversion trick as for den calendar days: % u -> n % p -> d Chars := Chars.delete('u').delete('d'), % remove u (-> n) and d (-> p) CharsLen = Chars.len, println(Chars=CharsLen), % Create three dice (integer representation) X = new_array(3,6), X :: 1..CharsLen, Ds = [], foreach(M in 1..12) D = new_list(3), % the three dice (in some order) D :: 1..3, all_different(D), foreach(I in 1..3) Char = check_char(Months[M,I]), % convert to (invertable) character nth(J,Chars,Char), % convert character Char to number J matrix_element(X,D[I],_,J), % ensure that die X[D[I]] has this character (J) Ds := Ds ++ D end end, % Symmetry breaking foreach(I in 1..3) if Language == eng then all_different(X[I]) end, increasing(X[I]) end, % X[1,1] #= 1, % slower X[1,1] #< X[2,1], X[2,1] #< X[3,1], % lex2(X), % slower Vars = X.vars ++ Ds, println(solve), solve($[ffd], Vars), % foreach(D in X) println(D) end, println("\nDice:"), Dice = new_array(3,6), foreach(I in 1..3) foreach(J in 1..6) nth(X[I,J],Chars,CC), Dice[I,J] := CC end, Dice[I] := Dice[I] end, foreach(D in Dice) println(D.to_list) end, nl, % Check all months foreach(Mon in Months) printf("%w: ", Mon), [C1,C2,C3] = Mon, % Generate possible chars (with inversions) CC1 = check_char(C1), CC2 = check_char(C2), CC3 = check_char(C3), Dss=[D1,D2,D3], permutation([1,2,3],Dss), member(CC1,Dice[D1].to_list), member(CC2,Dice[D2].to_list), member(CC3,Dice[D3].to_list), printf("%w(%d) %w(%d) %w(%d)\n",CC1,D1, CC2,D2, CC3,D3) end, nl, % fail, % uncomment for generating all solutions nl. % Port of MiniZinc's lex2.mzn % """ %-----------------------------------------------------------------------------% % Require adjacent rows and adjacent columns in the array 'x' to be % lexicographically ordered. Adjacent rows and adjacent columns may be equal. %-----------------------------------------------------------------------------% % """ % Note: This use lex_less/1. lex2(X) => Len = X[1].length, foreach(I in 2..X.length) lex_le([X[I-1,J] : J in 1..Len], [X[I,J] : J in 1..Len]) end. check_char(C) = CC => if C == 'u' then CC = 'n' elseif C == 'd' then CC = 'p' else CC = C end.