public void Solve(GrilleSudoku s)
        {
            Console.WriteLine("Begin solving Sudoku");
            Console.WriteLine("The problem is: ");

            int[][] problem = new int[9][];
            for (int row = 0; row <= 8; row++)
            {
                problem[row] = new int[9];
                for (int column = 0; column <= 8; column++)
                {
                    problem[row][column] = s.GetCellule(row, column);
                }
            }

            int[,] tab = new int[9, 9];
            for (int row = 0; row <= 8; row++)
            {
                problem[row] = new int[9];
                for (int column = 0; column <= 8; column++)
                {
                    tab[row, column] = s.GetCellule(row, column);
                }
            }
            //DisplayMatrix(problem);
            int numOrganisms = 200;
            int maxEpochs    = 5000;
            int maxRestarts  = 20;

            Sudoku sudo = Sudoku.New(tab);
            Sudoku soln = Solvette(sudo, numOrganisms, maxEpochs, maxRestarts);

            Console.WriteLine("Best solution found: ");

            //DisplayMatrix(soln);

            //affichage du sudoku
            Console.WriteLine(soln.ToString());
            //Reconversion depuis int[,]
            for (int row = 0; row < 9; row++)
            {
                for (int column = 0; column < 9; column++)
                {
                    s.Cellules[row * 9 + column] = soln.CellValues[row, column];
                }
            }

            int err = Error(soln);

            if (err == 0)
            {
                Console.WriteLine("Success \n");
            }
            else
            {
                Console.WriteLine("Did not find optimal solution \n");
            }
            Console.WriteLine("End Sudoku demo");
            Console.ReadLine();
        }
Пример #2
0
 static void AddDataConstraints(GrilleSudoku data, Model model, Decision[][] grid)
 {
     for (int r = 0; r < 9; ++r)
     {
         for (int c = 0; c < 9; ++c)
         {
             if (data.GetCellule(r, c) != 0)
             {
                 model.AddConstraint("v" + r + c, grid[r][c] == data.GetCellule(r, c));
             }
         }
     }
 }
        public void Solve(GrilleSudoku s)
        {
            //Création d'un solver Or-tools

            Solver solver = new Solver("Sudoku");

            //Création de la grille de variables

            IntVar[,] grid = solver.MakeIntVarMatrix(9, 9, 1, 9, "grid");
            IntVar[] grid_flat = grid.Flatten();



            //Masque de résolution
            foreach (int i in cellIndices)
            {
                foreach (int j in cellIndices)
                {
                    if (s.GetCellule(i, j) > 0)
                    {
                        solver.Add(grid[i, j] == s.GetCellule(i, j));
                    }
                }
            }

            //Un chiffre ne figure qu'une seule fois par ligne/colonne/cellule
            foreach (int i in cellIndices)
            {
                // Lignes
                solver.Add((from j in cellIndices
                            select grid[i, j]).ToArray().AllDifferent());

                // Colonnes
            }

            //Cellules



            //Début de la résolution
            DecisionBuilder db = solver.MakePhase(grid_flat,
                                                  Solver.INT_VAR_SIMPLE,
                                                  Solver.INT_VALUE_SIMPLE);

            solver.NewSearch(db);

            //Mise à jour du sudoku
        }
        public void Solve(GrilleSudoku s)
        {
            List <List <int> > list_cell = new List <List <int> >();

            foreach (var i in System.Linq.Enumerable.Range(0, 9))
            {
                var ligne = new List <int>(9);
                list_cell.Add(ligne);
                foreach (var j in System.Linq.Enumerable.Range(0, 9))
                {
                    ligne.Add(s.GetCellule(j, i));
                }
            }
            var monTableau = list_cell.Select(l => l.ToArray()).ToArray();
            var monPuzzle  = new Puzzle(monTableau, false);
            var monSolver  = new Solver(monPuzzle);

            monSolver.DoWork(this, new System.ComponentModel.DoWorkEventArgs(null));

            foreach (var i in System.Linq.Enumerable.Range(0, 9))
            {
                foreach (var j in System.Linq.Enumerable.Range(0, 9))
                {
                    s.SetCell(i, j, monPuzzle.Rows[i][j].Value);
                }
            }
        }
