← Назад к вопросам

Как бы создал сервис для проверки правильности результатов судоку

2.0 Middle🔥 101 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Сервис для проверки правильности результатов судоку

Разберу, как я бы создал такой сервис, используя чистую архитектуру.

Анализ задачи

Судоку — это сетка 9×9, где нужно проверить три условия:

  1. Строки (rows) — каждая строка содержит числа 1-9 без повторений
  2. Столбцы (columns) — каждый столбец содержит числа 1-9 без повторений
  3. Блоки 3×3 — каждый блок содержит числа 1-9 без повторений

Архитектурный подход

Presentation Layer (REST Controller)
        ↓
Application Layer (Service)
        ↓
Domain Layer (SudokuValidator)

Реализация

1. Domain Model

public class SudokuBoard {
    private static final int SIZE = 9;
    private static final int BLOCK_SIZE = 3;
    private final int[][] board;
    
    public SudokuBoard(int[][] board) {
        if (board == null || board.length != SIZE) {
            throw new IllegalArgumentException("Board must be 9x9");
        }
        this.board = board;
    }
    
    public int[][] getBoard() {
        return board;
    }
    
    public int getSize() {
        return SIZE;
    }
    
    public int getBlockSize() {
        return BLOCK_SIZE;
    }
    
    public int getValue(int row, int col) {
        return board[row][col];
    }
}

public class SudokuValidationResult {
    private final boolean valid;
    private final String message;
    private final String errorLocation;  // Где ошибка (строка, столбец или блок)
    
    public SudokuValidationResult(boolean valid, String message, String errorLocation) {
        this.valid = valid;
        this.message = message;
        this.errorLocation = errorLocation;
    }
    
    public boolean isValid() {
        return valid;
    }
    
    public String getMessage() {
        return message;
    }
    
    public String getErrorLocation() {
        return errorLocation;
    }
    
    public static SudokuValidationResult valid() {
        return new SudokuValidationResult(true, "Судоку решено правильно", null);
    }
    
    public static SudokuValidationResult invalid(String message, String location) {
        return new SudokuValidationResult(false, message, location);
    }
}

2. Domain Service (Validator)

import java.util.HashSet;
import java.util.Set;

@Component
public class SudokuValidator {
    
    public SudokuValidationResult validate(SudokuBoard board) {
        // Проверяем строки
        for (int row = 0; row < board.getSize(); row++) {
            if (!isRowValid(board, row)) {
                return SudokuValidationResult.invalid(
                    "Строка " + (row + 1) + " содержит дубликаты",
                    "Row " + (row + 1)
                );
            }
        }
        
        // Проверяем столбцы
        for (int col = 0; col < board.getSize(); col++) {
            if (!isColumnValid(board, col)) {
                return SudokuValidationResult.invalid(
                    "Столбец " + (col + 1) + " содержит дубликаты",
                    "Column " + (col + 1)
                );
            }
        }
        
        // Проверяем 3×3 блоки
        for (int blockRow = 0; blockRow < 3; blockRow++) {
            for (int blockCol = 0; blockCol < 3; blockCol++) {
                if (!isBlockValid(board, blockRow, blockCol)) {
                    return SudokuValidationResult.invalid(
                        "Блок (" + (blockRow + 1) + ", " + (blockCol + 1) + 
                        ") содержит дубликаты",
                        "Block (" + (blockRow + 1) + ", " + (blockCol + 1) + ")"
                    );
                }
            }
        }
        
        return SudokuValidationResult.valid();
    }
    
    /**
     * Проверяет строку на наличие дубликатов и значений вне диапазона 1-9
     */
    private boolean isRowValid(SudokuBoard board, int row) {
        Set<Integer> seen = new HashSet<>();
        
        for (int col = 0; col < board.getSize(); col++) {
            int value = board.getValue(row, col);
            
            if (value < 1 || value > 9) {
                return false;  // Значение вне диапазона
            }
            
            if (!seen.add(value)) {
                return false;  // Дубликат
            }
        }
        
        return true;
    }
    
