2016-02-24 205 views
3

如何在Prolog中圍繞其中心點旋轉4 x 4矩陣?我可以在4×4矩陣的情況下簡單地重新排列元素,但是如何爲像N x N這樣的一般情況做到這一點?在Prolog中旋轉矩陣

回答

2

你想要的是倒不矩陣轉置...但差不多!

 
:- use_module(library(clpfd)). 

matrix_rotated(Xss, Zss) :- 
    transpose (Xss, Yss), 
    maplist (reverse , Yss, Zss). 

樣品查詢:

 
?- matrix_rotated([[a1,a2,a3,a4], 
        [b1,b2,b3,b4], 
        [c1,c2,c3,c4]], Xss). 
Xss = [[c1,b1,a1], 
     [c2,b2,a2], 
     [c3,b3,a3], 
     [c4,b4,a4]]. 
+1

至少要將旋轉加180度:'reverse(M,R0),maplist(reverse ,R0,R)'。另外還要區分順時針和逆時針旋轉90度。 –

+0

@Boris。(2)我明白了。你有一個好名字嗎? (1)你是什麼意思?我用手寫下了一個完整的旋轉(4步)作爲測試用例,'matrix_rotated/2'很好地匹配了。那麼爲什麼第二個「反向」呢? (是的,在我寫測試用例之前,我沒有寫任何代碼,歡呼!) – repeat

+1

@Boris。它當然值得!運行時不是我主要關心的問題:「*過早優化是根... *」 – repeat

1

如上所述,transpose/2不是正確的解決方案。我已經編寫了一些東西(可能有點太籠統),它允許根據單元格數量來指定旋轉量以向右移動。移位默認爲1,但是我注意到爲了獲得90°旋轉,移位在傳遞到內部幀時正在減小。將匿名變量傳遞給matrix_rotate/3具有使用當前內部幀大小的效果。然後順時針旋轉90°。

/** <module> matrix_rotate 
* 
* answer to http://stackoverflow.com/questions/35594027/rotate-a-matrix-in-prolog 
* -------- 
* 
* source file /home/carlo/prolog/matrix_rotate.pl 
* created at mer feb 24 16:43:50 2016 
* 
* @author carlo 
* @version 0.9.9 
* @copyright carlo 
* @license LGPL v2.1 
*/ 

:- module(matrix_rotate, 
    [matrix_rotate/2 
    ,matrix_rotate/3 
    ,matrix_allocate/4 
    ,matrix_row_col_element/4 
    ,rowcol/3 
    ]). 

:- meta_predicate matrix_allocate(3, +,+,-). 

matrix_allocate(Pred, NRows, NCols, Mat) :- 
    bagof(Vs, Row^(
     between(1, NRows, Row), 
     bagof(C, Col^(between(1, NCols, Col), call(Pred, Row, Col, C)), Vs) 
    ), Mat). 

empty(_R,_C,_). 
rowcol(R, C, (R,C)). 

matrix_rotate(Mat, Rot) :- 
    matrix_rotate(Mat, 1, Rot). 
matrix_rotate(Mat, Shift, Rot) :- 
    matrix_is_square(Mat, Size), 
    matrix_allocate(empty, Size, Size, Rot), 
    frames_shift(Mat, Shift, 1, Rot), 
    ( Size mod 2 =:= 1 
    -> Cen is Size // 2 + 1, 
     matrix_row_col_element(Mat, Cen, Cen, E), 
     matrix_row_col_element(Rot, Cen, Cen, E) 
    ; true 
    ). 

frames_shift(Mat, Shift, Frame, Rot) :- 
    length(Mat, Size), 
    Frame =< Size // 2, 
    ( integer(Shift) 
    -> ActualShift = Shift 
    ; ActualShift is Size - (Frame - 1)*2 - 1 
    ), 
    frame(Mat, Frame, L), 
    shift_right(L, ActualShift, S), 
    frame(Rot, Frame, S), 
    F is Frame+1, 
    !, frames_shift(Mat, Shift, F, Rot). 
frames_shift(_Mat, _Shift, _Frame, _Rot). 

matrix_is_square(Mat, Size) :- 
    length(Mat, Size), 
    forall(member(Row, Mat), length(Row, Size)). 

matrix_row_col_element(Mat, Row, Col, El) :- 
    nth1(Row, Mat, Cells), 
    nth1(Col, Cells, El). 

shift_right(List, Shift, Shifted) :- 
    length(Post, Shift), 
    append(Pre, Post, List), 
    append(Post, Pre, Shifted). 

frame(Mat, N, Frame) :- 
    length(Mat, S), 
    T is N, B is S-N+1, 
    L is N, R is S-N+1, 
    matrix_range_elements(Mat, T,T, L,R-1, Top), 
    matrix_range_elements(Mat, T,B-1, R,R, Right), 
    matrix_range_elements(Mat, B,B, R,L+1, Bottom), 
    matrix_range_elements(Mat, B,T+1, L,L, Left), 
    append([Top, Right, Bottom, Left], Frame). 

matrix_range_elements(Mat, RStart, RStop, CStart, CStop, Elems) :- 
    bagof(E, matrix_element_between(Mat, RStart, RStop, CStart, CStop, E), Elems). 

matrix_element_between(Mat, RStart, RStop, CStart, CStop, Elem) :- 
    signed_between(RStart, RStop, R), 
    signed_between(CStart, CStop, C), 
    matrix_row_col_element(Mat, R, C, Elem). 

