Skip to main content

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}