Пример #5
0
        public bool Resolve(GrilleSudoku s)            // La fonction solve va pemettre de choisir la solution valide satsifaisant les contraintes de sodoku
        {
            for (int ligne = 0; ligne < Size; ligne++) // parcourt les lignes
            {
                for (int col = 0; col < Size; col++)   // parcourt les colonnes
                {
                    // Ce double for parcourt chaque colonne pour une ligne donnée
                    if (s.GetCellule(ligne, col) == 0)           // empty: si la cellule est vide alors on va rentrer dans la boucle for
                    {
                        for (int value = 1; value <= 9; value++) // qui va tester les valeur de 1 à 9
                        {
                            if (IsValid(s, ligne, col, value))   // Si la valeur est valide avec les conditions de validité qu'on va définirapres dans le code
                            {
                                s.SetCell(ligne, col, value);    // Alors on remplie la case vide avec la valeur valide

                                if (Resolve(s))                  // Et on appelle la fonction elle-meme pour la récursivité : Principe de backtracking
                                {
                                    return(true);                // On on valide la solution
                                }
                                else
                                {
                                    s.SetCell(ligne, col, 0);  // Sinon on réinitialise la valeur à 0
                                }
                            }
                        }

                        return(false); // On refait le même processus
                    }
                }
            }

            return(true); // Jusqu'à ce qu'on trouve une solution valide et on la garde
        }
Пример #6
0
        private static IImmutableList <Tuple <int, int, int, bool> > BuildInternalRowsForGrid(GrilleSudoku grid)
        {
            var rowsByCols =
                from row in Rows
                from col in Cols
                let value = grid.GetCellule(row, col)
                            select BuildInternalRowsForCell(row, col, value);

            return(rowsByCols.SelectMany(cols => cols).ToImmutableList());
        }
        public void Solve(GrilleSudoku s)
        {
            var substExprs = new List <Expr>();
            var substVals  = new List <Expr>();

            for (int i = 0; i < 9; i++)
            {
                for (int j = 0; j < 9; j++)
                {
                    if (s.GetCellule(i, j) != 0)
                    {
                        substExprs.Add(X[i][j]);
                        substVals.Add(z3Context.MkInt(s.GetCellule(i, j)));
                    }
                }
            }
            BoolExpr instance_c = (BoolExpr)GenericContraints.Substitute(substExprs.ToArray(), substVals.ToArray());

            var z3Solver = GetSolver();

            z3Solver.Assert(instance_c);

            if (z3Solver.Check() == Status.SATISFIABLE)
            {
                Model m = z3Solver.Model;
                for (int i = 0; i < 9; i++)
                {
                    for (int j = 0; j < 9; j++)
                    {
                        if (s.GetCellule(i, j) == 0)
                        {
                            s.SetCell(i, j, ((IntNum)m.Evaluate(X[i][j])).Int);
                        }
                    }
                }
            }
            else
            {
                Console.WriteLine("Failed to solve sudoku");
            }
        }
Пример #8
0
        private IList <IList <IList <int> > > GetRowsPermutationsUncached(GrilleSudoku objSudoku)
        {
            var toReturn = new List <IList <IList <int> > >(9);

            for (int i = 0; i < 9; i++)
            {
                var tempList = new List <IList <int> >();
                foreach (var perm in AllPermutations)
                {
                    // Permutation should match current mask row numbers, and have numbers different that other mask rows
                    if (!Range9.Any(rowIdx => Range9.Any(j => objSudoku.GetCellule(rowIdx, j) > 0 &&
                                                         ((rowIdx == i && perm[j] != objSudoku.GetCellule(rowIdx, j)) || (rowIdx != i && perm[j] == objSudoku.GetCellule(rowIdx, j))))))
                    {
                        tempList.Add(perm);
                    }
                }
                toReturn.Add(tempList);
            }

            return(toReturn);
        }
Пример #9
0
        private bool IsColValid(GrilleSudoku s, int col, int value)
        {
            for (var ligne = 0; ligne < Size; ligne++)
            {
                if (s.GetCellule(ligne, col) == value)
                {
                    return(false);
                }
            }

            return(true);
        }
Пример #10
0
        private bool IsLigneValid(GrilleSudoku s, int ligne, int value)
        {
            for (var col = 0; col < Size; col++)
            {
                if (s.GetCellule(ligne, col) == value)
                {
                    return(false);
                }
            }

            return(true);
        }
