色彩パズルを解く


35

Puzzling.SEの友人に次のパズルが投稿されました。この色彩パズルは常に解決可能ですか?エドガー・G。ここでプレイできます

パズルの説明

m x n3つの異なる色のタイルを含むグリッドがある場合、色が異なる場合は、隣接する2つのタイルを選択できます。これらの2つのタイルは、3番目の色、つまり、これら2つのタイルで表されない1つの色に変換されます。すべてのタイルが同じ色を持っている場合、パズルは解決されます。どうやら、1ができることを証明どちらも場合は、このパズルは、常に解決可能であることも3で割り切れるされません。mn

8x8 3色パズル

もちろん、これは解くアルゴリズムを求めます。このパズルを解決する関数またはプログラムを作成します。「副作用」を持つ関数(つまり、stdoutいくつかの厄介なデータ型の戻り値ではなく出力がオンになっている)は明示的に許可されていることに注意してください。

入出力

入力はm x n、整数12および3(または0、便利な場合12)で構成される行列になります。この入力は、適切な形式で入力できます。どちらmnしている>1あなたは、パズルが解決されていないと仮定して3で割り切れないと

その後、パズルを解きます。これには、「変換」される2つの隣接するタイルを繰り返し選択する必要があります(上記を参照)。これらのタイルの2つの座標を、解決アルゴリズムが実行した各ステップに対して出力します。これは、任意の健全な出力形式でもかまいません。座標の0から1のインデックス付けと、行または列のどちらを最初にインデックス付けするかを自由に選択できます。ただし、これを回答に記載してください。

元の8x8のケースでは、アルゴリズムは妥当な時間内に実行されるはずです。明示的に許可されていない、それを完全にけだもの強制、あなたのアルゴリズムがもとで実行する必要がありますつまりO(k^[m*(n-1)+(m-1)*n])k解決のために必要なステップの数。ただし、ソリューションは最適である必要はありません。リンクされた質問で与えられた証拠は、これを行う方法についてのアイデアを与えるかもしれません(例えば、最初に垂直に隣接するタイルのみを使用してすべての列を実行し、次にすべての行を実行します)

テストケース

これらのテストケースでは、座標は1から始まり、行に最初にインデックスが付けられます(MATLAB / Octaveおよびおそらく他の多くのものと同様)。

