diff --git a/src/source-3-non-det-examples/__tests__/fast-multiple-dwelling.test.js b/src/source-3-non-det-examples/__tests__/fast-multiple-dwelling.test.js new file mode 100644 index 0000000..c9d5caf --- /dev/null +++ b/src/source-3-non-det-examples/__tests__/fast-multiple-dwelling.test.js @@ -0,0 +1,11 @@ +// chapter=3 variant=non-det executionMethod=interpreter +const md = multiple_dwelling(); +let res = ''; +for (let i = 0; i < length(md); i=i+1) { + const person_floor = list_ref(md, i); + const person = head(person_floor); + const floor = stringify(head(tail(person_floor))); + res = res + person + floor + ','; +} +res; +// 'baker3,cooper2,fletcher4,miller5,smith1,' \ No newline at end of file diff --git a/src/source-3-non-det-examples/__tests__/liars.test.js b/src/source-3-non-det-examples/__tests__/liars.test.js new file mode 100644 index 0000000..73a85ef --- /dev/null +++ b/src/source-3-non-det-examples/__tests__/liars.test.js @@ -0,0 +1,12 @@ +// chapter=3 variant=non-det executionMethod=interpreter +const liars = list(list('betty', betty), list('ethel', ethel), list('joan', joan), +list('kitty', kitty), list('mary', mary)); +let res = ''; +for (let i = 0; i < length(liars); i=i+1) { + const person_rank = list_ref(liars, i); + const person = head(person_rank); + const rank = stringify(head(tail(person_rank))); + res = res + person + rank + ','; +} +res; +// 'betty3,ethel5,joan2,kitty1,mary4,' \ No newline at end of file diff --git a/src/source-3-non-det-examples/__tests__/queens.test.js b/src/source-3-non-det-examples/__tests__/queens.test.js new file mode 100644 index 0000000..cc3462c --- /dev/null +++ b/src/source-3-non-det-examples/__tests__/queens.test.js @@ -0,0 +1,11 @@ +// chapter=3 variant=non-det executionMethod=interpreter +const queens = queens_fast(8, 8); +let res = ''; +for (let i = 0; i < length(queens); i=i+1) { + const position = list_ref(queens, i); + const x = stringify(head(position)); + const y = stringify(tail(position)); + res = res + '(' + x + ',' + y + '),'; +} +res; +// '(4,8),(2,7),(7,6),(3,5),(6,4),(8,3),(5,2),(1,1),' \ No newline at end of file diff --git a/src/source-3-non-det-examples/fast-multiple-dwelling.js b/src/source-3-non-det-examples/fast-multiple-dwelling.js new file mode 100644 index 0000000..6b225dc --- /dev/null +++ b/src/source-3-non-det-examples/fast-multiple-dwelling.js @@ -0,0 +1,40 @@ +/* SICP JS Exercise 4.31 + + This file contains a fast(er) solution to the multiple dwelling puzzle. + The key idea is that we perform every 'require' check immediately once + the information it needs is available. This minimises wasteful backtracking. +*/ + +function distinct(items) { + return is_null(items) + ? true + : is_null(tail(items)) + ? true + : is_null(member(head(items), tail(items))) + ? distinct(tail(items)) + : false; + } + + function multiple_dwelling() { + const baker = amb(1, 2, 3, 4, 5); + require(!(baker === 5)); + const cooper = amb(1, 2, 3, 4, 5); + require(!(cooper === 1)); + const fletcher = amb(1, 2, 3, 4, 5); + require(!(fletcher === 5)); + require(!(fletcher === 1)); + require(!(math_abs(fletcher - cooper) === 1)); + const miller = amb(1, 2, 3, 4, 5); + require(miller > cooper); + const smith = amb(1, 2, 3, 4, 5); + require(!(math_abs(smith - fletcher) === 1)); + require(distinct(list(baker, cooper, fletcher, miller, smith))); + + + return list(list("baker", baker), + list("cooper", cooper), + list("fletcher", fletcher), + list("miller", miller), + list("smith", smith)); + } + // multiple_dwelling(); diff --git a/src/source-3-non-det-examples/liars.js b/src/source-3-non-det-examples/liars.js new file mode 100644 index 0000000..ca75627 --- /dev/null +++ b/src/source-3-non-det-examples/liars.js @@ -0,0 +1,31 @@ +/* + SICP JS Exercise 4.33 + + This file contains a solution to the Liars' puzzle +*/ + +function distinct(items) { + return is_null(items) + ? true + : is_null(tail(items)) + ? true + : is_null(member(head(items), tail(items))) + ? distinct(tail(items)) + : false; +} + +const kitty = amb(1, 2, 3, 4, 5); +const mary = amb(1, 2, 3, 4, 5); +require(amb(kitty === 2, mary === 4)); +const betty = amb(1, 2, 3, 4, 5); +require(amb(kitty === 2, betty === 3)); +require(amb(mary === 4, betty === 1)); +const ethel = amb(1, 2, 3, 4, 5); +const joan = amb(1, 2, 3, 4, 5); +require(amb(ethel === 1, joan === 2)); +require(amb(joan === 3, ethel === 5)); + +require(distinct(list(betty, ethel, joan, kitty, mary))); + +// list(list('betty', betty), list('ethel', ethel), list('joan', joan), +// list('kitty', kitty), list('mary', mary)); \ No newline at end of file diff --git a/src/source-3-non-det-examples/queens.js b/src/source-3-non-det-examples/queens.js new file mode 100644 index 0000000..5c75b2f --- /dev/null +++ b/src/source-3-non-det-examples/queens.js @@ -0,0 +1,164 @@ +/* + SICP JS Exercise 4.35 + + This file contains two solutions for the n-queens puzzle using non-determinism. + Each of them makes use of the generate-and-test paradigm. + + The first (queens_slow) uses non-determinism for generating both the row and column + for each position. It does so in an optimized manner, testing the corresponding condition + as soon as the choice is generated, thereby reducing the search space. However, this is not + enough to yield a quick solution when N = 8. + This solution also gives repeated solutions (i.e different permutations that have all the same positions) upon backtracking. + + The second (queens_fast) uses non-determinism in picking only the row and not the column of each position, with the + columns being fixed. This further reduces the search space, yielding a quick solution when N = 8. +*/ + +const N = 8; // the number of queens and the size of the board +const empty_positions = null; + +/******************************************************************************/ +/* Slow version which uses non-determinism for both the columns and rows, */ +/* perform testing of a required condition as soon as the choice is generated */ +/******************************************************************************/ + +// const result_queens_slow = queens_slow(N, N); +// pretty_print(result_queens_slow, N); +// generate more solutions by entering 'try again' in the REPL + +function queens_slow(board_size, num_queens) { + require(num_queens <= board_size); + const possible_positions = enum_list(1, board_size); + + const result = accumulate( + (_, so_far) => { + const row = an_element_of(possible_positions); + require(is_row_safe(row, so_far)); + + const col = an_element_of(possible_positions); + require(is_col_safe(col, so_far)); + + const new_position = pair(row, col); + require(is_diagonal_safe(new_position, so_far)); + + const new_positions = pair(new_position, so_far); + return new_positions; + }, + empty_positions, + enum_list(1, num_queens) + ); + + return result; +} + +function is_row_safe(new_row, queen_positions) { + const rows = map(position => head(position), queen_positions); + return member(new_row, rows) === null; +} + +function is_col_safe(new_col, queen_positions) { + const cols = map(position => tail(position), queen_positions); + return member(new_col, cols) === null; +} + +function is_diagonal_safe(new_position, queen_positions) { + const new_sum = head(new_position) + tail(new_position); + const new_sub = head(new_position) - tail(new_position); + const sums = map( + position => head(position) + tail(position), + queen_positions + ); + + return ( + member(new_sum, sums) === null && + member( + new_sub, + map(position => head(position) - tail(position), queen_positions) + ) === null + ); +} + +/******************************************************************************/ +/* Fast version which uses non-determinism only for the rows, */ +/* with the columns being hardcoded. */ +/******************************************************************************/ + +// const result_queens_fast = queens_fast(N, N); +// pretty_print(result_queens_fast, N); +// generate more solutions by entering 'try again' in the REPL + +function queens_fast(board_size, num_queens) { + require(num_queens <= board_size); + const possible_positions = enum_list(1, board_size); + + function queen_cols(k) { + if (k === 0) { + return empty_positions; + } else { + const so_far = queen_cols(k - 1); + + const new_row = an_element_of(possible_positions); + const new_position = pair(new_row, k); + require(is_safe(new_position, so_far)); + + const new_positions = pair(new_position, so_far); + return new_positions; + } + } + return queen_cols(num_queens); +} + +function is_safe(new_position, positions) { + const new_row = head(new_position); + const new_col = tail(new_position); + + return accumulate( + (position, so_far) => { + const row = head(position); + const col = tail(position); + + return ( + so_far && + new_row - new_col !== row - col && + new_row + new_col !== row + col && + new_row !== row + ); + }, + true, + positions + ); +} + + +/* Pretty prints a solution to the n-queens puzzle */ +function pretty_print(result, board_size) { + function member_eq(v, xs) { + return is_null(xs) + ? null + : equal(v, head(xs)) + ? xs + : member_eq(v, tail(xs)); + } + const possible_positions = enum_list(1, board_size); + + let col_index_str = " "; + for_each(i => { + col_index_str = col_index_str + stringify(i) + " "; + }, possible_positions); + display(col_index_str); + + for_each(row => { + let row_str = stringify(row) + " "; + for_each(col => { + const position = pair(row, col); + const contains_position = member_eq(position, result) !== null; + if (contains_position) { + row_str = row_str + "Q "; + } else { + row_str = row_str + ". "; + } + }, possible_positions); + + display(row_str); + }, possible_positions); +}