You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust数独生成:回溯算法生成3X3区域含重复数字的错误棋盘

数独生成器3X3区域重复数字问题排查

我是Rust新手,将数独生成器作为首个项目开发,遵循《如何生成唯一解数独棋盘》算法,但生成的棋盘3X3区域内总是出现重复数字。怀疑is_safe函数或数字混排逻辑存在问题,恳请帮忙找出错误所在。

use std::{fmt::{Debug, Formatter, Error, Display}, ops::Deref};

use rand::Rng;
use termion::color;

const POSITIONS: [i32; 10] = [
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    0
];


#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Slot {
    pub val: i32
}

#[derive(Clone, Debug)]
pub struct SlotGrid {
    pub data: Vec<Vec<Slot>>
}

pub struct Board {
    data: SlotGrid,
    solved: SlotGrid,
    pub game_data: SlotGrid
}

impl Slot {
    pub fn new(val: i32) -> Self {
        if !POSITIONS.to_vec().contains(&val) {
            panic!("Invalid value");
        }

        Self { val: val }
    }
}

// TODO: custom size support
impl SlotGrid {
    pub fn new() -> Self {
        let d1: Vec<Vec<i32>> = vec![
            vec![1, 2, 3, 4, 5, 6, 7, 8, 9],
            vec![4, 5, 6, 7, 8, 9, 1, 2, 3],
            vec![7, 8, 9, 1, 2, 3, 4, 5, 6],
            vec![2, 3, 1, 5, 6, 4, 8, 9, 7],
            vec![5, 6, 4, 8, 9, 7, 2, 3, 1],
            vec![8, 9, 7, 2, 3, 1, 5, 6, 4],
            vec![3, 1, 2, 6, 4, 5, 9, 7, 8],
            vec![6, 4, 5, 9, 7, 8, 3, 1, 2],
            vec![9, 7, 8, 3, 1, 2, 6, 4, 5]
        ];

        let mut d2: Vec<Vec<Slot>> = Vec::new();

        for row in d1 {
            let mut row2: Vec<Slot> = Vec::new();
                
            for item in row {
                row2.push(Slot::new(item));
            }

            d2.push(row2);
        }

        Self { data: d2 }
    }

    pub fn set(&mut self, x: usize, y: usize, val: i32) {
        self.data[y][x].val = val;
    }

    pub fn get(&self, x: usize, y: usize) -> Slot {
        self.data[y][x]
    }

    fn swap_slots(&mut self, n1: i32, n2: i32) {
        for y in 0 .. 8 {
            for x in 0 .. 8 {
                if self.get(x, y).val == n1 {
                    self.set(x, y, n2);
                    continue;
                }
                
                if self.get(x, y).val == n2 {
                    self.set(x, y, n1);
                }
            }
        }   
    }

    fn swap_rows(&mut self, y1: usize, y2: usize) {
        let first: Vec<Slot> = self.data[y1].clone();
        let second: Vec<Slot> = self.data[y2].clone();

        self.data[y2] = first;
        self.data[y1] = second;
    }

    fn swap_cols(&mut self, x1: usize, x2: usize) {
        for y in 0 .. 8 {
            let first = self.data[y][x1];
            let second = self.data[y][x2];

            self.data[y][x1].val = second.val;
            self.data[y][x2].val = first.val;
        }
    }

    fn swap_3x3_rows(&mut self, y1: usize, y2: usize) {
        for i in 0 .. 2 {
            self.swap_rows(y1 * 3 + i, y2 * 3 + i);
        }
    }

    fn swap_3x3_cols(&mut self, x1: usize, x2:usize) {
        for i in 0 .. 2 {
            self.swap_cols(x1 * 3 + i, x2 * 3 + i);
        }
    }

    pub fn shuffle_rows(&mut self) {
        let mut rng = rand::thread_rng();
        let mut num: usize;
                    
        for i in 0 .. 8 {
            let rand = rng.gen::<usize>() % 3;
            num = i / 3;
            self.swap_rows(i, num * 3 + rand);
        }
    }

    pub fn shuffle_cols(&mut self) {
        let mut rng = rand::thread_rng();
        let mut num: usize;

        for i in 0 .. 0 {
            let rand = rng.gen::<usize>() % 3;
            num = i / 3;
            self.swap_cols(i, num * 3 + rand);
        }
    }