Пример #11
0
        private bool IsLigneValid(GrilleSudoku s, int ligne, int value)
        //Vérifie que chaque nombre n'apparait qu'une fois sur la ligne
        {
            for (var col = 0; col < Size; col++)
            {
                if (s.GetCellule(ligne, col) == value)
                {
                    return(false);
                }
            }

            return(true);
        }
 public int[][] ConvertToMatrix(GrilleSudoku grille)  // conversion de grillesudoku en int[][]
 {
     int[][] sud = new int[9][];
     for (int i = 0; i < 9; i++)
     {
         sud[i] = new int[9];
         for (int j = 0; j < 9; j++)
         {
             sud[i][j] = grille.GetCellule(i, j);
         }
     }
     return(sud);
 }
Пример #13
0
        private bool IsColValid(GrilleSudoku s, int col, int value)
        //Vérifie que chaque nombre n'apparait qu'une fois sur la colonne
        {
            for (var ligne = 0; ligne < Size; ligne++)
            {
                if (s.GetCellule(ligne, col) == value)
                {
                    return(false);
                }
            }

            return(true);
        }
        public bool Is_present(GrilleSudoku s, List <List <int> > possibilites, int poss, int position)
        {
            foreach (int voisin in Cell_Neighboor(s, position))
            {
                int ligne   = voisin / 9;
                int colonne = voisin % 9;

                if (s.GetCellule(ligne, colonne) == poss)
                {
                    return(false);
                }
            }
            return(true);
        }
        public void unassign(GrilleSudoku s, int cell, List <int> assignment, List <List <int> > possibilites, List <List <int> > pruned)
        {
            int val_cell = s.GetCellule(cell / 9, cell % 9);

            if (assignment.Contains(cell))
            {
                if (!possibilites[cell].Contains(val_cell))
                {
                    possibilites[cell].Add(val_cell);
                }


                if (possibilites[cell].Contains(10))
                {
                    possibilites[cell].Remove(10);
                }

                assignment.Remove(cell);
                s.SetCell(cell / 9, cell % 9, 0);
                List <int> l = new List <int>();
                pruned[cell] = l;

                foreach (int neighboor_value in Cell_Neighboor(s, cell))
                {
                    List <int> r = new List <int>();

                    if (!possibilites[neighboor_value].Contains(val_cell))
                    {
                        if (Is_present(s, possibilites, val_cell, neighboor_value))
                        {
                            possibilites[neighboor_value].Add(val_cell);
                            //Console.WriteLine("Voisin : {0} Re add dans poss : {1}", neighboor_value, val_cell);
                        }
                    }

                    if (pruned[neighboor_value].Contains(val_cell))
                    {
                        pruned[neighboor_value].Remove(val_cell);
                    }
                }
            }
            else
            {
                Console.WriteLine("ERROOR 265 : UNSIGN A NON SIGN");
            }
        }
Пример #16
0
        private bool IsCarreValid(GrilleSudoku s, int ligne, int col, int value)
        {
            var l = ligne - ligne % 3;
            var c = col - col % 3;

            for (var i = l; i < l + 3; i++)
            {
                for (var j = c; j < c + 3; j++)
                {
                    if (s.GetCellule(i, j) == value)
                    {
                        return(false);
                    }
                }
            }

            return(true);
        }
Пример #17
0
        private bool IsCarreValid(GrilleSudoku s, int ligne, int col, int value)
        //Vérifie que chaque valeur n'apparait qu'une fois dans le carré
        {
            var l = ligne - ligne % 3;
            var c = col - col % 3;

            for (var i = l; i < l + 3; i++)
            {
                for (var j = c; j < c + 3; j++)
                {
                    if (s.GetCellule(i, j) == value)
                    {
                        return(false);
                    }
                }
            }

            return(true);
        }
        // FONCTION DE FOWARD CHECKING
        public List <List <int> > Foward_Checking(GrilleSudoku s)
        {
            List <List <int> > LesPoss = new List <List <int> >();

            for (int i = 0; i < 81; i++)
            {
                List <int> Poss = new List <int>();
                if (s.GetCellule(i / 9, i % 9) != 0)
                {
                    Poss.Add(10);
                }
                else
                {
                    foreach (int possibilities in s.GetPossibilities(i / 9, i % 9))
                    {
                        Poss.Add(possibilities);
                    }
                }
                LesPoss.Add(Poss);
            }
            return(LesPoss);
        }
