Quantcast
Channel: 円周率近似値の日に生まれて理系じゃないわけないだろ! - knifeのblog
Viewing all articles
Browse latest Browse all 5376

7セグメントパズル? -プログラミング編-

$
0
0

随分と日数が経ってしまいましたが、プログラミング編です。



図のような0から9を模した7セグメントっぽいピースと枠がある。
枠内に、全てのピースを重ならないように詰めて下さい。
ピースは回転しても裏返しても構いません。
それぞれのサイズは、方眼の通りです。


シンキングタ~イム


綺麗(すべてがメッシュ状に収まる)な解だけを探すプログラムを組むことができました。

プログラミングと一口に言っても、今回はプログラムを組むことよりも、大変な作業がありました。

それはデータを作ること。

unsigned long long num[2][40] = {{
    0x9fcf0f80bf0f3f6f,    /* 縦0 */
    0x9fcf0fe02f0f3f6f,
    0x9fcf0fe08f0f3f60,
    0x9fcf0f20bf0f3f60,
    0x00000000008fef25,    /* 縦1 */
    0x00000000008fbf25,
    0x00000000008fbf20,
    0x00000000008fef20,
    0x1fc00f9f6f003f4f,    /* 縦2 */
    0x1fc00f9f6f003f40,
    0x9f4f003fc00f1f6f,
    0x9f4f003fc00f1f60,
    0x9f4f00bf4f003f4f,    /* 縦3 */
    0x1fc00f1fe00f1f6f,
    0x1fc00f1fe00f1f60,
    0x9f4f00bf4f003f40,
    0x800f00bfcf0f202f,    /* 縦4 */
    0x808f0f3fe00f002f,
    0x00800f9fef0f202f,
    0x808f0fbf6f00200f,
    0x9f4f003fc00f1f6f,    /* 縦5 */
    0x9f4f003fc00f1f60,
    0x1fc00f9f6f003f4f,
    0x1fc00f9f6f003f40,
    0x9fcf0f3fe00f1f6f,    /* 縦6 */
    0x9f4f00bfcf0f3f6f,
    0x9fcf0fbf6f003f4f,
    0x1fc00f9fef0f3f6f,
    0x800f00e00f003f4f,    /* 縦7 */
    0x1fc00f00b00f002f,
    0x00800f00b00f1f6f,
    0x9f4f00e00f00200f,
    0x9fcf0fbfef0f3f6f,    /* 縦8 */
    0x9fcf0fbfef0f3f60,
    0x9fcf0fbfef0f3f60,
    0x9fcf0fbfef0f3f60,
    0x9f4f00bfcf0f3f6f,    /* 縦9 */
    0x9fcf0f3fe00f1f6f,
    0x1fc00f9fef0f3f6f,
    0x9fcf0fbf6f003f4f},{
    0x9f4fcf000f3fdf6f,    /* 横0 */
    0x9f7fcf000f3f1f6f,
    0x9f1fcf000f3fdf60,
    0x9f7fcf000f3f4f60,
    0x00000000001f7f45,    /* 横1 */
    0x00000000001fdf45,
    0x00000000001fdf40,
    0x00000000001f7f40,
    0x9fc08f0f0f203f6f,    /* 横2 */
    0x9fc08f0f0f203f60,
    0x809fcf0f0f3f602f,
    0x809fcf0f0f3f6020,
    0x9fdfcf0f0f20202f,    /* 横3 */
    0x80808f0f0f3f7f6f,
    0x80808f0f0f3f7f60,
    0x9fdfcf0f0f202020,
    0x1fdf400f001f600f,    /* 横4 */
    0x009f400f001f7f4f,
    0x1fc0000f001f7f4f,
    0x1fdf400f00003f4f,
    0x809fcf0f0f3f602f,    /* 横5 */
    0x809fcf0f0f3f6020,
    0x9fc08f0f0f203f6f,
    0x9fc08f0f0f203f60,
    0x809fcf0f0f3f7f6f,    /* 横6 */
    0x9fdfcf0f0f3f602f,
    0x9fdfcf0f0f203f6f,    
    0x9fc08f0f0f3f7f6f,
    0x9f7f4f000020000f,    /* 横7 */
    0x000080000f1fdf6f,
    0x80000f00003fdf4f,
    0x1f7fc0000f00002f,
    0x9fdfcf0f0f3f7f6f,    /* 横8 */
    0x9fdfcf0f0f3f7f60,
    0x9fdfcf0f0f3f7f60,
    0x9fdfcf0f0f3f7f60,
    0x9fdfcf0f0f3f602f,    /* 横9 */
    0x809fcf0f0f3f7f6f,
    0x9fc08f0f0f3f7f6f,
    0x9fdfcf0f0f203f6f}};