signed_between(Start, Stop, I) :- 
    A is Start, B is Stop, 
    (B < A -> between(B, A, N), I is A+B-N ; between(A, B, I)). 

這段代碼突出了一些有用的通用模式,用於處理列表列表中的矩陣。還有一些困難,太...

用法示例:

?- matrix_allocate(rowcol,4,4,M),matrix_rotate(M,_,R),maplist(writeln,R). 
[(4,1),(3,1),(2,1),(1,1)] 
[(4,2),(3,2),(2,2),(1,2)] 
[(4,3),(3,3),(2,3),(1,3)] 
[(4,4),(3,4),(2,4),(1,4)] 
M = [[(1, 1), (1, 2), (1, 3), (1, 4)], [(2, 1), (2, 2), (2, 3), (2, 4)], [(3, 1), (3, 2), (3, 3), (3, 4)], [(4, 1), (4, 2), (4, 3), (4, 4)]], 
R = [[(4, 1), (3, 1), (2, 1), (1, 1)], [(4, 2), (3, 2), (2, 2), (1, 2)], [(4, 3), (3, 3), (2, 3), (1, 3)], [(4, 4), (3, 4), (2, 4), (1, 4)]]. 

?- matrix_allocate(rowcol,3,3,M),matrix_rotate(M,_,R),maplist(writeln,R). 
[(3,1),(2,1),(1,1)] 
[(3,2),(2,2),(1,2)] 
[(3,3),(2,3),(1,3)] 
M = [[(1, 1), (1, 2), (1, 3)], [(2, 1), (2, 2), (2, 3)], [(3, 1), (3, 2), (3, 3)]], 
R = [[(3, 1), (2, 1), (1, 1)], [(3, 2), (2, 2), (1, 2)], [(3, 3), (2, 3), (1, 3)]]. 
+3

移調是不一樣的旋轉,是嗎? –

+0

@Boris:是的,我的錯......我在製作可重複使用的東西......我會在完成時刪除上面的代碼 – CapelliC

+0

爲什麼你需要'matrix_is_square/2'成立? – repeat

3

有人爲wrong on the internet,使值班電話。 畢竟我們必須服從Cunningham's Law

這個答案充分利用library(clpfd)SWI-Prolog找到。

:- use_module(library(clpfd)). 

rotate_clockwise(Matrix, N, Rotated) :- 
    N_mod_4 #= N mod 4, 
    rotate_clockwise_(N_mod_4, Matrix, Rotated). 

rotate_clockwise_(0, M, M). 
rotate_clockwise_(1, M, R) :- 
    transpose(M, R0), 
    maplist(reverse, R0, R). 
rotate_clockwise_(2, M, R) :- 
    reverse(M, R0), 
    maplist(reverse, R0, R). 
rotate_clockwise_(3, M, R) :- 
    transpose(M, R0), 
    reverse(R0, R). 

可以旋轉通過翻轉兩次的長方形矩陣(嘗試一下用一本書或某事)。 根據您翻轉的兩個軸,您可以得到3個非平凡旋轉中的一個:如果我們同意「一次旋轉」是順時針90度,那麼您可以旋轉0,1,2或3次。在上面的代碼:

  • 倒裝沿着水平軸是reverse
  • 倒裝沿着垂直軸是maplist(reverse)
  • 倒裝沿左上 - 低級 - 右軸是transpose

至於爲什麼不定義順時針旋轉90度,然後多次旋轉:嗯,它實際上是更多的代碼! 有了上面的定義,無論我們給rotate_clockwise/3什麼有效的參數,它都做正確的旋轉,不會做不必要的翻轉,並保持呼叫者代碼的簡短。

?- rotate_clockwise([[a,b,c],[d,e,f]], -1, R). % once counter-clockwise 
R = [[c, f], [b, e], [a, d]]. 

?- rotate_clockwise([[a,b,c],[d,e,f]], 2, R). % 180 degrees 
R = [[f, e, d], [c, b, a]]. 

?- rotate_clockwise([[a,b,c],[d,e,f]], 168, R). % rotate once each hour, for a week 
R = [[a, b, c], [d, e, f]]. 

?- rotate_clockwise([[a,b,c],[d,e,f]], N, R). % enumerate all possibilities 
R = [[a, b, c], [d, e, f]], 
N mod 4#=0 ; 
R = [[d, a], [e, b], [f, c]], 
N mod 4#=1 ; 
R = [[f, e, d], [c, b, a]], 
N mod 4#=2 ; 
R = [[c, f], [b, e], [a, d]], 
N mod 4#=3. 

?- Matrix = [[a],[b]], 
    rotate_clockwise(Matrix, -1, R), 
    rotate_clockwise(Matrix, 3, R). % those two mean the same 
Matrix = [[a], [b]], 
R = [[a, b]]. 
+1

非常酷! ... – mat

+1

@mat你知道嗎,我知道它:它是CLPFD,很酷,不是這個玩具的例子:)這是一個很好的決定,在庫中包含'轉置/ 2',順便說一句;一旦我不得不把'library(clpfd)'作爲一個依賴項,那麼絕對沒有理由不使用這個算法。其他亮點(對我個人而言)是'zcompare/3'和'chain/2'。 –

+1

這就是這種洞察力,擁抱它很酷!我們將慢慢而有條不紊地改變Prolog程序的寫法。現在仍然顯得不尋常的事情將在適當的時候成爲民間傳說。 – mat