#include <iostream>
#include <vector>
#include <set>
#include <cctype>
#include <algorithm>
#include <valarray>

using namespace std;

/*
数独を総当りで解く関数を2つ作成 

1, forを使用したもの 
2, 再起を使用したもの 

・1～9の数字を使用 
・盤面は9*9マス(3*3のブロックが9つ) 
・縦 + 横 + ブロック(3*3)において数字が重複しないようにする 
・問題が不正な(問題の時点で重複があった)場合問題が不正と出力 
・問題に空行が見つかった場合はその行の値を全て無しとみなす 
・読み込みが9行未満で終了した場合はそれ以降の行も値を無しとみなす 
・解が100個以上見つかった場合は100個目を出力 
・解が見つからなかった場合は解無しと出力 

問題の受け取りはテキストから一行ずつ 
各行につき9文字+改行 
値を指定しない場合はドット'.'を指定する 
*/

template <int N = 3, class Num = int, Num InvalidValue = -1, Num Empty = 0 >
struct Sudoku {
    static const int kBaseSize        = N;
    typedef Num number_type;

    static const number_type kInvalidValue = InvalidValue;
    static const number_type kEmpty        = Empty;

    typedef Sudoku<kBaseSize,number_type,kInvalidValue,kEmpty> this_type;

    typedef vector<number_type> SVector;
    typedef valarray<number_type> SArray;
    typedef vector<SArray> SAVector;

    SArray table;

    Sudoku() : table(0,N*N*N*N) {}
    Sudoku(const this_type& r) : table(r.table) {}
    Sudoku(number_type* aProblem) : table(aProblem,N*N*N*N) {}
    Sudoku(SVector aProblem) : table(aProblem.data(),N*N*N*N) {}

    bool checkPointForRange(int x, int y){
        return ! ( x < 0 || N*N <= x || y < 0 || N*N <= y );
    }

    SAVector relatedCompartmentsOf(int x, int y){
        if( !checkPointForRange(x,y) )
            return SAVector();

        int Y_ = (int)(y/N); // avoid optimization
        int X_ = (int)(x/N); // avoid optimization
        SAVector coms;
        // x axis
        coms.emplace_back( table[ gslice(y*N*N + 0,       {1,   N*N},{N*N,1}) ] );
        // y axis
        coms.emplace_back( table[ gslice(0     + x,       {N*N, 1  },{N*N,1}) ] );
        // n*n square
        coms.emplace_back( table[ gslice(Y_*N*N*N + X_*N, {N,   N  },{N*N,1}) ] );
        return coms;
    }

    SVector candidatesOf(int x, int y){
        bool has[N*N +1] = {false};
        SAVector coms = relatedCompartmentsOf(x,y);

        if( coms.empty() ) return SVector();

        for_each(coms.begin(),coms.end(), [&has](SArray& a){
            for_each(begin(a),end(a), [&has](number_type& a){ has[a] = true; });
        });

        SVector candidates;
        for( int i = 1; i < (N*N+1); i++ )
            if( ! has[i] )
                candidates.emplace_back(i);
        return candidates;
    }

private:
    bool hasOverlap_(SArray& a){
        bool has[N*N +1] = {false};
        for(int i = 0; i < N*N; i++ ){
            if( a[i] == kEmpty ) continue;
            if( has[ a[i] ] ) return true;
            has[ a[i] ] = true;
        }
        return false;
    }

public:
    bool isOverlapOn( int x, int y ){
        SAVector coms = relatedCompartmentsOf(x,y);
        return !coms.empty() &&
            any_of(coms.begin(),coms.end(),
                [&](SArray& a){ return hasOverlap_(a); });
    }

    bool hasOverlap(){
        for( int y = 0, x = 0; y < N*N; x++, y++ ){
            if( isOverlapOn((int)(y/N) + (x%N)*N, y) ){
                return true;
            }
        }
        return false;
    }

    bool set( int x, int y, number_type value ){
        if( !checkPointForRange(x,y) ||
            ( ( value < 1 || N*N < value ) && value != kEmpty )
        )
            return false;

        table[y*N*N + x] = value;
        return true;
    }

    number_type get( int x, int y ){
        return ( checkPointForRange(x,y) ) ?
            table[y*N*N + x]: kInvalidValue;
    }

    bool isEmpty( int x, int y ){
        return get(x,y) == kEmpty;
    }

    bool setEmpty( int x, int y ){
        return set(x,y,kEmpty);
    }

    static number_type getEmptyValue(){ return kEmpty; }

    ostream& show(ostream& os){
        string s(N*N+1,0);
        for( int i = 0; i < N*N; i++ ){
            SArray v = table[ gslice(i*N*N,{1,N*N},{1,1}) ];
            v += (number_type)('0');
            copy( begin(v), end(v), s.begin() );
            replace( begin(s), end(s), '0', ' ' );
            os << s << endl;
        }
        return os;
    }
    ostream& show(){
        return show(cout);
    }
};