画像の2×2の4マスを正方形の1ブロックとみて、2本の対角線で分割し、反時計回りに9時の方角から、1、2、4、8とした。
全部埋めれば16進数でfとなる。
1つのピースは1を除けば3×5の15ブロックで、16進数15桁で表すことが可能となる。
最後の1桁は使うブロック数としましたので、unsigned long long型で間に合いました。
1つのピースは最大で縦で4パターン、横で4パターンの8パターンの可能性があり、全てデータとして用意しました。
但し、対称性があって既に調べたものを調べ直すのは馬鹿らしいので、最後の桁を0として、データは存在するが利用しないというようなフラグを立てました。

このデータ作りに何日か費やしました。
データの入力ミスは、コンパイラには解りませんので、出来上がったデータは、形として出力させて、正しいかを確認する作業が続きます。

データが完成してしまえば、後はコーディングだけです。

このデータの構造にしようと決めたのは、これならばANDを取れば当たり判定が容易だと考えたからです。

多重ネストになるのは、ちょっと嫌だったので、再帰処理にしました。

再帰処理
unsigned char map[9][11];
int l[10];
int X[10],Y[10],V[10],R[10];

int put_tate(int n, int m, int x, int y)
{
    int i,j,f;
    unsigned long long buf1,buf2;

    buf1 = num[0][4*n+m];
    f = buf1%16;
    buf1 /= 16;
    buf2 = buf1;
    if ( f == 0 ) return 0;
    for (j=0; j<5; j++)
    for (i=0; i<f/5; i++) {
        if ( x+i > 8 || y+j > 10 || ( map[x+i][y+j] & buf1%16 ) > 0 ) return 0;
        buf1 /= 16;
    }
    for (j=0; j<5; j++)
    for (i=0; i<f/5; i++) {
        map[x+i][y+j] += buf2%16;
        buf2 /= 16;
    }
    X[n]=x;
    Y[n]=y;
    R[n]=m;
    V[n]=0;

    return 1;
}

int put_yoko(int n, int m, int x, int y)
{
    int i,j,f;
    unsigned long long buf1,buf2;

    buf1 = num[1][4*n+m];
    f = buf1%16;
    buf1 /= 16;
    buf2 = buf1;
     if ( f == 0 ) return 0;
    for (j=0; j<f/5; j++)
    for (i=0; i<5; i++) {
        if ( x+i > 8 || y+j > 10 || ( map[x+i][y+j] & buf1%16 ) > 0 ) return 0;
        buf1 /= 16;
    }
    for (j=0; j<f/5; j++)
    for (i=0; i<5; i++) {
        map[x+i][y+j] += buf2%16;
        buf2 /= 16;
    }
    X[n]=x;
    Y[n]=y;
    R[n]=m;
    V[n]=1;

    return 1;
}

int clear_map()
{
    int i,j;

    for (j=0; j<11; j++)
    for (i=0; i<9; i++) map[i][j] = 0;

    return 1;
}

int save_map(char *buf)
{
    int i;

    for (i=0; i<99; i++) buf[i] = map[i/11][i%11];
    return 0;
}

int load_map(char *buf)
{
    int i;

    for (i=0; i<99; i++) map[i/11][i%11] = buf[i];
    return 0;
}

int next_number(int n)
{
    int x,y,r,f,i;
    char buf[99];

    if ( n < 10 ) {
        save_map(buf);
        for (x=0; x<9; x+=2)
        for (y=0; y<7; y+=2)
        for (r=0; r<4; r++) {
            f = put_tate(l[n],r,x,y);
            if ( f == 1 ) {
                printf("%1dT%02d%02d%1d ",l[n],x,y,r);
                next_number(n+1);
                load_map(buf);
                printf("\b\b\b\b\b\b\b\b");
            }
        }
        for (x=0; x<5; x+=2)
        for (y=0; y<11; y+=2)
        for (r=0; r<4; r++) {
            f = put_yoko(l[n],r,x,y);
            if ( f == 1 ) {
                printf("%1dY%02d%02d%1d ",l[n],x,y,r);
                next_number(n+1);
                load_map(buf);
                printf("\b\b\b\b\b\b\b\b");
            }
        }
        printf("        \b\b\b\b\b\b\b\b");
    } else {
        printf("\r");
        for (i=0; i<10; i++) printf("%d=(%d,%d,%d,%d) ",i,X[i],Y[i],V[i],R[i]);
        printf("\n");
        return 0;
    }
}

後は、事前に解っている情報があれば、それを固定して、next_number()関数を必要なところから始めれば良い。

例えば、事前情報が一切ないならば、