    pub fn shuffle_nums(&mut self) {
        let mut rng = rand::thread_rng();
        
        for i in 0 .. 8 {
            let num = rng.gen::<usize>() % 9;

            self.swap_slots(i, num.try_into().unwrap());
        }
    }

    pub fn shuffle_3x3_rows(&mut self) {
        let mut rng = rand::thread_rng();
        
        for i in 0 .. 2 {
            let num = rng.gen::<usize>() % 3;
            
            self.swap_3x3_rows(i, num);
        }
    }

    pub fn shuffle_3x3_cols(&mut self) {
        let mut rng = rand::thread_rng();
        
        for i in 0 .. 2 {
            let num = rng.gen::<usize>() % 3;

            self.swap_3x3_cols(i, num);
        }
    }
}

impl Board {
    pub fn new(cmplx: usize) -> Self {
        let mut data = SlotGrid::new();
        let mut rng = rand::thread_rng();

        data.shuffle_rows();
        data.shuffle_cols();
        data.shuffle_3x3_rows();
        data.shuffle_3x3_cols();

        let old_data = data.clone();
        let mut out: SlotGrid = data.clone(); 

        let mut i = 0;

        loop {
            // remove until usolvable and go back 1

            let x = rng.gen::<usize>() % 9;
            let y = rng.gen::<usize>() % 9;

            data.set(x, y, 0);
        
            if !back_track(&mut (data.clone()), 0, 0) && i >= cmplx {
                break;
            }
            
            i += 1;
            out = data.clone();
        }

        Self { data: out.clone(), solved: old_data, game_data: out }
    }

    pub fn is_solved(&self) -> bool {
        for i in 0 .. self.data.data.len() - 1 {
            for j in 0 .. self.data.data[i].len() - 1 {
                if self.data.data[i][j] != self.solved.data[i][j] {
                    return false;
                }
            }
        }

        true
    }
}

impl Display for Board {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        let mut s = String::new();

        let mut i = 10;
        for row in &self.data.data {
            i -= 1;
            
            s.push_str(color::Fg(color::Magenta).to_string().deref());
            s.push_str(i.to_string().deref());
            s.push_str(" | ");
            s.push_str(color::Fg(color::Reset).to_string().deref());
            
            let mut j = 0;
            for slot in row {
                j += 1;

                let val = if slot.val == 0 {
                    "_".to_string()
                } else {
                    slot.val.to_string()
                };
                
                s.push_str(colorify(j, 9 - i, val).deref());
                s.push(' ');
            }

            s.push_str("\n");
            print!("\n");
        }
        
        s.push_str(color::Fg(color::Red).to_string().deref());
        s.push_str("    A B C D E F G H I");
        s.push_str(color::Fg(color::Reset).to_string().deref());

        write!(f, "{}", s)
    }
}

fn colorify(x: usize, y: usize, s: String) -> String {
    let x2 = x - 1;

    let corners: String = if (0 .. 3).contains(&x2) || (6 .. 9).contains(&x2) {
        color::Fg(color::LightCyan).to_string()
    } else {
        color::Fg(color::Green).to_string()
    };

    let mut out = String::new();

    if (0 .. 3).contains(&y) {
        out.push_str(corners.deref());
    }
    else if (3 .. 6).contains(&y) {
        if (3 .. 6).contains(&x2) {
            out.push_str(color::Fg(color::LightCyan).to_string().deref());
        }
        else {
            out.push_str(color::Fg(color::Green).to_string().deref());
        }
    }
    else if (6 .. 9).contains(&y) {
        out.push_str(corners.deref());
    }

    out.push_str(s.deref());
    out.push_str(color::Fg(color::Reset).to_string().deref());

    out
}

fn back_track(data: &mut SlotGrid, mut x: usize, mut y: usize) -> bool {
    if x == 8 && y == 8 {
        return true;
    }

    if x == 8 {
        y += 1;
        x = 0;
    }

    if data.get(x, y).val > 0 {
        return back_track(data, x + 1, y);
    }

    for i in 1 .. 8 {
        if is_safe(&data, x, y, i) {
            data.set(x, y, i.try_into().unwrap());
        
            if back_track(data, x + 1, y) {
                return true;
            }
        }

        data.set(x, y, 0);
    }

    return false;
}