Input: 
[1 2]
Output: (result: all 3's)
[1 1],[1,2]


Input:
[ 1 2
  3 1 ]
Output: (result: all 1's)
[1 1],[2 1]        (turn left column into 2's)
[2 1],[2 2]        (turn right column into 3's)
[1 1],[1 2]        (turn top row into 1's)
[2 1],[2 2]        (turn bottom row into 1's)

Input:
[1 2 3 2
 3 2 1 1]

Output: (result: all 3's)
[1 1],[1 2] 
[1 3],[1 4] 
[1 2],[1 3] 
[1 1],[1 2] 
[1 2],[1 3] 
[1 1],[1 2]
[1 3],[1 4]
[2 1],[2 2]
[1 1],[2 1]
[1 2],[2 2]
[1 3],[2 3]
[1 4],[2 4]

必要に応じて、より大きなテストケースのペーストビンを投稿することもできますが、これで十分だと思います。


これのコードチャレンジバージョンを見てみたいと思います。目標は、最小限の総移動でパズルのセットを解決することです。
メゴ

@Megoこれは絶対に考えました。ただし、これがDFSまたはBFSに変わり、実行に永遠に時間がかかることが心配です。または、これを防ぐために、あいまいな制限のセット(「1時間以内に実行する必要がある」など、大規模なコンピューターを使用しているユーザーを支持するか、すべてのソリューションをテストする必要があります)。さらに、現在の課題には予備の回答がありませんので、ヒューリスティックなどを必要とするさらに難しいバージョンがより人気があるとは思わないでしょう...説明する。
-Sanchises

Luaでそれをやろうと思うが、それはあなたの324バイトのソリューションよりも長いかもしれない^^
Katenkyo

@Katenkyo見つけるための唯一の方法!あなたのソリューションを楽しみにしています。
Sanchises

あなたは少し悲しいことに待つ必要があります、あなたはブルートフォースソリューションを防止したので、私はluaで短いソリューションを見つける必要があります:p
Katenkyo

回答:


5

ルビー、266バイト

多かれ少なかれ、Octaveソリューションの単なる移植ですが、列ではなく行を最初に解決します。入力は配列の配列で、内側の配列は行です。出力移動は[row, column, row, column]です。テストスイート

->m{t=->v{v.size*v.inject(:+)%3}
s=->a,x,r{g=t[a]
(q=(r=0..a.size-2).find{|i|a[i]!=a[i+1]&&g!=a[i]}||r.find{|i|a[i]!=a[i+1]}
a[q,2]=[t[a[q,2]]]*2
p r ?[x,q,x,q+1]:[q,x,q+1,x])while[]!=a-[g]}
m.size.times{|i|s[m[i],i,1]}
m=m.shift.zip *m
m.size.times{|i|s[m[i],i,p]}}

説明のないゴルフ

->m{                                  # Start lambda function, argument `m`
  t=->v{v.size*v.inject(:+)%3}        # Target color function
  s=->a,x,r{                          # Function to determine proper moves
                                      #   a = row array, x = row index, r = horizontal
    g=t[a]                            # Obtain target color
    (
      q=(r=0..a.size-2).find{|i|      # Find the first index `i` from 0 to a.size-2 where...
        a[i]!=a[i+1]                  # ...that element at `i` is different from the next...
        &&g!=a[i]                     # ...and it is not the same as the target color
      } || r.find{|i|a[i]!=a[i+1]}    # If none found just find for different colors
      a[q,2]=[t[a[q,2]]]*2            # Do the color flipping operation
      p r ?[x,q,x,q+1]:[q,x,q+1,x]    # Print the next move based on if `r` is truthy
    ) while[]!=a-[g]}                 # While row is not all the same target color, repeat
m.size.times{|i|                      # For each index `i` within the matrix's rows...
  s[m[i],i,1]                         # ...run the solving function on that row
                                      #   (`r` is truthy so the moves printed are for rows)
}
m=m.shift.zip *m                      # Dark magic to transpose the matrix
m.size.times{|i|s[m[i],i,p]}}         # Run the solving function on all the columns now
                                      #   (`r` is falsy so the moves printed are for columns)

2つの非ゴルフ言語間の移植によって、約20%の違いが生じる可能性があることは興味深いことです。あなたは多分短い説明を追加することができます(特に3行目-私は密か以来、私は私の答えでそれを使用することができます願っています?intersectなどかさばるキーワードである)
Sanchises

@sanchises説明が追加されました。に関してはintersect、Rubyはfind基本的に関数を操作するため、私の動作方法を修正できるかどうかはわかりませんが、functionキーワードも同様にかさばります。
値インク

私はまだあなたの方法を実際に使用することができますfind-ありがとう!それでも、あなたに
勝つ

13

オクターブ、334 313バイト

挑戦は少し気が重いように見えるかもしれないので、私は私自身の解決策を提示します。私はこの方法が機能することを正式に証明しませんでした(アルゴリズムがループに巻き込まれないことを証明することになります)が、今のところ完全に機能し、15秒以内に100x100のテストケースを実行します。いくつかのバイトを節約して以来、すべての座標を返す関数ではなく、副作用を持つ関数を使用することにしました。座標は行優先、1ベースで、としてフォーマットされrow1 col1 row2 col2ます。入力色は0,1,2これでよりよく動作するので、代わりmodに使用する必要numelがありnnzます。ゴルフバージョン:編集:Kevin Lauの答えのテクニックを使用して、さらに数バイトを節約しました。

function P(p)
k=0;[m,n]=size(p);t=@(v)mod(sum(v)*numel(v),3);for c=1:n
p(:,c)=V(p(:,c));end
k=1;for r=1:m
p(r,:)=V(p(r,:));end
function a=V(a)
while any(a-(g=t(a)))
isempty(q=find(diff(a)&a(1:end-1)-g,1))&&(q=find(diff(a),1));
a([q q+1])=t(a([q q+1]));if k
disp([r q r q+1])
else
disp([q c q+1 c])
end;end;end;end

解法アルゴリズムのサンプルGIF:

ここに画像の説明を入力してください

ゴルフされていないバージョン:

function solveChromaticPuzzle(p)
[m,n]=size(p);                           % Get size
t=@(v)mod(sum(v)*numel(v),3);            % Target colour function
for c=1:n                                % Loop over columns
    p(:,c)=solveVec(p(:,c));             % Solve vector
end
for r=1:m                                % Loop over rows
    p(r,:)=solveVec(p(r,:));
end
    function a=solveVec(a)               % Nested function to get globals
        g=t(a);                          % Determine target colour
        while(any(a~=g))                 % While any is diff from target...
            % Do the finding magic. Working left-to-right, we find the
            % first pair that can be flipped (nonzero diff) that does not
            % have the left colour different from our goal colour
            q=min(intersect(find(diff(a)),find(a~=g)));
            if(isempty(q))               % In case we get stuck...
                q=find(diff(a),1);       % ... just flip the first possible
            end;
            a([q q+1])=t(a([q q+1]));    % Do the actual flipping.
            if(exist('r'))               % If we're working per row
                disp([r q r q+1])        % Print the pair, using global row
            else
                disp([q c q+1 c])        % Print the pari, using global col
            end
        end
    end
end

ただ、気づいたが、私の名前はケニー・ラウではありません...他のユーザーだとそのユーザー名は、特に私がいないケニーだと述べている
バリューインク

7

Lua、594 575 559バイト

警告このゴルフを終える前にまだ多くの仕事があります!少なくとも500バイト未満で対応できるはずです。今のところ、それが機能する最初のソリューションであり、私はまだそれに取り組んでいます。

完了したら、完全な説明を提供します。

function f(t)s=#t a=","for i=1,s do p=t[i]for i=1,s
do p.Q=p.Q and p.Q+p[i]or p[i]end p.Q=(p.Q*#p)%3 for i=1,s do for j=1,#p-1 do
x=p[j]y=p[j+1]u=x~=y and(p.S and p.R==p.S or x~=p.Q)v=(x+y)*2p[j]=u and v%3or x
p[j+1]=u and v%3or y print(i..a..j,i..a..j+1)end
p.R=p.S p.S=table.concat(p)end end
for i=1,s do Q=Q and Q+t[i][1]or t[i][1]end Q=(Q*s)%3 for i=1,s
do for j=1,s-1 do p=t[j]q=t[j+1]x=p[1]y=q[1]u=x~=y and(S and R==S or x~=Q)v=(x+y)*2
for k=1,#p do p[k]=u and v%3or x q[k]=u and v%3or y
print(j..a..k,j+1..a..k)end Y=Y and Y..x or x end
R=S S=Y end end

5

錆、496 495バイト

悲しいことに、私はOPを倒すことはできませんが、Rustの回答については、バイトカウントに非常に満足しています。

let s=|mut v:Vec<_>,c|{
let p=|v:&mut[_],f,t|{
let x=|v:&mut[_],i|{
let n=v[i]^v[i+1];v[i]=n;v[i+1]=n;
for k in f..t+1{print!("{:?}",if f==t{(k,i,k,i+1)}else{(i,k,i+1,k)});}};
let l=v.len();let t=(1..4).find(|x|l*x)%3==v.iter().fold(0,|a,b|a+b)%3).unwrap();
let mut i=0;while i<l{let c=v[i];if c==t{i+=1;}else if c==v[i+1]{
let j=if let Some(x)=(i+1..l).find(|j|v[j+1]!=c){x}else{i-=1;i};x(v,j);}else{x(v,i);}}t};
p(&mut (0..).zip(v.chunks_mut(c)).map(|(i,x)|{p(x,i,i)}).collect::<Vec<_>>(),0,c-1usize)};

入力:数値のベクトルと列の数。例えば

s(vec!{1,2,1,3},2);

出力

 (row1,col1,row2,col2)

コマンドラインに。

最初にすべての行を解決してから、結果の列を一度だけ解決しますが、すべての列のステップを印刷します。したがって、実際には非常に効率的です:-P。

書式設定あり:

let s=|mut v:Vec<_>,c|{  
    let p=|v:&mut[_],f,t|{     // solves a single row/column
        let x=|v:&mut[_],i|{   // makes a move and prints it 
            let n=v[i]^v[i+1]; // use xor to calculate the new color
            v[i]=n;
            v[i+1]=n;
            for k in f..t{
                print!("{:?}",if f==t{(k,i,k,i+1)}else{(i,k,i+1,k)});
            }
        };
        let l=v.len();
        // find target color
        // oh man i am so looking forward to sum() being stabilized
        let t=(1..4).find(|x|(l*x)%3==v.iter().fold(0,|a,b|a+b)%3).unwrap();
        let mut i=0;
        while i<l{
            let c=v[i];
            if c==t{             // if the color is target color move on
                i+=1;
            }else if c==v[i+1]{ // if the next color is the same
                                // find the next possible move
                let j=if let Some(x)=(i+1..l).find(|j|v[j+1]!=c){x}else{i-=1;i};
                x(v,j);
            }else{              // colors are different so we can make a move
                x(v,i);         
            }
        }
        t
    };
    // first solve all rows and than sovle the resulting column c times 
    p(&mut (0..).zip(v.chunks_mut(c)).map(|(i,x)|p(x,i,i)).collect::<Vec<_>>(),0,c-1usize)
};

編集: セミコロンを節約するソリューションの色を返すようになりました^^


5

Befunge197 368 696 754バイト


(はい、私はいくつかのリバースコードゴルフをやっています、バイトが多いほど良いです)


このアルゴリズムをBefungeで書くのは難しいかもしれないし、楽しいかもしれないと思っていました

コミュニティプログラムにしたいので、もし誰かがそれに取り組みたいと思ったら、そうしてください。

最後に、私はこれまですべてを一人で作ったので、私は自分で仕上げます(ほぼ完了しました)


まだやっていること:トロル型のコード

&:19p&:09p:v:p94g92g90  <
 v94+1:g94&_$59g1+59p1-:|
 >p59gp1-: ^    vp95g93$<
v94\-1<v:g<     >  v
>g:1+v^_$v^90p94g92<
v5p94<   3>1+59p   ^
>9gg+\:^ %g   v93:g95<           v3_2         v
v1pg95g94<^95<>g-v>$v^           v ^-%3*2\gg9<
>9g39g+59g-1-|v-1_^ #^pg95+g92g90<1_09g:29g+5^
       ;  >  >  09g:29g+59gg\3%-# !^v         <
          ^p95<                  ^  <
     v  p96-1+g90g92<
     v                  p95_@
            >59g1+:39g-19g-^
     v    >p 79g:59gg\1+59gp$$$$$29g49pv
    > p59g^ |<<<<<<<<<<<<<<<<<<!-g96g94<
>:79^>29g49p>69g1+59gg49g:59gg\1+49p- v
^\-\6+gg95+1\g< v         !-g96:<-1g94_^
>"]",52*,::59g^v_::1+59gg\59gg-v^ <
^ .-g93g95,.-g<>:69g- v  v-g96:_1+^
>+" [,]",,,\29^       >#v_$:49g2-v
^1:.-g93g95,.-g92\,"[ ":<        <

(はい、それはトロールです、私を信じて)


基本的に、配列を読み取り、行を解決するための移動を計算します。入力は次のとおりです。

(number of rows) (number of columns) 1 2 3 1 1 3 2 1 2 ....

(配列全体がリストとして渡されます[row1、row2、row3、…])

出力は

[col row],[col',row']
[col2 row2],[col2',row2']
...

rowsとcolsは両方とも0から始まります。


行が解決されたので、ほぼ完了です!やった!


説明:(後で更新されます)

画像

したがって、5つの主要な部分があります。

  • 緑色の最初の行は、入力行を読み取り、配列の1行を書き込みます
  • オレンジ色の2番目のものは、配列の次の行に渡されます。
  • 3番目の青色の行は合計
  • 4番目はホットピンクで、合計のモジュラス3を取得し、関係する行の右側に保存して、次の行に移動します
  • 最後に、赤で、以前に計算された数値からターゲットカラーを計算する部分。この部分は本当に馬鹿げているので、おそらく書き直す必要がありますが、どうすればいい方法でそれを行うことができるかわかりませんでした(その部分だけで197バイトから368に渡されました)

灰色の部分は初期化です


結合するtoボックスを見つけるモジュールの詳細な説明を次に示します(これは、ここでコード化されています)

                                       B
            @                          v
            |                  !-g96g94<
ENTRY>29g49p>69g1+59gg49g:59gg\1+49p- v
                v         !-g96:<-1g94_^
               v_::1+59gg\59gg-v^ <
               >:69g- v  v-g96:_1+^
                      >#v_$:49g2-v
                    CALL<        <

CALL部分は、命令ポインタが別のモジュールに移動して、ボックスに結合するときです。「B」エントリを介してこのモジュールに戻ります

疑似コードは次のとおりです(currentxは配列の読み取りに関連しています)。

    69g1+59gg  // read target color
    49g:59gg\1+49p // read current color and THEN shift the currentx to the next box
    if ( top != top ){  // if the current color is bad
        49g1-          //  put the current place  (or currentx - 1) in the stack
        While:
            if ( :top != 69g ){   // if you didn't reach the end of the list
                ::1+              // copy twice, add 1
                if ( 59gg == \59gg ){ // if the next color is the same than current
                   1+                // iterate
                   break While;
                }
            }

        : // copies j (the previous raw iterator)
        if ( top == 69g ){  // if you reached the end of the row (which mean you can't swap with the color after that point)
            $:              // discard j's copy and copy target
            49g2-           // put the place just before the color change on the stack
            combine_with_next;
        } else {
            combine_with_next;
        }
        29g49p   // back to the beginning of the row (there was some changes int the row)
    }

    if ( 49g != 69g ) // if you didn't reach the end of the list
        break For:

テストしたい場合、リンクされたインタープリターを使用たい場合、配列を格納するのに十分なスペースがあるように、後続のスペースと後続の新しい行を配置する必要があることに注意しください。22 +末尾の行としての入力の行数、および34 + 1行の末尾のスペースとしての列の数は問題ないはずです。


好奇心が強い、なぜこれが競合しないのですか?
バリューインク

この部分のため:「私はそれがコミュニティプログラムになりたい」。そうでなければ、ちょっと
だまさ

結果は197バイトですが、Windowsで作業していますか?(だけでは\r\nなく数え\nますか?)
かてんきょう


最後に私がそれを作った唯一の人なら、私は言及を消しますが、私はそうしないことを願っています
-Maliafo

2

C、404バイト

私の最初のコードゴルフは、それがどのようになったかにかなり満足しています。しかし、あまりにも長い時間がかかりました。これは完全に標準的なCではなく、特別なフラグなしでgccの下でコンパイルする(警告を無視する)だ​​けのものです。ネストされた関数がそこにあります。関数fは次元mn最初の引数として受け取り、3番目の引数はサイズm× n(最初に行でインデックス付けされた)の配列への(intポインター)を受け取ります。他の引数は仮引数です。渡す必要はありません。変数を宣言する際にバイトを節約するためだけにあります。変更された各ペアをフォーマットrow1,col1:row1,col1;でSTDOUTに書き込み、セミコロンでペアを区切ります。0ベースのインデックスを使用します。

#define A a[i*o+p
#define B A*c
f(m,n,a,i,j,o,p,b,c,d)int*a;{
int t(x){printf("%d,%d:%d,%d;",b?i:c+x,b?c+x:i,b?i:c+1+x,b?c+1+x:i);}
o=n;p=b=1;for(;~b;b--){
for(i=0;i<m;i++){c=j=0;
for(;j<n;)c+=A*j++];d=c*n%3;
for(j=0;j<n-1;j++) 
while(A*j]^d|A*j+p]^d){
for(c=j;B]==B+p];c++);
if(c<n-2&B]==d&2*(B+p]+A*(c+2)])%3==d)
B+p]=A*(c+2)]=d,t(1);else
B]=B+p]=2*(B]+B+p])%3,
t(0);}}o=m;p=m=n;n=o;o=1;}}