int main()
{
    l[0]=0,l[1]=1,l[2]=2,l[3]=3,l[4]=4,l[5]=5,l[6]=6,l[7]=7,l[8]=8,l[9]=9;
    next_number(0);

    return EXIT_SUCCESS;
}

と、置くピースの順番(配列l)だけ指定して、0番目からすれば良い。

自分は、0は、必ず1か7と組み合わせて、枠内の縁に接する必要があることを考慮して、

int main()
{
    l[0]=0,l[1]=1,l[2]=2,l[3]=3,l[4]=4,l[5]=5,l[6]=6,l[7]=7,l[8]=8,l[9]=9;

    printf("CHECK #1\n");
    clear_map();
    put_tate(0,0,0,0);
    put_yoko(1,0,0,2);
    next_number(2);

    printf("CHECK #2\n");
    clear_map();
    put_tate(0,0,0,2);
    put_yoko(1,0,0,4);
    next_number(2);

    printf("CHECK #3\n");
    clear_map();
    put_tate(0,0,0,4);
    put_yoko(1,0,0,6);
    next_number(2);

    printf("CHECK #4\n");
    clear_map();
    put_tate(0,0,0,6);
    put_yoko(1,0,0,8);
    next_number(2);

    printf("CHECK #5\n");
    clear_map();
    put_yoko(0,1,0,8);
    put_tate(1,0,2,6);
    next_number(2);

    printf("CHECK #6\n");
    clear_map();
    put_yoko(0,1,2,8);
    put_tate(1,0,4,6);
    next_number(2);

    printf("CHECK #7\n");
    clear_map();
    put_yoko(0,1,4,8);
    put_tate(1,0,6,6);
    next_number(2);

    l[0]=0,l[1]=7,l[2]=1,l[3]=2,l[4]=3,l[5]=4,l[6]=5,l[7]=6,l[8]=8,l[9]=9;

    printf("CHECK #8\n");
    clear_map();
    put_tate(0,0,0,0);
    put_yoko(7,0,0,0);
    put_yoko(1,1,2,0);
    next_number(3);

    printf("CHECK #9\n");
    clear_map();
    put_tate(0,0,0,2);
    put_yoko(7,0,0,2);
    next_number(2);

    printf("CHECK #10\n");
    clear_map();
    put_tate(0,0,0,4);
    put_yoko(7,0,0,4);
    next_number(2);

    printf("CHECK #11\n");
    clear_map();
    put_tate(0,0,0,6);
    put_yoko(7,0,0,6);
    next_number(2);

    printf("CHECK #12\n");
    clear_map();
    put_yoko(0,1,0,8);
    put_tate(7,0,0,6);
    put_tate(1,1,0,4);
    next_number(3);
    
    printf("CHECK #13\n");
    clear_map();
    put_yoko(0,1,2,8);
    put_tate(7,0,2,6);
    next_number(2);

    printf("CHECK #14\n");
    clear_map();
    put_yoko(0,1,4,8);
    put_tate(7,0,4,6);
    next_number(2);

    return EXIT_SUCCESS;
}

CHECK #1からCHECK #14までで、0の場所を変えながら、それを検索させました。

結果は、