pub fn is_safe(data: &SlotGrid, x: usize, y: usize, val: usize) -> bool {
    println!("ran is_safe");
    for y2 in 0 .. 8 {
        if data.get(x, y2).val == val.try_into().unwrap() {
            drop(data);
            println!("A");
            return false;
        }
    }

    for x2 in 0 .. 8 {
        if data.get(x2, y).val == val.try_into().unwrap() {
            drop(data);
            println!("B");
            return false;
        }
    }

    let start_row = y - (y % 3);
    let start_col = x - (x % 3);

    for i in 0 .. 2 {
        for j in 0 .. 2 {
            if data.get(i + start_row, j + start_col).val == val.try_into().unwrap() {
                drop(data);
                println!("C");
                return false;
            }
        }
    }

    drop(data);
    return true;
}

核心错误点解析

1. is_safe函数的3X3区域遍历范围错误

检查3X3宫格时,代码使用0 .. 2的循环范围,仅覆盖宫格内4个格子,遗漏第3行和第3列,无法检测区域内完整的重复数字。同时行列遍历范围0..8也遗漏了第9个格子。

修正代码:

pub fn is_safe(data: &SlotGrid, x: usize, y: usize, val: usize) -> bool {
    // 检查整列
    for y2 in 0..9 {
        if data.get(x, y2).val == val.try_into().unwrap() {
            return false;
        }
    }

    // 检查整行
    for x2 in 0..9 {
        if data.get(x2, y).val == val.try_into().unwrap() {
            return false;
        }
    }

    // 检查完整3X3宫格
    let start_row = y - (y % 3);
    let start_col = x - (x % 3);

    for i in 0..3 {
        for j in 0..3 {
            if data.get(start_col + j, start_row + i).val == val.try_into().unwrap() {
                return false;
            }
        }
    }

    true
}

2. back_track函数的数字遍历与终止条件错误

原代码只遍历1-7的数字,完全跳过8和9,破坏数独规则;终止条件仅判断x==8&&y==8,会遗漏最后一个格子后的收尾逻辑。

修正代码:

fn back_track(data: &mut SlotGrid, mut x: usize, mut y: usize) -> bool {
    // 列遍历到末尾,切换到下一行开头
    if x == 9 {
        y += 1;
        x = 0;
    }

    // 所有行处理完成,返回成功
    if y == 9 {
        return true;
    }

    // 当前格子已有值,直接处理下一个
    if data.get(x, y).val > 0 {
        return back_track(data, x + 1, y);
    }

    // 遍历1-9所有有效数字
    for i in 1..=9 {
        if is_safe(data, x, y, i) {
            data.set(x, y, i.try_into().unwrap());
        
            if back_track(data, x + 1, y) {
                return true;
            }
        }

        data.set(x, y, 0);
    }

    false
}

3. shuffle_cols函数循环范围无效

循环写为for i in 0 .. 0,导致列混排逻辑完全不执行,初始棋盘列未被打乱。

修正代码:

pub fn shuffle_cols(&mut self) {
    let mut rng = rand::thread_rng();
    let mut num: usize;

    for i in 0..9 {
        let rand = rng.gen::<usize>() % 3;
        num = i / 3;
        self.swap_cols(i, num * 3 + rand);
    }
}

4. swap_3x3_rows和swap_3x3_cols遍历范围错误

使用0..2循环仅交换3X3块内前2行/列,遗漏第3行/列,导致块内交换不完整。

修正代码:

fn swap_3x3_rows(&mut self, y1: usize, y2: usize) {
    for i in 0..3 {
        self.swap_rows(y1 * 3 + i, y2 * 3 + i);
    }
}

fn swap_3x3_cols(&mut self, x1: usize, x2:usize) {
    for i in 0..3 {
        self.swap_cols(x1 * 3 + i, x2 * 3 + i);
    }
}

5. shuffle_nums函数数值范围错误

原代码循环0..8会交换0-7的数字,遗漏8和9,还错误包含空值0,可能导致空值被替换。

修正代码:

pub fn shuffle_nums(&mut self) {
    let mut rng = rand::thread_rng();
    // 生成1-9的随机排列进行交换
    let mut nums: Vec<i32> = (1..=9).collect();
    rng.shuffle(&mut nums);
    
    for (original, new_val) in (1..=9).zip(nums) {
        self.swap_slots(original,
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 13:05:27