30_magic_square_checker/30_magic_square_checker.rs
1//! # Example: Magic Square Checker
2//!
3//! Run: cargo run --example 30_magic_square_checker
4//!
5//! ## Problem
6//! A *magic square* is an n×n grid of numbers whose every row, every column, and
7//! both main diagonals add up to the same total (the *magic constant*). Given a
8//! square matrix, decide whether it is magic.
9//!
10//! ## Math idea
11//! Compute the target sum from the first row, then verify that all n rows, all n
12//! columns, and the two diagonals share that sum.
13//!
14//! ## Tensor representation
15//! The square is a 2-D `Tensor` of shape `[n, n]`. Cells are read with
16//! `Tensor::get(&[row, col])`, which returns `Option<f64>` (`None` if out of bounds).
17//!
18//! ## What this demonstrates
19//! - 2-D `Tensor` construction with `Tensor::new(data, &[n, n])`;
20//! - shape inspection via `Tensor::shape`;
21//! - element access via `Tensor::get`;
22//! - row / column / diagonal traversal with ordinary Rust iterators.
23//!
24//! ## Expected output
25//! ```text
26//! Lo Shu is magic: true (magic sum = 15)
27//! Sequence is magic: false
28//! Magic square checker: OK
29//! ```
30
31use matten::Tensor;
32
33/// Returns `Some(magic_sum)` if `m` is an n×n magic square, otherwise `None`.
34///
35/// The cell values here are small integers, so the row/column/diagonal sums are
36/// exact in `f64` and can be compared directly.
37fn magic_sum(m: &Tensor) -> Option<f64> {
38 let shape = m.shape();
39 if shape.len() != 2 || shape[0] != shape[1] || shape[0] == 0 {
40 return None;
41 }
42 let n = shape[0];
43 let at = |i: usize, j: usize| m.get(&[i, j]).expect("index in bounds");
44
45 // Target is the sum of the first row.
46 let target: f64 = (0..n).map(|j| at(0, j)).sum();
47
48 // Every row must match.
49 for i in 0..n {
50 let row: f64 = (0..n).map(|j| at(i, j)).sum();
51 if row != target {
52 return None;
53 }
54 }
55 // Every column must match.
56 for j in 0..n {
57 let col: f64 = (0..n).map(|i| at(i, j)).sum();
58 if col != target {
59 return None;
60 }
61 }
62 // Both diagonals must match.
63 let main_diag: f64 = (0..n).map(|i| at(i, i)).sum();
64 let anti_diag: f64 = (0..n).map(|i| at(i, n - 1 - i)).sum();
65 if main_diag != target || anti_diag != target {
66 return None;
67 }
68
69 Some(target)
70}
71
72fn main() {
73 // Lo Shu — the classic 3×3 magic square (magic constant 15).
74 let lo_shu = Tensor::new(vec![8.0, 1.0, 6.0, 3.0, 5.0, 7.0, 4.0, 9.0, 2.0], &[3, 3]);
75 // 1..9 in order: rows match but columns and diagonals do not.
76 let sequence = Tensor::new(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], &[3, 3]);
77
78 match magic_sum(&lo_shu) {
79 Some(s) => println!("Lo Shu is magic: true (magic sum = {s})"),
80 None => println!("Lo Shu is magic: false"),
81 }
82 match magic_sum(&sequence) {
83 Some(s) => println!("Sequence is magic: true (magic sum = {s})"),
84 None => println!("Sequence is magic: false"),
85 }
86
87 assert_eq!(magic_sum(&lo_shu), Some(15.0));
88 assert_eq!(magic_sum(&sequence), None);
89 println!("Magic square checker: OK");
90}