CHECK #1
CHECK #2
CHECK #3
CHECK #4
CHECK #5
CHECK #6
CHECK #7
CHECK #8
CHECK #9
CHECK #10
CHECK #11
CHECK #12
0=(0,8,1,1) 1=(0,4,0,1) 2=(2,0,0,2) 3=(6,6,0,0) 4=(6,2,0,0) 5=(2,4,1,0) 6=(0,0,0,0) 7=(0,6,0,0) 8=(4,0,1,0) 9=(4,6,0,0)
0=(0,8,1,1) 1=(0,4,0,1) 2=(2,0,0,2) 3=(6,6,0,0) 4=(6,2,0,0) 5=(2,4,1,0) 6=(4,6,0,1) 7=(0,6,0,0) 8=(4,0,1,0) 9=(0,0,0,1)
0=(0,8,1,1) 1=(0,4,0,1) 2=(4,6,0,2) 3=(0,0,0,1) 4=(6,2,0,0) 5=(2,4,1,0) 6=(2,0,0,0) 7=(0,6,0,0) 8=(4,0,1,0) 9=(6,6,0,0)
0=(0,8,1,1) 1=(0,4,0,1) 2=(4,6,0,2) 3=(0,0,0,1) 4=(6,2,0,0) 5=(2,4,1,0) 6=(6,6,0,1) 7=(0,6,0,0) 8=(4,0,1,0) 9=(2,0,0,1)
0=(0,8,1,1) 1=(0,4,0,1) 2=(2,4,1,2) 3=(0,0,0,1) 4=(6,2,0,0) 5=(4,6,0,0) 6=(2,0,0,0) 7=(0,6,0,0) 8=(4,0,1,0) 9=(6,6,0,0)
0=(0,8,1,1) 1=(0,4,0,1) 2=(2,4,1,2) 3=(0,0,0,1) 4=(6,2,0,0) 5=(4,6,0,0) 6=(6,6,0,1) 7=(0,6,0,0) 8=(4,0,1,0) 9=(2,0,0,1)
0=(0,8,1,1) 1=(0,4,0,1) 2=(2,4,1,2) 3=(6,6,0,0) 4=(6,2,0,0) 5=(2,0,0,0) 6=(0,0,0,0) 7=(0,6,0,0) 8=(4,0,1,0) 9=(4,6,0,0)
0=(0,8,1,1) 1=(0,4,0,1) 2=(2,4,1,2) 3=(6,6,0,0) 4=(6,2,0,0) 5=(2,0,0,0) 6=(4,6,0,1) 7=(0,6,0,0) 8=(4,0,1,0) 9=(0,0,0,1)
CHECK #13
0=(2,8,1,1) 1=(8,4,0,0) 2=(6,6,0,2) 3=(6,0,0,0) 4=(0,2,0,2) 5=(2,4,1,2) 6=(4,0,0,2) 7=(2,6,0,0) 8=(0,0,1,0) 9=(0,6,0,2)
0=(2,8,1,1) 1=(8,4,0,0) 2=(6,6,0,2) 3=(6,0,0,0) 4=(0,2,0,2) 5=(2,4,1,2) 6=(0,6,0,3) 7=(2,6,0,0) 8=(0,0,1,0) 9=(4,0,0,3)
0=(2,8,1,1) 1=(8,4,0,0) 2=(2,4,1,0) 3=(6,0,0,0) 4=(0,2,0,2) 5=(6,6,0,0) 6=(4,0,0,2) 7=(2,6,0,0) 8=(0,0,1,0) 9=(0,6,0,2)
0=(2,8,1,1) 1=(8,4,0,0) 2=(2,4,1,0) 3=(6,0,0,0) 4=(0,2,0,2) 5=(6,6,0,0) 6=(0,6,0,3) 7=(2,6,0,0) 8=(0,0,1,0) 9=(4,0,0,3)
CHECK #14

0を縦向きで置いたケース、#1から#7までに、解は一つも見つからず、
0を横向きで置いて、7を結合させたケースだけで、解が12通り、
但し、2と5、6と9、それぞれの入れ替えを含む解が見つかる。

もし、0の上下、裏表を固定しなければ、トータル48通りの解があったということです。

2と5、6と9、上下、裏表、これらの入れ替えを同じものと考えるならば、解はたったの3通りしかないということでもあります。







画像自体は、HTML5とJavascriptで描いています。

ただ、このプログラムの構造上、綺麗にメッシュ状になる解しか探索しませんので、それ以外の解は見つけてくれません。

もし、それらまで見つけるようなものを書くと、
ピースの面積を4倍にする。
探索する範囲は同じだが、メッシュ状以外も探索させるため、量が4倍になる。
また、0の場所は固定したとしても、1や7をはめ込まない解も存在する可能性があるため、事前の枝刈りがほぼ出来ない。
1つ場所を固定すると単純に10倍速くなると考えることが出来る。
2つ場所を固定すると100倍、3つ場所を固定すると1000倍という感じです。

メッシュ以外も探索させるとなると、ピースの構造上16倍遅くなり、更に枝刈りが全くといっていいほど出来ないので100倍で、プログラム的に1600倍くらい時間が掛かる単純計算となってしまう。

さて、メッシュ状の解の個数が解ったので、このパズルの難しさを考えてみる。

あくまでも単純計算ね。

10ピースあるので、場所は、10!=3628800
向きが4、表裏が2で、10^8=100000000
掛け合わせて、362880000000000
母数が362兆8800億通りだとすると、解は3×16=48
7兆56億分の1の難しさなのだろうか。

数学的に、横向きが3ピース、残りの7ピースが縦向きとなることが解りましたね。

横向きの3ピースを固定すると18通りだけで、
横向きの3ピースの場所は、3!=6
残り7ピースの場所は、7!=5040
向きが2、裏表が2で、10^4=10000
掛け合わせて、302400000
3億240万分の48、
630万分の1の難しさになりました。

3種類の解を眺めてみると、8、4、2または5の3つのピースの位置関係が固定されていることに気が付きますね。

こういう情報があれば、難しさは格段に減っていくこととなるだろう。


ではでは
 

 


Viewing all articles
Browse latest Browse all 5376

Trending Articles