個々の行/列を解決するために、OPとは少し異なるアルゴリズムを使用しました。次のようなものになります(擬似コード):

for j in range [0, rowlength):
    while row[j] != targetCol or row[j+1] != targetCol:
        e=j
        while row[e] == row[e+1]:
            e++             //e will never go out of bounds
        if e<=rowLength-3 and row[e] == targetCol 
                and (row[e+1] != row[e+2] != targetCol):
            row.changeColor(e+1, e+2)
        else:
            row.changeColor(e, e+1)

for(;~b;b--)ループ実行はちょうど2倍、2回目のパスでは、代わりに、行の列を解決します。これは交換することによって行われるnm、との値を変更oし、pアレイをアドレスへのポインタ演算で使用されています。

これは、テストメインを使用して、未実行のバージョンであり、すべての移動後に配列全体を印刷します(Enterキーを押してステップ1ターン)。

#define s(x,y)b?x:y,b?y:x
#define A a[i*o+p
#define B A*e
f(m,n,a,i,j,o,p,b,c,d,e)int*a;{

    int t(x){
        printf("%d,%d:%d,%d;\n",s(i,e+x),s(i,e+1+x));
        getchar();
        printf("\n");
        for(int i2=0;i2<(b?m:n);i2++){
            for(int j2=0;j2<(b?n:m);j2++){
                printf("%d ",a[i2*(b?n:m)+j2]);
            }
            printf("\n");
        }
        printf("\n");
    }

    printf("\n");
    b=1;
    for(int i2=0;i2<(b?m:n);i2++){
        for(int j2=0;j2<(b?n:m);j2++){
            printf("%d ",a[i2*(b?n:m)+j2]);
        }
        printf("\n");
    }
    printf("\n");

    o=n;p=1;
    for(b=1;~b;b--){
        for(i=0;i<m;i++){
            c=0;
            for(j=0;j<n;j++) c+= a[i*o+p*j];
            d=0;
            d = (c*n)%3;
            for(j=0;j<n-1;j++) {
                while(a[i*o+p*j]!=d||a[i*o+p*j+p]!=d){
                    for(e=j;a[i*o+p*e]==a[i*o+p*e+p];e++);
                    if(e<=n-3 && a[i*o+p*e]==d 
                            && 2*(a[i*o+p*e+p]+a[i*o+p*(e+2)])%3==d){
                        a[i*o+p*e+p]=a[i*o+p*(e+2)]=d;
                        t(1);
                    }else{
                        a[i*o+p*e]=a[i*o+p*e+p] = 2*(a[i*o+p*e]+a[i*o+p*e+p])%3;
                        t(0);
                    }
                }
            }
        }
        o=m;p=m=n;n=o;o=1;
    }
}

