題目說明

在中國象棋中,雙方之「將」與「帥」不能碰面,假設棋盤上只剩「將」與「帥」二子 (約定用 A 表示「將」,B 表示「帥」),且被限制在己方之 3*3 正方格內移動。

請寫出一程式,輸出 A, B 所有合法位置,並要求只能用一變數。

 

解法與說明

這份題目由軟體亞洲研究院工程師 Matt Scott 提供。若不考慮為單一變數限制,非常容易解,若用初學的暴力硬破的話長這樣

 

#include <stdio.h>
int main()
{
    int x1, y1, x2, y2;
    for(x1=0; x1<=2; ++x1){
       for(y1=0; y1<=2; ++y1){
          for(x2=0; x2<=2; ++x2){
             for(y2=0; y2<=2; ++y2){
                /* x1 與 x2 不同時便為合理 */
                if(x1!=x2) printf("A(%d,%d), B(%d,%d)\n", x1, y1, x2, y2);
            }
          }
       }
    }
    return 0;
}

 

 為說明方便,以數字 1~9 表示 A, B 之座標,如下所示

AngryBird000.png  

這樣可只使用二個變數完成

#include <stdio.h>
int main()
{
    int A=1, B=1;
    for(A=1; A<=9; ++A){
       for(B=1; B<=9; ++B){
          if(A%3!=B%3) printf("A:%d, B:%d\n", A, B);
       }
    }
    return 0;
}

 

 但仍不是題意所要求的一個變數完成,於是開始考慮另一種方式。由於要求的範圍 A, B 均為 1~9,各需 4 bits,故考慮以下編碼方式

bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
a3 a2 a1 a0 b4 b3 b2 b1

這麼做便可將 A, B 都存在一 unsigned char 裡。問題在於如何使用 bit 運算,將資料從此一 byte 變數之左邊與右邊分別存入與讀出。最後之挑戰,乃在於如何不宣告其他變數之前提下,建立一個 for loop。

#define HALF_BITS_LENGTH 4
// 這個值是記憶儲存單元長度的一半,在這道題裡為4bit
#define FULLMASK 255
// 這個數字表示一個全部bit 的mask,以二進位表示��11111111。
#define LMASK (FULLMASK << HALF_BITS_LENGTH)
// 這個巨集表示左bits 的mask,以二進位表示是11110000。
#define RMASK (FULLMASK >> HALF_BITS_LENGTH)
// 這個數字表示右bits 的mask,以二進位表示是00001111。
#define RSET(b, n) (b = ((LMASK & b) ^ n))
// 這個巨集將b 的右邊設置成n
#define LSET(b, n) (b = ((RMASK & b) ^ (n << HALF_BITS_LENGTH)))
// 這個巨集,將b 的左邊設置成n
#define RGET(b) (RMASK & b)
// 這個巨集得到b 的右邊的值
#define LGET(b) ((LMASK & b) >> HALF_BITS_LENGTH)
// 這個巨集得到b 的左邊的值
#define GRIDW 3
// 這個數字表示將帥移動範圍的行寬度。
#include <stdio.h>
#define HALF_BITS_LENGTH 4
#define FULLMASK 255
#define LMASK (FULLMASK << HALF_BITS_LENGTH)
#define RMASK (FULLMASK >> HALF_BITS_LENGTH)
#define RSET(b, n) (b = ((LMASK & b) ^ n))

#define LSET(b, n) (b = ((RMASK & b) ^ (n << HALF_BITS_LENGTH)))
#define RGET(b) (RMASK & b)
#define LGET(b) ((LMASK & b) >> HALF_BITS_LENGTH)
#define GRIDW 3
int main()
{
    unsigned char b;
    for(LSET(b, 1); LGET(b) <= GRIDW * GRIDW; LSET(b, (LGET(b) + 1)))
       for(RSET(b, 1); RGET(b) <= GRIDW * GRIDW; RSET(b, (RGET(b) + 1)))
          if(LGET(b) % GRIDW != RGET(b) % GRIDW)
             printf("A = %d, B = %d\n", LGET(b), RGET(b));
    return 0;
}

 

然而,於 MSRA 裡有他人說,下面程式碼也能達到同樣目的

 

BYTE i=81;
while(i--){
    if(i/9 % 3 == i % 9 % 3) continue; /* 不合條件 */
    printf("A=%d, B=%d\n", i/9%3, i%9%3);
}

 

 很快,又有另一個人說他解決才是效率最高的

 

struct {
    unsigned char a:4;
    unsigned char b:4;
}i;

for(i.a=1; i.a<=9; ++i.a)
    for(i.b=1; i.b<=9; ++i.b)
       if(i.a%3 != i.b%3) 
          printf("A=%d, B=%d\n", i.a, i.b);

 [評論]

1. 雖文前使用 Bitwise 是個繞路的方法,於此實用性也不高,但確實可讓人參考,因  Bitwise 確實為加速常用之技巧。

2. 之前寫過 「惡搞一行版九九乘法表」, ( 會叫 "惡搞版" 就是實際上別那麼做) ,原始碼大致如下所示

// C99 雙變數版
for(int i=1, j=1; i<=9; printf("%2d*%2d=%2d\n",i,j,i*j),(j==9?j=1,i++:j++));
// C99 單變數版
for(int j=0; j<9*9; printf("%2d*%2d=%2d\n",j/9+1, j%9+1, (j/9+1)*(j%9+1)),j++);

 

注意到其中一份 code 只有用到一個變數。這二個問題事實上都很像!雖上述是用 int 去存乘數與被乘數,但確實可只用 8 bits 變數存下來,另也可使用 bitwise 方式進行,大致上如下所示

#include <stdio.h>

#define LCLR(x)   ( (x) = (x) & 0x0f) /* clear high 4 bits of x */
#define RCLR(x)   ( (x) = (x) & 0xf0) /* clear low 4 bits of x */
#define LSET(x,v) ( LCLR(x), (x) = (x) | ((v)<<4) ) /* set the value of low 4 bits of x */
#define RSET(x,v) ( RCLR(x), (x) = (x) | (v)) /* set the value of high 4 bits of x */
#define LGET(x)   ( ( (x) & 0xf0) >> 4) /* get the value of high 4 bits of x */
#define RGET(x)   ( ( (x) & 0x0f)     ) /* get the value of low  4 bits of x */
#define INC_L(x)  ( (x) = (x) + 0x10) /* increment one of high 4 bits of x  */
#define INC_R(x)  ( (x) = (x) + 0x01) /* increment one of low  4 bits of x  */

int main()
{
    unsigned char x;
    for(LSET(x, 2); LGET(x)<=9; INC_L(x)){
       for(RSET(x, 1); RGET(x)<=9; INC_R(x)){
          printf("%2d*%2d=%2d  ", LGET(x), RGET(x), LGET(x)*RGET(x));
       }
       putchar('\n');
    }
    return 0;
}

 

寫得沒原題漂亮,但只為大概之展示。額外補充,事實上「九九乘法表」可以研究到不少東西,除了正常版、一行版、單變數版之外,也有遞迴的九九乘法表。

 

#include <stdio.h>

void _99(int i, int j)
{
    if(i<=9){
       printf("%d*%d=%2d\t", i, j, i*j);
       if(j<9) _99(i, j+1);
       else putchar('\n'), _99(i+1, 1);
    }
}

int main()
{
    _99(2, 1);
    return 0;
}

也有 Meta 版的九九乘法表,這裡離題過遠,於此便不附上。

edisonx 發表在 痞客邦 PIXNET 留言(0) 人氣()