Пример #19
0
        private int[][] workingSudoku;   //Sudoku sur lequel vous allez travailler

        public Sudoku(GrilleSudoku grid) //Constructeur
        {
            initialSudoku = new int[9][];
            workingSudoku = new int[9][];
            string phrase = "";

            for (int i = 0; i < 9; i++)
            {
                initialSudoku[i] = new int[9];
                workingSudoku[i] = new int[9];
            }
            for (int i = 0; i < 9; i++)
            {
                for (int j = 0; j < 9; j++)
                {
                    phrase = phrase + grid.GetCellule(i, j);
                }
            }

            initialSudoku = stringToSudoku(phrase);
            workingSudoku = stringToSudoku(phrase);
        }
        // FONCTION SOLVE
        public void Solve(GrilleSudoku s)
        {
            // Foward Checking
            List <List <int> > possibilites = this.Foward_Checking(s);
            List <List <int> > pruned       = new List <List <int> >();

            List <int> liste = new List <int>();

            for (int i = 0; i < 81; i++)
            {
                pruned.Add(liste);
            }

            // ACR CONSITENCY CHECKING
            AC3(s, possibilites);
            if (Is_Finished(possibilites))
            {
                Console.WriteLine("Résolue juste avec AC3  !!!");
            }

            else
            {
                List <int> assignment = new List <int>();
                for (int i = 0; i < 81; i++)
                {
                    if (s.GetCellule(i / 9, i % 9) != 0)
                    {
                        assignment.Add(i);
                    }
                }
                if (recursive_backtrack_algorithm(s, possibilites, assignment, pruned))
                {
                    Console.WriteLine("Résolue avec Backtracking !!");
                }
            }
        }
        public void Solve(GrilleSudoku s)
        {
            //Création d'un solver Or-tools
            Solver solver = new Solver("Sudoku");

            //Création de la grille de variables
            //Variables de décision

            IntVar[,] grid = solver.MakeIntVarMatrix(9, 9, 1, 9, "grid"); //VERIFIER
            IntVar[] grid_flat = grid.Flatten();                          //VERIFIER


            //Masque de résolution
            for (int i = 0; i < cellIndices.Count(); i++)
            {
                for (int j = 0; j < cellIndices.Count(); j++)
                {
                    if (s.GetCellule(i, j) > 0)
                    {
                        solver.Add(grid[i, j] == s.GetCellule(i, j));
                    }
                }
            }

            //Un chiffre ne figure qu'une seule fois par ligne/colonne/cellule
            for (int i = 0; i < cellIndices.Count(); i++)
            {
                // Lignes
                solver.Add((from j in cellIndices
                            select grid[i, j]).ToArray().AllDifferent());

                // Colonnes
                solver.Add((from j in cellIndices
                            select grid[j, i]).ToArray().AllDifferent());
            }

            //Cellules
            for (int i = 0; i < CELL.Count(); i++)
            {
                for (int j = 0; j < CELL.Count(); j++)
                {
                    solver.Add((from di in CELL
                                from dj in CELL
                                select grid[i * cell_size + di, j * cell_size + dj]
                                ).ToArray().AllDifferent());
                }
            }

            //Début de la résolution
            DecisionBuilder db = solver.MakePhase(grid_flat,
                                                  Solver.INT_VAR_SIMPLE,
                                                  Solver.INT_VALUE_SIMPLE);

            solver.NewSearch(db);

            //Mise à jour du sudoku
            //int n = cell_size * cell_size;
            //Or on sait que cell_size = 3 -> voir ligne 13
            //Inspiré de l'exemple : taille des cellules identique
            while (solver.NextSolution())
            {
                for (int i = 0; i < 9; i++)
                {
                    for (int j = 0; j < 9; j++)
                    {
                        s.SetCell(i, j, (int)grid[i, j].Value());
                    }
                }
            }

            //Si 4 lignes dessus optionnelles, EndSearch est obligatoire
            solver.EndSearch();
        }
        //NOUVEAU CODE


        public void Solve2(GrilleSudoku s)
        {
            bool solved  = false;
            bool full    = false; // If this is true after a segment, the puzzle is solved and we can break
            bool deadEnd = false;

            List <List <int> > list_cell = new List <List <int> >();

            foreach (var i in System.Linq.Enumerable.Range(0, 9))
            {
                var ligne = new List <int>(9);
                list_cell.Add(ligne);
                foreach (var j in System.Linq.Enumerable.Range(0, 9))
                {
                    ligne.Add(s.GetCellule(j, i));
                }
            }



            var monTableau = list_cell.Select(l => l.ToArray()).ToArray();
            var monPuzzle  = new Puzzle(monTableau, false);
            var monSolver  = new Solver(monPuzzle);
            //monSolver.DoWork(this, new System.ComponentModel.DoWorkEventArgs(null));

            List <Cell> allCells = null;
            Stack <BackTrackingState> exploredCellValues = null;

            monPuzzle.RefreshCandidates();
            do
            {
                deadEnd = false;

                // First we do human inference
                do
                {
                    full = true;

                    bool changed = false;
                    // Check for naked singles or a completed puzzle
                    for (int x = 0; x < 9; x++)
                    {
                        for (int y = 0; y < 9; y++)
                        {
                            Cell cell = monPuzzle[x, y];
                            if (cell.Value == 0)
                            {
                                full = false;
                                // Check for naked singles
                                int[] a = cell.Candidates.ToArray(); // Copy
                                if (a.Length == 1)
                                {
                                    cell.Set(a[0]);
                                    changed = true;
                                }
                            }
                        }
                    }
                    // Solved or failed to solve
                    if (full || (!changed && !monSolver.RunTechnique()))
                    {
                        break;
                    }
                } while (true);

                full = monPuzzle.Rows.All(row => row.All(c => c.Value != 0));
                //full = s.SetCell(x, y, monPuzzle.Rows[x][y].Value);

                // If puzzle isn't full, we do exploration
                if (!full)
                {
                    // Les Sudokus les plus difficiles ne peuvent pas être résolus avec un stylo bille, c'est à dire en inférence pure.
                    // Il va falloir lacher le stylo bille et prendre le crayon à papier et la gomme pour commencer une exploration fondée sur des hypothèses avec possible retour en arrière
                    if (allCells == null)
                    {
                        allCells           = monPuzzle.Rows.SelectMany((row, rowIdx) => row).ToList();
                        exploredCellValues = new Stack <BackTrackingState>();
                    }
                    //puzzle.RefreshCandidates();

                    // Pour accélérer l'exploration et éviter de traverser la feuille en gommant trop souvent, on va utiliser les heuristiques des problèmes à satisfaction de contraintes
                    // cf. les slides et le problème du "coffre de voiture" abordé en cours

                    //heuristique MRV
                    var minCandidates = allCells.Min(cell => cell.Candidates.Count > 0 ? cell.Candidates.Count : int.MaxValue);

                    if (minCandidates != int.MaxValue)
                    {
                        // Utilisation de l'heuristique Deg: de celles qui ont le moins de candidats à égalité, on choisi celle la plus contraignante, celle qui a le plus de voisins (on pourrait faire mieux avec le nombre de candidats en commun avec ses voisins)
                        var candidateCells = allCells.Where(cell => cell.Candidates.Count == minCandidates);
                        //var degrees = candidateCells.Select(candidateCell => new {Cell = candidateCell, Degree = candidateCell.GetCellsVisible().Aggregate(0, (sum, neighbour) => sum + neighbour.Candidates.Count) });
                        var degrees = candidateCells.Select(candidateCell => new { Cell = candidateCell, Degree = candidateCell.GetCellsVisible().Count(c => c.Value == 0) }).ToList();
                        //var targetCell = list_cell.First(cell => cell.Candidates.Count == minCandidates);
                        var maxDegree  = degrees.Max(deg1 => deg1.Degree);
                        var targetCell = degrees.First(deg => deg.Degree == maxDegree).Cell;

                        //dernière exploration pour ne pas se mélanger les pinceaux

                        BackTrackingState currentlyExploredCellValues;
                        if (exploredCellValues.Count == 0 || !exploredCellValues.Peek().Cell.Equals(targetCell))
                        {
                            currentlyExploredCellValues = new BackTrackingState()
                            {
                                Board = monPuzzle.GetBoard(), Cell = targetCell, ExploredValues = new List <int>()
                            };
                            exploredCellValues.Push(currentlyExploredCellValues);
                        }
                        else
                        {
                            currentlyExploredCellValues = exploredCellValues.Peek();
                        }


                        //utilisation de l'heuristique LCV: on choisi la valeur la moins contraignante pour les voisins
                        var candidateValues  = targetCell.Candidates.Where(i => !currentlyExploredCellValues.ExploredValues.Contains(i));
                        var neighbourood     = targetCell.GetCellsVisible();
                        var valueConstraints = candidateValues.Select(v => new
                        {
                            Value       = v,
                            ContraintNb = neighbourood.Count(neighboor => neighboor.Candidates.Contains(v))
                        }).ToList();
                        var minContraints = valueConstraints.Min(vc => vc.ContraintNb);
                        var exploredValue = valueConstraints.First(vc => vc.ContraintNb == minContraints).Value;
                        currentlyExploredCellValues.ExploredValues.Add(exploredValue);
                        targetCell.Set(exploredValue);
                        //targetCell.Set(exploredValue, true);
                    }
                    else
                    {
                        //Plus de candidats possibles, on atteint un cul-de-sac
                        if (monPuzzle.IsValid())
                        {
                            solved = true;
                        }
                        else
                        {
                            deadEnd = true;
                        }


                        //deadEnd = true;
                    }
                }
                else
                {
                    //If puzzle is full, it's either solved or a deadend
                    if (monPuzzle.IsValid())
                    {
                        solved = true;
                    }
                    else
                    {
                        deadEnd = true;
                    }
                }


                if (deadEnd)
                {
                    //On se retrouve bloqué, il faut gommer et tenter d'autres hypothèses
                    BackTrackingState currentlyExploredCellValues = exploredCellValues.Peek();
                    //On annule la dernière assignation
                    currentlyExploredCellValues.Backtrack(monPuzzle);
                    var targetCell = currentlyExploredCellValues.Cell;
                    //targetCell.Set(0, true);
                    while (targetCell.Candidates.All(i => currentlyExploredCellValues.ExploredValues.Contains(i)))
                    {
                        //on a testé toutes les valeurs possibles, On est à un cul de sac, il faut revenir en arrière
                        exploredCellValues.Pop();
                        if (exploredCellValues.Count == 0)
                        {
                            Debug.WriteLine("bug in the algorithm techniques humaines");
                        }
                        currentlyExploredCellValues = exploredCellValues.Peek();
                        //On annule la dernière assignation
                        currentlyExploredCellValues.Backtrack(monPuzzle);
                        targetCell = currentlyExploredCellValues.Cell;
                        //targetCell.Set(0, true);
                    }
                    // D'autres valeurs sont possible pour la cellule courante, on les tente
                    //utilisation de l'heuristique LCV
                    var candidateValues  = targetCell.Candidates.Where(i => !currentlyExploredCellValues.ExploredValues.Contains(i));
                    var neighbourood     = targetCell.GetCellsVisible();
                    var valueConstraints = candidateValues.Select(v => new
                    {
                        Value       = v,
                        ContraintNb = neighbourood.Count(neighboor => neighboor.Candidates.Contains(v))
                    }).ToList();
                    var minContraints = valueConstraints.Min(vc => vc.ContraintNb);
                    var exploredValue = valueConstraints.First(vc => vc.ContraintNb == minContraints).Value;
                    currentlyExploredCellValues.ExploredValues.Add(exploredValue);
                    targetCell.Set(exploredValue);
                }
            } while (!solved);



            foreach (var i in System.Linq.Enumerable.Range(0, 9))
            {
                foreach (var j in System.Linq.Enumerable.Range(0, 9))
                {
                    s.SetCell(i, j, monPuzzle.Rows[i][j].Value);
                }
            }
        }
Пример #23
0
        public void Solve(GrilleSudoku s)
        {
            int[][] sJaggedTableau = Enumerable.Range(0, 9).Select(i => Enumerable.Range(0, 9).Select(j => s.GetCellule(i, j)).ToArray()).ToArray();
            var     sTableau       = To2D(sJaggedTableau);

            Program.estValide(sTableau, 0);
            Enumerable.Range(0, 9).ToList().ForEach(i => Enumerable.Range(0, 9).ToList().ForEach(j => s.SetCell(i, j, sTableau[i, j])));
        }