    /**
     * Проверяет столбец на наличие дубликатов
     */
    private boolean isColumnValid(SudokuBoard board, int col) {
        Set<Integer> seen = new HashSet<>();
        
        for (int row = 0; row < board.getSize(); row++) {
            int value = board.getValue(row, col);
            
            if (value < 1 || value > 9) {
                return false;
            }
            
            if (!seen.add(value)) {
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * Проверяет 3×3 блок на наличие дубликатов
     * blockRow и blockCol — индексы блока (0-2), не ячейки
     */
    private boolean isBlockValid(SudokuBoard board, int blockRow, int blockCol) {
        Set<Integer> seen = new HashSet<>();
        
        int startRow = blockRow * board.getBlockSize();
        int startCol = blockCol * board.getBlockSize();
        
        for (int row = startRow; row < startRow + board.getBlockSize(); row++) {
            for (int col = startCol; col < startCol + board.getBlockSize(); col++) {
                int value = board.getValue(row, col);
                
                if (value < 1 || value > 9) {
                    return false;
                }
                
                if (!seen.add(value)) {
                    return false;
                }
            }
        }
        
        return true;
    }
}

3. Application Service

@Service
public class SudokuService {
    
    private final SudokuValidator validator;
    
    public SudokuService(SudokuValidator validator) {
        this.validator = validator;
    }
    
    public SudokuValidationResult checkSolution(int[][] boardArray) {
        try {
            SudokuBoard board = new SudokuBoard(boardArray);
            return validator.validate(board);
        } catch (IllegalArgumentException e) {
            return SudokuValidationResult.invalid(
                "Невалидный размер доски: " + e.getMessage(),
                "Board"
            );
        }
    }
}

4. REST Controller

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/sudoku")
public class SudokuController {
    
    private final SudokuService sudokuService;
    
    public SudokuController(SudokuService sudokuService) {
        this.sudokuService = sudokuService;
    }
    
    @PostMapping("/validate")
    public ResponseEntity<SudokuValidationResponse> validateSudoku(
            @RequestBody SudokuValidationRequest request) {
        
        SudokuValidationResult result = sudokuService.checkSolution(request.getBoard());
        
        return ResponseEntity.ok(new SudokuValidationResponse(
            result.isValid(),
            result.getMessage(),
            result.getErrorLocation()
        ));
    }
}

class SudokuValidationRequest {
    private int[][] board;
    
    public int[][] getBoard() {
        return board;
    }
    
    public void setBoard(int[][] board) {
        this.board = board;
    }
}

class SudokuValidationResponse {
    private boolean valid;
    private String message;
    private String errorLocation;
    
    public SudokuValidationResponse(boolean valid, String message, String errorLocation) {
        this.valid = valid;
        this.message = message;
        this.errorLocation = errorLocation;
    }
    
    // Getters...
}

5. Unit тесты

@SpringBootTest
public class SudokuValidatorTest {
    
    @Autowired
    private SudokuValidator validator;
    
    @Test
    public void testValidSudoku() {
        int[][] validBoard = {
            {5, 3, 4, 6, 7, 8, 9, 1, 2},
            {6, 7, 2, 1, 9, 5, 3, 4, 8},
            {1, 9, 8, 3, 4, 2, 5, 6, 7},
            {8, 5, 9, 7, 6, 1, 4, 2, 3},
            {4, 2, 6, 8, 5, 3, 7, 9, 1},
            {7, 1, 3, 9, 2, 4, 8, 5, 6},
            {9, 6, 1, 5, 3, 7, 2, 8, 4},
            {2, 8, 7, 4, 1, 9, 6, 3, 5},
            {3, 4, 5, 2, 8, 6, 1, 7, 9}
        };
        
        SudokuBoard board = new SudokuBoard(validBoard);
        SudokuValidationResult result = validator.validate(board);
        
        assertTrue(result.isValid());
    }
    
    @Test
    public void testInvalidRow() {
        int[][] invalidBoard = {
            {5, 5, 4, 6, 7, 8, 9, 1, 2},  // Две пятёрки в первой строке
            {6, 7, 2, 1, 9, 5, 3, 4, 8},
            {1, 9, 8, 3, 4, 2, 5, 6, 7},
            {8, 5, 9, 7, 6, 1, 4, 2, 3},
            {4, 2, 6, 8, 5, 3, 7, 9, 1},
            {7, 1, 3, 9, 2, 4, 8, 5, 6},
            {9, 6, 1, 5, 3, 7, 2, 8, 4},
            {2, 8, 7, 4, 1, 9, 6, 3, 5},
            {3, 4, 5, 2, 8, 6, 1, 7, 9}
        };
        
        SudokuBoard board = new SudokuBoard(invalidBoard);
        SudokuValidationResult result = validator.validate(board);
        
        assertFalse(result.isValid());
        assertTrue(result.getMessage().contains("Строка"));
    }
    
    @Test
    public void testInvalidBlock() {
        int[][] invalidBoard = {
            {5, 3, 4, 6, 7, 8, 9, 1, 2},
            {6, 7, 2, 1, 9, 5, 3, 4, 8},
            {1, 9, 5, 3, 4, 2, 5, 6, 7},  // Две пятёрки в левом верхнем блоке
            {8, 5, 9, 7, 6, 1, 4, 2, 3},
            {4, 2, 6, 8, 5, 3, 7, 9, 1},
            {7, 1, 3, 9, 2, 4, 8, 5, 6},
            {9, 6, 1, 5, 3, 7, 2, 8, 4},
            {2, 8, 7, 4, 1, 9, 6, 3, 5},
            {3, 4, 5, 2, 8, 6, 1, 7, 9}
        };
        
        SudokuBoard board = new SudokuBoard(invalidBoard);
        SudokuValidationResult result = validator.validate(board);
        
        assertFalse(result.isValid());
    }
}

Пример использования

curl -X POST http://localhost:8080/api/v1/sudoku/validate \
  -H "Content-Type: application/json" \
  -d '{
    "board": [
      [5,3,4,6,7,8,9,1,2],
      [6,7,2,1,9,5,3,4,8],
      [1,9,8,3,4,2,5,6,7],
      [8,5,9,7,6,1,4,2,3],
      [4,2,6,8,5,3,7,9,1],
      [7,1,3,9,2,4,8,5,6],
      [9,6,1,5,3,7,2,8,4],
      [2,8,7,4,1,9,6,3,5],
      [3,4,5,2,8,6,1,7,9]
    ]
  }'

Временная и пространственная сложность

  • Time Complexity: O(1) — всегда проверяем ровно 9×9 = 81 ячейку
  • Space Complexity: O(1) — используем Set максимум на 9 элементов, всегда O(9) = O(1)

Этот подход чистый, тестируемый и соответствует Clean Architecture.