main(){
    int g[11][11] = 
    {
        {0,2,1,2,2,1,0,1,1,0,2},
        {2,1,1,0,1,1,2,0,2,1,0},
        {1,0,2,1,0,1,0,2,1,2,0},
        {0,0,2,1,2,0,1,2,0,0,1},
        {0,2,1,2,2,1,0,0,0,2,1},
        {2,1,1,0,1,1,2,1,0,0,2},
        {1,0,2,1,0,1,0,2,2,1,2},
        {0,0,2,1,2,0,1,0,1,2,0},
        {1,2,0,1,2,0,0,2,1,2,0},
        {2,1,1,0,1,1,2,1,0,0,2},
        {0,2,1,0,1,0,2,1,0,0,2},
    };
    #define M 4
    #define N 7
    int grid[M][N];
    for(int i=0;i<M;i++) {
        for(int j=0;j<N;j++) {
            grid[i][j] = g[i][j];
        }
    }
    f(M,N,grid[0],0,0,0,0,0,0,0,0);
};

好奇心から:なぜ別のアルゴリズムを選択したのですか(バイト節約の観点から)?
-Sanchises

1
人々がさまざまなソリューションを思いついたときはもっと面白いと思います。いくつかの簡単なテストから、2つの方法のバイト数はほぼ同じだと推測しました。おそらくあなたのアルゴリズムも試して、もっと低くなるかどうかを確認します。
-Norg74

質問にコメントするのに十分な担当者がないため、ここに投稿します。各行をブルートフォースしてから、各列を個別に強制することは有効でしょうか?それは技術的には「完全に無理やり」ではなく、指定された時間の複雑さの限界よりも低くなければなりません。実際にそうすることを考えました。
-Norg74

強引な言及は「合理的な時間」の発言を強化することを意図していたので、t«O(...)として参照してください。ブルートフォースと合理的なアルゴリズムの間には灰色の領域があることを知っているので、パズルを解くために働いていると感じているのか、それとも「進行」にとらわれないDFSまたはBFSのわずかな変更であるのかについて、独自の判断を使用してください。
-Sanchises
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.