Constraint Satisfaction Problems (CSP) — Practice Tasks¶
All tasks must be solved in Go, Java, and Python. Each task ships with a precise spec and starter code in all three languages. Implement the CSP modeling and backtracking/propagation logic and validate against the evaluation criteria. Always test against a brute-force generate-and-test oracle on small inputs before trusting your solver, and independently re-validate every returned assignment against all constraints.
Beginner Tasks (4)¶
Task 1 — Consistency check¶
Problem. Implement consistent(var, value, assignment, neighbors) that returns whether assigning value to var violates any ≠ constraint with already-assigned neighbors.
Input / Output spec. - Inputs: a variable id, a candidate value, a map of assigned variables → values, and a neighbor list for var. - Output: true if no assigned neighbor already holds value, else false.
Constraints. - Up to 1000 variables; neighbor lists may be large. - Only assigned neighbors constrain the check.
Hint. Loop the neighbors; return false on the first assigned neighbor whose value equals value.
Starter — Go.
package main
import "fmt"
func consistent(v, val int, assign map[int]int, neighbors []int) bool {
// TODO: return false if any assigned neighbor holds val
return true
}
func main() {
assign := map[int]int{1: 0, 2: 1}
fmt.Println(consistent(0, 0, assign, []int{1, 2})) // false
fmt.Println(consistent(0, 2, assign, []int{1, 2})) // true
}
Starter — Java.
import java.util.*;
public class Task1 {
static boolean consistent(int v, int val, Map<Integer, Integer> assign, int[] neighbors) {
// TODO
return true;
}
public static void main(String[] args) {
Map<Integer, Integer> assign = Map.of(1, 0, 2, 1);
System.out.println(consistent(0, 0, assign, new int[]{1, 2})); // false
System.out.println(consistent(0, 2, assign, new int[]{1, 2})); // true
}
}
Starter — Python.
def consistent(v, val, assign, neighbors):
# TODO: return False if any assigned neighbor holds val
return True
if __name__ == "__main__":
assign = {1: 0, 2: 1}
print(consistent(0, 0, assign, [1, 2])) # False
print(consistent(0, 2, assign, [1, 2])) # True
Task 2 — MRV variable selection¶
Problem. Given current domains (a map from variable to list of legal values) and a set of assigned variables, return the unassigned variable with the fewest remaining values (break ties by smallest id).
Input / Output spec. - Inputs: domains, assigned set. - Output: the chosen variable id, or -1 if none unassigned.
Constraints. 1 ≤ vars ≤ 10⁴. Deterministic tie-break required.
Hint. Track best count and best id; iterate variables in increasing id order.
Starter — Go.
package main
import "fmt"
func selectMRV(domains map[int][]int, assigned map[int]bool, vars []int) int {
// TODO: smallest domain among unassigned; tie -> smallest id
return -1
}
func main() {
domains := map[int][]int{0: {1, 2, 3}, 1: {2}, 2: {1, 2}}
fmt.Println(selectMRV(domains, map[int]bool{}, []int{0, 1, 2})) // 1
}
Starter — Java.
import java.util.*;
public class Task2 {
static int selectMRV(Map<Integer, List<Integer>> domains, Set<Integer> assigned, int[] vars) {
// TODO
return -1;
}
public static void main(String[] args) {
Map<Integer, List<Integer>> domains = new HashMap<>();
domains.put(0, List.of(1, 2, 3));
domains.put(1, List.of(2));
domains.put(2, List.of(1, 2));
System.out.println(selectMRV(domains, new HashSet<>(), new int[]{0, 1, 2})); // 1
}
}
Starter — Python.
def select_mrv(domains, assigned, variables):
# TODO: smallest domain among unassigned; tie -> smallest id
return -1
if __name__ == "__main__":
domains = {0: [1, 2, 3], 1: [2], 2: [1, 2]}
print(select_mrv(domains, set(), [0, 1, 2])) # 1
Task 3 — Forward checking step¶
Problem. After assigning var = value, prune value from each unassigned neighbor's domain. Return the list of neighbors whose domain became empty (wipeouts). Do not mutate domains of assigned neighbors.
Input / Output spec. - Inputs: var, value, domains, neighbors, assigned set. - Output: list of wiped-out neighbor ids (empty list if none).
Constraints. Domains are lists/sets of ints. Mutation in place is allowed.
Hint. For each unassigned neighbor, remove value; if its domain is now empty, record it.
Starter — Go.
package main
import "fmt"
func forwardCheck(v, val int, domains map[int][]int, neighbors []int, assigned map[int]bool) []int {
// TODO: prune val from unassigned neighbors; collect wipeouts
return nil
}
func main() {
domains := map[int][]int{1: {0}, 2: {0, 1}}
fmt.Println(forwardCheck(0, 0, domains, []int{1, 2}, map[int]bool{})) // [1]
}
Starter — Java.
import java.util.*;
public class Task3 {
static List<Integer> forwardCheck(int v, int val, Map<Integer, List<Integer>> domains,
int[] neighbors, Set<Integer> assigned) {
// TODO
return new ArrayList<>();
}
public static void main(String[] args) {
Map<Integer, List<Integer>> domains = new HashMap<>();
domains.put(1, new ArrayList<>(List.of(0)));
domains.put(2, new ArrayList<>(List.of(0, 1)));
System.out.println(forwardCheck(0, 0, domains, new int[]{1, 2}, new HashSet<>())); // [1]
}
}
Starter — Python.
def forward_check(v, val, domains, neighbors, assigned):
# TODO: prune val from unassigned neighbors; collect wipeouts
return []
if __name__ == "__main__":
domains = {1: [0], 2: [0, 1]}
print(forward_check(0, 0, domains, [1, 2], set())) # [1]
Task 4 — Map coloring decision (2 colors)¶
Problem. Given an undirected graph, decide whether it is 2-colorable (i.e. bipartite) using a CSP backtracking solver. Return true/false.
Input / Output spec. - Inputs: n (vertices), edges (list of pairs). - Output: boolean.
Constraints. 1 ≤ n ≤ 1000. Graph may be disconnected.
Hint. Domain {0, 1}; backtrack with ≠ constraints, or recognize this is just bipartiteness.
Starter — Go.
package main
import "fmt"
func twoColorable(n int, edges [][2]int) bool {
// TODO: backtracking CSP with 2 colors
return false
}
func main() {
fmt.Println(twoColorable(3, [][2]int{{0, 1}, {1, 2}})) // true
fmt.Println(twoColorable(3, [][2]int{{0, 1}, {1, 2}, {2, 0}})) // false
}
Starter — Java.
import java.util.*;
public class Task4 {
static boolean twoColorable(int n, int[][] edges) {
// TODO
return false;
}
public static void main(String[] args) {
System.out.println(twoColorable(3, new int[][]{{0, 1}, {1, 2}})); // true
System.out.println(twoColorable(3, new int[][]{{0, 1}, {1, 2}, {2, 0}})); // false
}
}
Starter — Python.
def two_colorable(n, edges):
# TODO: backtracking CSP with 2 colors
return False
if __name__ == "__main__":
print(two_colorable(3, [(0, 1), (1, 2)])) # True
print(two_colorable(3, [(0, 1), (1, 2), (2, 0)])) # False
Intermediate Tasks (3)¶
Task 5 — Graph coloring with MRV + forward checking¶
Problem. Return a proper k-coloring of an undirected graph using backtracking with MRV variable selection and forward checking, or report that none exists.
Input / Output spec. - Inputs: n, k, edges. - Output: a length-n array of colors 0..k-1, or null/None/nil if infeasible.
Constraints. 1 ≤ n ≤ 200, 1 ≤ k ≤ 8. Must use MRV and restore domains on backtrack.
Hint. Use a bitset/boolean array per vertex for the domain; wipeout = no bit set.
Starter — Go.
package main
import "fmt"
func kColor(n, k int, edges [][2]int) ([]int, bool) {
// TODO: MRV + forward checking backtracking
return nil, false
}
func main() {
col, ok := kColor(4, 3, [][2]int{{0, 1}, {1, 2}, {2, 0}, {2, 3}})
fmt.Println(col, ok)
}
Starter — Java.
import java.util.*;
public class Task5 {
static int[] kColor(int n, int k, int[][] edges) {
// TODO: MRV + forward checking; return null if infeasible
return null;
}
public static void main(String[] args) {
System.out.println(Arrays.toString(
kColor(4, 3, new int[][]{{0, 1}, {1, 2}, {2, 0}, {2, 3}})));
}
}
Starter — Python.
def k_color(n, k, edges):
# TODO: MRV + forward checking backtracking
return None
if __name__ == "__main__":
print(k_color(4, 3, [(0, 1), (1, 2), (2, 0), (2, 3)]))
Task 6 — AC-3 with a custom constraint¶
Problem. Implement AC-3 for a binary CSP defined by a predicate allowed(x, vx, y, vy). Return reduced domains, or signal inconsistency on a wipeout. Test on the constraint vx < vy over a small chain.
Input / Output spec. - Inputs: domains (map var → values), neighbors (map var → list), allowed predicate. - Output: reduced domains and a boolean consistency flag.
Constraints. 1 ≤ vars ≤ 500, |domain| ≤ 50. Must re-enqueue arcs correctly after pruning.
Hint. Queue all arcs; on a successful revise, re-enqueue (z, x) for neighbors z ≠ y.
Starter — Go.
package main
import "fmt"
func ac3(domains map[int][]int, neighbors map[int][]int,
allowed func(x, vx, y, vy int) bool) bool {
// TODO: arc-consistency fixpoint; false on wipeout
return true
}
func main() {
domains := map[int][]int{0: {1, 2, 3}, 1: {1, 2, 3}}
neighbors := map[int][]int{0: {1}, 1: {0}}
allowed := func(x, vx, y, vy int) bool {
if x < y {
return vx < vy
}
return vy < vx
}
fmt.Println(ac3(domains, neighbors, allowed), domains)
}
Starter — Java.
import java.util.*;
public class Task6 {
interface Quad { boolean ok(int x, int vx, int y, int vy); }
static boolean ac3(Map<Integer, List<Integer>> domains,
Map<Integer, List<Integer>> neighbors, Quad allowed) {
// TODO
return true;
}
public static void main(String[] args) {
Map<Integer, List<Integer>> domains = new HashMap<>();
domains.put(0, new ArrayList<>(List.of(1, 2, 3)));
domains.put(1, new ArrayList<>(List.of(1, 2, 3)));
Map<Integer, List<Integer>> neighbors = Map.of(0, List.of(1), 1, List.of(0));
Quad allowed = (x, vx, y, vy) -> x < y ? vx < vy : vy < vx;
System.out.println(ac3(domains, neighbors, allowed) + " " + domains);
}
}
Starter — Python.
from collections import deque
def ac3(domains, neighbors, allowed):
# TODO: arc-consistency fixpoint; False on wipeout
return True
if __name__ == "__main__":
domains = {0: [1, 2, 3], 1: [1, 2, 3]}
neighbors = {0: [1], 1: [0]}
allowed = lambda x, vx, y, vy: (vx < vy) if x < y else (vy < vx)
print(ac3(domains, neighbors, allowed), domains)
Task 7 — Cryptarithmetic verifier and solver¶
Problem. Given three words a + b = c, find a digit assignment to distinct letters (no leading zeros) making the equation hold, or report none. Return the mapping.
Input / Output spec. - Inputs: three uppercase words. - Output: map letter → digit, or none.
Constraints. Total distinct letters ≤ 10. Words length ≤ 8.
Hint. Collect distinct letters and leading letters; backtrack over digit assignment; verify the sum at a complete assignment.
Starter — Go.
package main
import "fmt"
func solveCrypto(a, b, c string) (map[byte]int, bool) {
// TODO: assign distinct digits, no leading zeros, check a+b==c
return nil, false
}
func main() {
sol, ok := solveCrypto("SEND", "MORE", "MONEY")
fmt.Println(sol, ok)
}
Starter — Java.
import java.util.*;
public class Task7 {
static Map<Character, Integer> solveCrypto(String a, String b, String c) {
// TODO
return null;
}
public static void main(String[] args) {
System.out.println(solveCrypto("SEND", "MORE", "MONEY"));
}
}
Starter — Python.
def solve_crypto(a, b, c):
# TODO: distinct digits, no leading zeros, a + b == c
return None
if __name__ == "__main__":
print(solve_crypto("SEND", "MORE", "MONEY"))
Advanced Tasks (3)¶
Task 8 — Generic CSP solver with MAC¶
Problem. Build a reusable solver taking variables, domains, neighbors, and a binary constraint predicate; solve with backtracking + MRV + MAC (AC-3 at each node). Demonstrate on an n=8 N-Queens CSP.
Input / Output spec. - Inputs: model components and a constraint callback. - Output: a satisfying assignment, or none.
Constraints. Must snapshot/restore domains per node; must not corrupt state across branches.
Hint. After assigning X=v, set domain[X]={v}, seed AC-3 with arcs (Y, X) for neighbors, and recurse on a domain copy.
Starter — Go.
package main
import "fmt"
func solveMAC(vars []int, domains map[int][]int, neighbors map[int][]int,
ok func(a, va, b, vb int) bool) (map[int]int, bool) {
// TODO: MRV + MAC backtracking
return nil, false
}
func main() {
n := 8
vars := make([]int, n)
domains := map[int][]int{}
neighbors := map[int][]int{}
for r := 0; r < n; r++ {
vars[r] = r
d := make([]int, n)
for c := 0; c < n; c++ {
d[c] = c
}
domains[r] = d
for r2 := 0; r2 < n; r2++ {
if r2 != r {
neighbors[r] = append(neighbors[r], r2)
}
}
}
ok := func(a, ca, b, cb int) bool {
dr := a - b
if dr < 0 {
dr = -dr
}
dc := ca - cb
if dc < 0 {
dc = -dc
}
return ca != cb && dr != dc
}
sol, found := solveMAC(vars, domains, neighbors, ok)
fmt.Println(sol, found)
}
Starter — Java.
import java.util.*;
public class Task8 {
interface Quad { boolean ok(int a, int va, int b, int vb); }
static Map<Integer, Integer> solveMAC(int[] vars, Map<Integer, List<Integer>> domains,
Map<Integer, List<Integer>> neighbors, Quad c) {
// TODO: MRV + MAC
return null;
}
public static void main(String[] args) {
int n = 8;
int[] vars = new int[n];
Map<Integer, List<Integer>> domains = new HashMap<>(), neighbors = new HashMap<>();
for (int r = 0; r < n; r++) {
vars[r] = r;
List<Integer> d = new ArrayList<>();
for (int cc = 0; cc < n; cc++) d.add(cc);
domains.put(r, d);
List<Integer> nb = new ArrayList<>();
for (int r2 = 0; r2 < n; r2++) if (r2 != r) nb.add(r2);
neighbors.put(r, nb);
}
Quad c = (a, ca, b, cb) -> ca != cb && Math.abs(a - b) != Math.abs(ca - cb);
System.out.println(solveMAC(vars, domains, neighbors, c));
}
}
Starter — Python.
def solve_mac(variables, domains, neighbors, ok):
# TODO: MRV + MAC (AC-3 at each node)
return None
if __name__ == "__main__":
n = 8
variables = list(range(n))
domains = {r: list(range(n)) for r in range(n)}
neighbors = {r: [r2 for r2 in range(n) if r2 != r] for r in range(n)}
ok = lambda a, ca, b, cb: ca != cb and abs(a - b) != abs(ca - cb)
print(solve_mac(variables, domains, neighbors, ok))
Task 9 — Sudoku as a CSP¶
Problem. Solve a 9×9 Sudoku modeled as a CSP: 81 cell variables, domains 1..9, AllDifferent over each row, column, and 3×3 box. Use MRV + forward checking (or MAC). Return the completed grid.
Input / Output spec. - Input: 9×9 grid with 0 for blanks. - Output: solved grid, or report unsolvable.
Constraints. Standard Sudoku rules. Should solve typical boards quickly via propagation.
Hint. Precompute each cell's "peers" (the up-to-20 cells sharing a unit) as its neighbors; the constraint is ≠ among peers.
Starter — Go.
package main
import "fmt"
func solveSudoku(grid [9][9]int) ([9][9]int, bool) {
// TODO: CSP with AllDifferent (as != among peers), MRV + forward checking
return grid, false
}
func main() {
var g [9][9]int
// 0 = blank; fill a puzzle here
g[0] = [9]int{5, 3, 0, 0, 7, 0, 0, 0, 0}
out, ok := solveSudoku(g)
fmt.Println(out, ok)
}
Starter — Java.
public class Task9 {
static boolean solveSudoku(int[][] grid) {
// TODO: CSP MRV + forward checking; mutate grid in place
return false;
}
public static void main(String[] args) {
int[][] g = new int[9][9];
g[0] = new int[]{5, 3, 0, 0, 7, 0, 0, 0, 0};
System.out.println(solveSudoku(g));
}
}
Starter — Python.
def solve_sudoku(grid):
# TODO: CSP with AllDifferent peers, MRV + forward checking
return False
if __name__ == "__main__":
g = [[0] * 9 for _ in range(9)]
g[0] = [5, 3, 0, 0, 7, 0, 0, 0, 0]
print(solve_sudoku(g))
Task 10 — Exam scheduling CSP¶
Problem. Schedule m exams into s time slots so that no student has two exams in the same slot. Input: a list of student enrollments (each student lists the exams they take). Two exams sharing any student conflict (a ≠ constraint). Return a slot assignment per exam, or report infeasible.
Input / Output spec. - Inputs: m (exams), s (slots), enrollments (list of exam-id lists per student). - Output: array of slot per exam, or none.
Constraints. 1 ≤ m ≤ 100, 1 ≤ s ≤ 12. This is graph coloring with s colors on the conflict graph.
Hint. Build the conflict graph (edge between exams sharing a student), then run your k-coloring solver with k = s. Use MRV + forward checking.
Starter — Go.
package main
import "fmt"
func scheduleExams(m, s int, enrollments [][]int) ([]int, bool) {
// TODO: build conflict graph, color with s colors
return nil, false
}
func main() {
enroll := [][]int{{0, 1}, {1, 2}, {0, 2}}
sol, ok := scheduleExams(3, 3, enroll)
fmt.Println(sol, ok)
}
Starter — Java.
import java.util.*;
public class Task10 {
static int[] scheduleExams(int m, int s, List<List<Integer>> enrollments) {
// TODO: conflict graph + s-coloring
return null;
}
public static void main(String[] args) {
List<List<Integer>> enroll = List.of(
List.of(0, 1), List.of(1, 2), List.of(0, 2));
System.out.println(Arrays.toString(scheduleExams(3, 3, enroll)));
}
}
Starter — Python.
def schedule_exams(m, s, enrollments):
# TODO: build conflict graph, color with s colors (MRV + forward checking)
return None
if __name__ == "__main__":
enroll = [[0, 1], [1, 2], [0, 2]]
print(schedule_exams(3, 3, enroll))
Evaluation Criteria¶
| Criterion | What to check |
|---|---|
| Correctness | Returned assignment satisfies all constraints (re-validate independently). |
| Completeness | Returns a solution when one exists; returns failure cleanly when none does. |
| Heuristics | MRV (and degree tie-break) actually used where required. |
| Propagation | Forward checking / AC-3 prunes domains and detects wipeouts. |
| State hygiene | Domains and assignments fully restored on backtrack — no cross-branch corruption. |
| Edge cases | Empty domains, isolated variables, unsolvable instances, multiple solutions. |
| Testing | Compared against a brute-force oracle on small inputs. |
Test every solution in Go, Java, and Python. For each task, write at least one solvable case, one unsolvable case, and (where relevant) one case with multiple valid outputs.
In this topic
- interview
- tasks