template <int n = 3>
struct Solver {
    typedef Sudoku<n> Su;
    typedef typename Su::SVector SVector ;
    typedef typename Su::number_type SNumber ;

    static const int nn = n*n;

    struct Helper {
        int x;
        int y;
        bool succeed;
        Su problem;
        Su current;
        vector<Su> answers;

        Helper(Su& probSrc):
            x(0), y(0),
            succeed(true),
            problem(probSrc),
            current(problem)
        {
            if(fixed()) moveNext();
        }


        bool move(int i, int it){
            int cx = x;
            int cy = y;
            do if( ! problem.checkPointForRange(x+=i,0) ){
                if( ! problem.checkPointForRange(0,y+=i) ){
                    y = cy; x = cx;
                    return succeed = false;
                }
                x = it;
            } while ( fixed() );
            return succeed = true;
        }

        bool fixed()       { return !problem.isEmpty(x,y); }
        bool moveNext()    { return move(1, 0);    }
        bool movePrev()    { return move(-1,nn-1); }
        bool moveFailed()  { return !succeed; }
        int linearIndex()  { return y*nn + x; }
    
        
        SVector candidate(){ return current.candidatesOf(x,y); } 
        bool set(SNumber i){ return current.set(x,y,i); } 
        bool setEmpty()    { return current.setEmpty(x,y); } 
        SNumber get()      { return current.get(x,y); } 

        void pushAnswer()  { answers.push_back(current); }
        int sizeAnswers()  { return answers.size(); }
    };

    bool readAndSolve(){
        vector<int> N(nn*nn,Su::getEmptyValue());
        auto N_it = N.begin();
        string s;
    
        for( int i = 0; getline(cin,s) && i < n*n; i++ ){
            if( s.empty() ){
                N_it += nn;
                continue;
            }
            if( s.length() != n*n ){
                cout << "error: invalid length of line. len = "
                     << s.length() << endl << s << endl;
                return false;
            }
            std::replace(s.begin(),s.end(),'.','0');
            if( ! all_of(s.begin(),s.end(),::isdigit) ){
                cout << "error: invalid char at line." << s << endl;
                return false;
            }
            N_it = std::transform(s.begin(),s.end(),N_it,
                [](char& c){ return (int)(c - '0'); });
        }
    
        Su problem(N);
    
        cout << "Problem" << endl;
        problem.show();
        if( problem.hasOverlap() ){
            cout << "Problem has overlap." << endl;
            return false;
        }

        Helper h1(problem);
        cout << endl << "<< for loop >>" << endl;
        getAnswersByForLoop(h1,100);
        showAnswer(h1.answers);

        Helper h2(problem);
        cout << endl << "<< rec. call >>" << endl;
        getAnswersByRecursiveCall(h2,100);
        showAnswer(h2.answers);
    
        return true;
    }

    void showAnswer( vector<Su>& answers ){
        if( answers.empty() ){
            cout << "No answers were found." << endl;
        } else {
            cout << answers.size() << " answers were found." << endl;
            cout << "The last one is below." << endl;
            answers.back().show();
        }
    }

    void getAnswersByForLoop(Helper& h, int limit ){

        // max = n^5 * sizeof SNumber
        for( SVector cands[nn*nn]; !h.moveFailed(); ){
            // forward
            {
                SVector& cd = cands[h.linearIndex()];
                // entered next empty element.
                if( cd.empty() )
                    cd = h.candidate();

                // if no candidates, goto backward process.
                if( !cd.empty() ){
                    h.set( cd.back() );

                    // it could have moved to the next empty element.
                    if( h.moveNext() ) continue;

                    // or it is the answer.  
                    h.pushAnswer();

                    if( h.sizeAnswers() >= limit ){
                        cout << "Limit exceeds: too many answers. aborted." << endl;
                        return;
                    }
                    // goto backward process.
                }
            }

            // backward
            // if prev() is false, then completed. (current is the first element.)
            while( h.movePrev() ){
                // search candidate.
                SVector& cdc = cands[h.linearIndex()];

                // current value is the last candidate.
                if( cdc.empty() || cdc.front() == h.get() ) {
                    // there are no answers exist along with this path.
                    h.setEmpty();
                    cdc.clear();
                } else {
                    cdc.pop_back();
                    break;
                }
            }
        }
    }

    void getAnswersByRecursiveCall(Helper& h, int limit ){
        for( auto cd: h.candidate() ){
            h.set(cd);
            if( h.moveNext() )
                getAnswersByRecursiveCall(h,limit);
            else {
                h.pushAnswer();
                if( h.sizeAnswers() >= limit )
                    cout << "Limit exceeds: too many answers. aborted." << endl;
            }

            if( h.sizeAnswers() >= limit )
                return;

            h.setEmpty();
        }
        h.movePrev();
    }
};

int main() {
    Solver<3>().readAndSolve();
    return 0;
}


