Loop

Arvin Liu

一個方便找投影片的目錄

Syllabus - 0

內容 快速連結
前導 Section 1
goto  最基礎的重複結構 Section 2
Ex I - I - 可控隨機數 (介紹) Section 3
Ex I - II - 可控隨機數 (解析) Section 4
Ex II - 考拉茲猜想  3n+1問題 Section 5
do-while  結合 goto + if 的重複結構 Section 6
Ex III - I - 什麼是二進位? Section 7
Ex III - II - 進位制轉換 - I Section 8
To do or not to do? Section 9
while  事先檢查的重複結構 Section 10
Ex IV - 質數判斷 Section 11
for 最重要的重複結構 Section 12
break & continue 流程控制 Section 13
Loop p.s. 重複結構注意事項 Section 14
內容 快速連結
Loop sp. 盤點那些奇怪的迴圈 Section 15
Ex V - APCS觀念題練習 Section 16
Ex VI - 第二大數字判斷 Section 17
Review 複習所有重複結構 Section 18
Nested loop 巢狀重複結構 - 你今晚的惡夢 Section 19
Double for 最經典的雙重迴圈 Section 20
Ex VII - 指數表 Section 21
Double break !! 跳出雙重迴圈 Section 22
Ex VIII - I - 孤單的機器人 Section 23
Ex VIII - II - 孤單的機器人 (補充) Section 24
Ex IX - 幾何解析小幫手 Section 25
Ex X - 三角形 Section 26
結語 Section 27

重要章節
主要章節

這個投影片怎麼用呢?

Before Before Class - 1

  • 上下左右切換投影片,用ESC可以看到整個投影片的大綱。
    • 每個直排都是一個主題,右上角有編號,你可按右上角的             回第一頁。
    • 正確觀看順序:一直往下,到底往右。

Esc後大概長這樣,你可以先Esc在上下左右選你要看哪頁投影片。

關於這堂課

Before Before Class - 0

  • 廢圖和影片很多,希望大家可以不要這麼嚴肅:)
  • 這堂課(loop)非常多地方需要思考,我也會問問題讓大家思考。
    • 每個問題我都會讓大家思考的30s~1min,認真的想想看吧!
  • 在接下來的課程中 (迴圈 + 進階迴圈 + 遞迴) ,
    我會不時的讓大家做有計時的練習題,雖然有計時不過大家還是別太緊張(?)
    • 寫練習題時,如果真的不會就趁機趕快複習以前的東西,
      沒寫出來回家再看也沒關係,code都有附。
       
  • 有問題不要害羞,可以直接匿名發問。

Before Class

老樣子,耍個廢起個頭吧 😎😎😎

上課前,耍個廢

Before Class - 0

你看了什麼?

Before Class - 1

  • 派大星很靠北。
  • 再摔一次,我沒看到。
  • 沙山很危險
  • 你看了重複的兩段,在程式裡面可不可以重複做類似的事情呢?
    • 可以,這種叫做重複結構

剛剛影片的流程圖

耍廚廢文

Before Class - 2

  • 找找看右邊流程圖的重複結構在哪
    • 其實寫成這樣會比較簡單一點
    • 其實我沒有推Ayame

一個流程圖告訴你為什麼推ayame

goto

一個最基本的重複結構

最簡單的重複結構 - goto

goto - 0

流程圖

  • 可以,這種叫做重複結構。怎麼做呢?
    • 首先,我們需要一個標誌,告訴程式到時候回到哪裡。
    • 再來,在你想要時間回朔的地方回朔回去
  • 像下面這樣:
    • 用                    表示標記點。
    • 用                             表示讓流程圖回到標記點。
#include <iostream>
using namespace std;
int main() {
  int x;
  // 標記點 (label)
  start:
    cin >> x;
    cout << x+1 << endl;
    goto start;
  return 0;
}
[標誌名稱]:
goto [標誌名稱];

if-else + goto

goto - 1

一個比之前更完整的猜數字流程圖

  • 你可以使用if-else配合goto做到一些讓人想不到的神奇的效果(?)

3 mins

int x = 7, guess;
cin >> guess;
if (guess > x) {
  cout << "Smaller.\n";
} else if (guess == x) {
  cout << "Atari!\n";
} else {
  cout << "Larger.\n";
}

原本猜數字的code

if-else + goto

goto - 2

一個比之前更完整的猜數字流程圖

  • 你可以使用if-else配合goto做到一些讓人想不到的神奇的效果(?)

3 mins

#include <iostream>
using namespace std;
int main() {
  int x = 7, guess;
  start:
    cin >> guess;
    if (guess > x) {
      cout << "Smaller.\n";
      goto start;
    } else if (guess == x) {
      cout << "Atari!\n";
    } else {
      cout << "Larger.\n";
      goto start;
    }
  return 0;
}

猜數字 + goto 的code

小小的腦筋急轉彎

goto - 2

  • 你能夠跳出時間輪迴嗎?
    • 你可以在第六行或第九行加入一些code,
      不能改動其他的程式,或是在其他地方新增code
    • 有很多種解法,你可以想到多少種?

5 mins

#include <iostream>
using namespace std;
int main() {
  /* you can NOT add some code here. */
  start:
  /* you can add some code here. */
  cout << "PEKOPEKO!" << endl;
  goto start;
  /* you can add some code here. */
  return 0;
}
start:
if (false) {
cout << "PEKOPEKO!" << endl;
goto start;
}
start:
goto end;
cout << "PEKOPEKO!" << endl;
goto start;
end:;

聰明的你可能會用if。

腦子有洞的你可能會用goto。

start:
/*
cout << "PEKOPEKO!" << endl;
goto start;
*/

你怎麼就沒想過註解呢?

start:
return 0;
cout << "PEKOPEKO!" << endl;
goto start;

性格灑脫的你可能會自行了斷。

小工商 - CTF

goto - 3

一種資安的競賽類型 - 奪旗比賽 (Capture the Flag)

  • 資安比賽有很多種類型:
    • CTF 奪旗大賽
    • Attack & Defense 攻擊&防禦
  • ​剛剛你看到的這種類型在資安很常見(?)
    • 就是要輸入或插入一些奇奇怪怪的東西,繞過一些東西防護機制或是正常程式之類的。
    • 例如 SQL Injection 等等
  • 如果有興趣歡迎之後走資安領域喔!

Exercise I - I

Controllable RNG -  可控隨機數 (介紹)

Exercise I - I - Controllable RNG 可控隨機數 - 0

171 - 前導影片

171 - 前導 - 什麼是RNG?

隨機數生成器是通過一些算法、物理訊號、環境噪音等來產生看起來似乎沒有關聯性的 數列的方法或裝置。丟硬幣、丟骰子、洗牌就是生活上常見的隨機數產生方式。

大部分計算機上的偽隨機數,並不是真正的隨機數,只是重複的周期比較大的數列,是按一定的算法和種子值生成的。

Random Number Generator

  • 簡單來說,就是一種隨機產生亂數的方法。
  • 為什麼電腦上都是偽RNG呢?
    • 因為電腦只會跑固定的程式,它不會採集天地之靈氣後告訴你亂數。
    • 那電腦的亂數都怎麼生成的呢?

梅森旋轉算法

線性反饋移位暫存器

Exercise I - I - Controllable RNG 可控隨機數 - 1

171 - 前導 - 什麼是LFSR?

  • 大概念就是對一個數字做一些奇奇怪怪的運算(如下面的code),就會產生出下一個奇奇怪怪的數字。(蛤?)
    • 例如,初始化時如果lfsr為44257,那麼接下來呼叫產生出來的數字會幾乎無法預測。

wiki的LFSR範例圖

unsigned LFSR() {
  static unsigned short lfsr = 44257;
  unsigned bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5) ) & 1;
  return lfsr =  (lfsr >> 1) | (bit << 15);
}

線性反饋移位暫存器

44257 -> 22128 -> 43832 -> 21916 -> 10958
-> 5479 -> 35507 -> 17753 -> 8876 ->  ...

Exercise I - I - Controllable RNG 可控隨機數 - 2

171 - 前導 - 怎麼用LFSR做亂數生成?

  • 以我們剛剛的code為例,如果初始化時lfsr的值為44257,那麼接下來呼叫產生出來的數字會幾乎無法預測。 (但你要是知道亂數是用LFSR生成的就另當別論)

 

  • 那我們該怎麼利用LFSR所產生出來的數字做亂數生成呢?
    • 產生0到n的亂數
    • 產生a到b的亂數
    • 知道以上這兩種作法其實就ok了。
       
  • 正常生成亂數,其實用c/c++的rand()就可以了,不用自己寫RNG。
  • 上面什麼是RNG,什麼是LFSR,聽不懂沒關係,當作聽故事就好了。
44257 -> 22128 -> 43832 -> 21916 -> 10958 -> 5479 -> 35507 -> 17753 -> 8876 ->  ...
int num = LFSR() % (n + 1);
int num = LFSR() % (b - a + 1) + a;

Exercise I - I - Controllable RNG 可控隨機數 - 3

線性反饋移位暫存器

171 - 前導 - 怎麼用LFSR做亂數生成?

  • 那我們該怎麼利用LFSR所產生出來的數字做亂數生成呢?
    • 產生0到n的亂數
    • 產生a到b的亂數
  • ​底下為一個產生範圍0~10亂數的一份完整的code,跑跑看吧!
    • ​你能不能讓它無窮的跑亂數呢?
    • 你能不能把它改成生成1~6的亂數來模擬丟骰子呢?
int num = LFSR() % (n + 1);
int num = LFSR() % (b - a + 1) + a;

5 mins

#include <iostream>
using namespace std;

unsigned LFSR() {
  static unsigned short lfsr = 44257;
  unsigned bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5) ) & 1;
  return lfsr =  (lfsr >> 1) | (bit << 15);
}

int main () {
    int n = 10, x;
    x = LFSR() % (n+1);
    cout << x << " ";
    x = LFSR() % (n+1);
    cout << x << " ";
    x = LFSR() % (n+1);
    cout << x << " ";
    x = LFSR() % (n+1);
    cout << x << " ";
    x = LFSR() % (n+1);
    cout << x << " ";
    // ...
}

Exercise I - I - Controllable RNG 可控隨機數 - 4

線性反饋移位暫存器

171 - 延伸 - 種子碼之所以叫種子

  • 我們仔細看看LFSR,預設是44257的話,可以一直生成序列下去。
    那麼其實這個44257,就稱之為PRNG的種子碼。

Exercise I - I - Controllable RNG 可控隨機數 - 5

44257 -> 22128 -> 43832 -> 21916 -> 10958 -> 5479 -> 35507 -> 17753 -> 8876 ->  ...
  • 例如在minecraft裡面,給它一個種子碼,它就會利用PRNG生出一個世界來。
    • 同個種子碼生成同個世界,不同種子碼生成完全不同世界即使只差1
    • 舉例來說,下面兩個世界就是不一樣的種子碼。(from 30 best Minecraft Seeds)

Exercise I - II

Controllable RNG -  可控隨機數 (解析)

Exercise I

程式練習題

接下來有一題請你用
程式實做重複結構的題目。

  • 我會花1分鐘讀題
  • 總共計時15分鐘
  • 開好你的程式環境吧!

171 - 可控隨機數 - 題目

  • 題目:給你一個RNG函式以及一個正整數n以及正整數k,請你生成並輸出五個隨機數,一行一個。​
    • 請按照LFSR的生成順序生成。
    • 如果生成的數字是k的倍數,那麼這個數字我們就不要了,重新生成一個新的。
    • 提示:可以照著右邊的流程圖寫。
    • 小題醒:記得複製下面的code貼在main前。

15 mins

Exercise I - II - Controllable RNG 可控隨機數 - 0

unsigned LFSR() {
  static unsigned short lfsr = 44257;
  unsigned bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5) ) & 1;
  return lfsr =  (lfsr >> 1) | (bit << 15);
}

這題的流程圖

171 - 可控隨機數 - 題目

15 mins

#include <iostream>
using namespace std;

unsigned LFSR();

int main() {
  int n, k;
  cin >> n >> k;
  int cnt = 0;
  start:
    int x = LFSR() % (n+1);
    if (x % k == 0)
      goto start;
    cout << x << endl;
    cnt ++;
    if (cnt != 5)
      goto start;
  return 0;
}

unsigned LFSR() {
  static unsigned short lfsr = 44257;
  unsigned bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5) ) & 1;
  return lfsr =  (lfsr >> 1) | (bit << 15);
}

Exercise I - II - Controllable RNG 可控隨機數 - 1

這題的流程圖

  • 題目:給你一個RNG函式以及一個正整數n以及正整數k,請你生成並輸出五個隨機數,一行一個。​
    • 按照流程圖一行一行跟著寫就可以囉😎😎😎

Exercise II

Collatz Conjecture - 考拉茲猜想

Exercise II

程式練習題

接下來有一題請你用
程式實做重複結構的題目。

  • 我會花1分鐘讀題。
  • 總共計時20分鐘
  • 開好你的程式環境吧!

170 - 考拉茲猜想 - 題目

Exercise I - Collatz Conjecture 考拉茲猜想 - 0

  • 題目:輸出考拉茲猜想的數字過程。
  • 例如,如果題目輸入1,請輸出
     
  • 如果題目輸入18,請輸出



    (為了方便觀看,這邊有換行,但你寫的時候只需要最後換行。)
  • 這次沒有流程圖了QQ,但是比較簡單!
  • 小提示:你把你想做的事情直接寫出來,然後觀察哪裡重複了

20 mins

wiki對考拉茲猜想的描述。

1 -> end
18 -> 9 -> 28 -> 14 -> 7 -> 22 -> 11 
-> 34 -> 17 -> 52 -> 26 -> 13 -> 40 
-> 20 -> 10 -> 5 -> 16 -> 8 -> 4 
-> 2 -> 1 -> end

170 - 考拉茲猜想 - 引導

Exercise I - Collatz Conjecture 考拉茲猜想 - 1

20 mins

  • 題目:請輸出考拉茲猜想的數字過程。
  • 小提示:你把你想做的事情直接寫出來,然後觀察哪裡重複了。
cin >> n;

cout << n << " -> ";
if (n % 2 == 0) 
  n /= 2;
else
  n = 3 * n + 1;

cout << n << " -> ";
if (n % 2 == 0) 
  n /= 2;
else
  n = 3 * n + 1;

cout << n << " -> ";
if (n % 2 == 0) 
  n /= 2;
else
  n = 3 * n + 1;

直接寫出來就是長這樣。

cin >> n;

start:
  cout << n << " -> ";
  if (n % 2 == 0) 
    n /= 2;
  else
    n = 3 * n + 1;
goto start;

用goto循環

170 - 考拉茲猜想 - 引導

Exercise I - Collatz Conjecture 考拉茲猜想 - 2

20 mins

  • 題目:請輸出考拉茲猜想的數字過程。
  • 小提示:你把你想做的事情直接寫出來,然後觀察哪裡重複了。
cin >> n;

start:
  cout << n << " -> ";
  if (n % 2 == 0) 
    n /= 2;
  else
    n = 3 * n + 1;
goto start;

用goto循環

cin >> n;

start:
  cout << n << " -> ";
  if (n % 2 == 0) 
    n /= 2;
  else
    n = 3 * n + 1;
  if (n != 1)
    goto start;
cout << "1 -> end" << endl;

如果n還沒到1才要回去。

170 - 考拉茲猜想 - 引導

Exercise I - Collatz Conjecture 考拉茲猜想 - 3

20 mins

  • 題目:請輸出考拉茲猜想的數字過程。
  • 小提示:你把你想做的事情直接寫出來,然後觀察哪裡重複了。
cin >> n;

start:
  cout << n << " -> ";
  if (n % 2 == 0) 
    n /= 2;
  else
    n = 3 * n + 1;
  if (n != 1)
    goto start;
cout << "1 -> end" << endl;

如果n還沒到1才要回去。

一開始n是1會錯QQ

cin >> n;
if (n != 1) {
  start:
    cout << n << " -> ";
    if (n % 2 == 0) 
      n /= 2;
    else
      n = 3 * n + 1;
    if (n != 1)
      goto start;
}
cout << "1 -> end" << endl;

AC!

170 - 考拉茲猜想 - 解答

Exercise I - Collatz Conjecture 考拉茲猜想 - 4

#include <iostream>
using namespace std;
int main() {
  int n;
  cin >> n;
  if (n != 1) {
    start:
      cout << n << " -> ";
      if (n % 2 == 0) 
        n /= 2;
      else
        n = 3 * n + 1;
      if (n != 1)
        goto start;
  }
  cout << "1 -> end" << endl;
  return 0;
}

20 mins

  • 題目:請輸出考拉茲猜想的數字過程。
  • 以下就是完整個一份code。

do-while

結合 if-else goto重複結構

goto + if = do-while

do-while - 0

do-while流程圖

#include <iostream>
int main() {
  int x;
  do {
    std::cin >> x;  
  } while (x != 0);
  return 0;
}
do {
  // 做事情
} while (true 或 false);
  • 一個輸入到 0 就結束的程式:
  • do-while基本語法是這樣的:
    • 大括號裡面做事情,結尾用一個 while 接條件+分號
    • 做到大括號判斷條件,如果是 true 就回去 { 重跑一次,如果是 false,就直接往下執行。

goto <-> do-while ?

do-while - 1

do-while流程圖

  • 我們來看看gotodo-while 的差別:
    • goto 配合 if,是這樣子的:
      如果條件成立,跳回之前重來一次。



       
    • do-while 是這樣子的:
      如果條件成立,跳回之前重來一次。


       
    • 好像一樣?就真的一樣。
do {
  // 做事情
} while (true 或 false);
start:
// 做事情
if (true 或 false)
  goto start;

goto -> do-while

do-while - 2

#include <iostream>
using namespace std;
int main() {
  int n;
  cin >> n;
  if (n != 1) {
    start:
      cout << n << " -> ";
      if (n % 2 == 0) 
        n /= 2;
      else
        n = 3 * n + 1;
      if (n != 1)
        goto start;
  }
  cout << "1 -> end" << endl;
  return 0;
}
#include <iostream>
using namespace std;
int main() {
  int n;
  cin >> n;
  if (n != 1) {
    do {
      cout << n << " -> ";
      if (n % 2 == 0) 
        n /= 2;
      else
        n = 3 * n + 1;
    } while(n != 1);
  }
  cout << "1 -> end" << endl;
  return 0;
}
  • goto轉成do-while
    • 為甚麼要用do-while寫呢?因為看起來比較簡單!

goto + if (剛剛的code)

do-while (改寫的code)

3 mins

其實,平常最好不要用goto...

do-while - 3

  • 你的code會看起來很跳來跳去的。😖
  • 這種混雜混雜的code,就稱speghetti code (麵條式代碼)
  • BTW,有個非常特別的比賽叫做IOCCC (國際C語言混亂代碼大賽),右邊這個是某年競賽的產物(?)
                                                     /*[*/#include<stdio.h>//
                         #include<stdlib.h>//]++++[->++[->+>++++<<]<][(c)2013]
                        #ifndef                                           e//[o
                       #include<string.h>//]![misaka.c,size=3808,crc=d0ec3b36][
                      #define e                                           0x1//
                     typedef struct{int d,b,o,P;char*q,*p;}f;int p,q,d,b,_=0//|
                  #include __FILE__//]>>>[->+>++<<]<[-<<+>>>++<]>>+MISAKA*IMOUTO
                #undef e//[->[-<<+<+<+>>>>]<<<<<++[->>+>>>+<<<<<]>+>+++>+++[>]]b
             #define e(c)/**/if((_!=__LINE__?(_=__LINE__):0)){c;}//[20002,+[-.+]
            ,O,i=0,Q=sizeof(f);static f*P;static FILE*t;static const char*o[]={//
          "\n\40\"8oCan\40not\40open %s\n\0aaFbfeccdeaEbgecbbcda6bcedd#e(bbed$bbd",
        "a6bgcdbbccd#ead$c%bcdea7bccde*b$eebbdda9bsdbeccdbbecdcbbcceed#eaa&bae$cbe",
       "e&cbdd$eldbdeedbbdede)bdcdea&bbde1bedbbcc&b#ccdee&bdcdea'bbcd)e'bad(bae&bccd",
      "e&bbda1bdcdee$bbce#b$c&bdedcd%ecdca4bhcdeebbcd#e$b#ecdcc$bccda7bbcc#e#d%c*bbda",
     ">bad/bbda"};static int S(){return(o[p][q]);}static/**/int/**/Z=0  ;void/**/z(int//
    l){if(/**/Z-l){Z=l;q++;if(p<b*5&&!S()){p+=b;q=0;}}}int main(int I,    /**/char**l){//
   d=sizeof(f*);if(1<(O=_)){b=((sizeof(o)/sizeof(char*))-1)/4;q=22; p=     0;while(p<b*5){
  /*<*/if(Z-1){d=S()>96;i=S()-(d?96:32) ;q++;if(p<b*5&&!S()){p+=b;  q=      0;}Z=1;}/*[[*/
  while(i){_=o[0][S()-97];I=_-10?b:1;   for( ;I--;)putchar(_ );if   (!      --i||d)z(~i );}
 if(p==b*5&&O){p-=b;O--;}}return 0U;   }if(! (P=( f*)calloc /*]*/  (Q        ,I)))return 1;
 {;}for(_=p=1;p<I;p++){e(q=1);while    (q<   p&&  strcmp(  l[p     ]         ,l[(q)]))++  q;
 t=stdin;if(q<p){(void)memcpy/* "      */    (&P  [p],&P   [q     ]          ,Q);continue ;}
if(strcmp(l[p],"-")){t=fopen(l         [     p]   ,"rb"   )                  ;if(!t ){{;}  ;
printf(05+*o,l[p ]);return+1;                      {;}                       }}_=b= 1<<16   ;
*&O=5;do{if(!(P[p].q=realloc   (P[p].q,(P[p].P     +=       b)+1))){return   01;}O   &=72   /
6/*][*/;P[p].o+=d=fread(P[p]      .q       +P[     p           ].       o,  1,b,t)   ;}//
 while(d==b)      ;P [p].q[       P[       p]                  .o       ]=  012;d    =0;
 e(fclose(t        )  );P         [p]      .p                  =P[      p]  .q;if    (O)
 {for(;d<P[            p]          .o     ;d=                   q+     1)    {q=     d;
  while(q<P[                        p].o&&P[                    p].q[q]-     10     ){
  q++;}b=q-d;                         _=P                         [p].        d     ;
  if(b>_){/*]b                                                                */
   P[p].d=b;}{;                                                                }
   #undef/*pqdz'.*/  e//                                                      ;
   #define/*s8qdb]*/e/**/0                                                   //
   //<<.<<.----.>.<<.>++.++<                                              .[>]
   /*P[*/P[p].b++;continue;}}}t=                                       stdout;
  for (p=1;p<I;p++){/**/if(P[p].b>i                               ){i=P[p].b;}}
 if  (O){for(p=0;p<i;p++){q=0;/*[*/while(I               >++q){_=P[q].p-P[q ].q;
b=   0;if(_<P[q ].o){while(012-*P[q].p)     {putchar(*(P[q].p++));b++;}P[q]. p++;
}   ;while (P[  q].d>b++)putchar(040);}             putchar(10);}return 0;}p   =1;
   for(;   p<I   ;p++)fwrite(P[p] .q,P[              p].o,1,t);return 0 ;}//
  #/*]     ]<.    [-]<[-]<[- ]<[    -]<               [-  ]<;*/elif  e    //b
 |(1        <<     ( __LINE__        /*               >>   `*//45))  |     01U
             #                       /*               */     endif            //

這份code可以讓多個文件並排輸出。

你可以複製上面的這份code編譯看看(?)
(要把文件存成.c 不能是.cpp)

跳來跳去好難看啊....

Exercise III - I

介紹二進位制

172 - 什麼是二進位制?

Exercise III - I - Dec2bin 進位置轉換 - 0

15 mins

  • 你會數數嗎?

172 - 什麼是二進位制?

Exercise III - I - Dec2bin 進位置轉換 - 1

  • 你會數(十進位制的)數嗎?
    • 數到十的時候會進位。
  • 那你會數(二進位制的)數嗎?
    • 數到二的時候會進位。

1

2

3

4

...

9

10

5

6

1

10

11

100

101

110

172 - 十進位轉二進位?

Exercise III - I - Dec2bin 進位置轉換 - 2

1

2

3

4

5

6

1

10

11

100

101

110

進位制

進位制

看的出來
規律嗎?

172 - 十進位轉二進位?

Exercise III - I - Dec2bin 進位置轉換 - 3

1

2

3

4

5

6

001

010

011

100

101

110

0\cdot 2^2 + 0\cdot 2^1 + {\red 1} \cdot 2^0
0\cdot 2^2 + {\red 1 }\cdot 2^1 + 0 \cdot 2^0
0\cdot 2^2 + {\red 1}\cdot 2^1 + {\red 1} \cdot 2^0
{\red 1}\cdot 2^2 + 0\cdot 2^1 + 0 \cdot 2^0
{\red 1}\cdot 2^2 + 0\cdot 2^1 + {\red 1} \cdot 2^0
{\red 1}\cdot 2^2 + {\red 1}\cdot 2^1 + 0 \cdot 2^0

172 - 十進位轉二進位?

Exercise III - I - Dec2bin 進位置轉換 - 4

  • 為了要得到2的冪次方的系數,我們會用
    右邊這個除法來得到。
    • 如何做呢?我們以右圖的13做為舉例。
      • 13 % 2 = 1,這表示 
      • 13 / 2 = 6
      • 6 % 2 = 0,這表示
      • 6 / 2 = 3
      • 3 % 2 = 1,這表示
      • 3 / 2 = 1
      • 1 % 2 = 1,這表示
      • 1 / 2 = 0
    •  

一個將13轉成1101的過程

{\red 1}\cdot 2^0
{\red 0}\cdot 2^1
{\red 1}\cdot 2^2
{\red 1}\cdot 2^3
(13)_{10} = {\red 1}\cdot 2^3 + {\red 1}\cdot 2^2 + {\red 0}\cdot 2^1 + {\red 1}\cdot 2^0 = ({\red 1 \red 1\red 0\red 1})_{2}

Exercise III - II

進位制轉換 - I

Exercise III

程式練習題

接下來有一題請你用
程式實做重複結構的題目。

  • 我會花10秒讀題。
  • 總共計時15分鐘
  • 開好你的程式環境吧!

172 - 進位制轉換 - 題目

  • 題目:給你一個十進位數字 (範圍從 0 到 2^60 次方),
    請你將這個數字轉成二進位後倒著印
  • 小提示:像考拉茲猜想一樣,想辦法把重複結構用do-while做出來。

15 mins

Exercise III - II - Dec2bin 進位置轉換 - 0

172 - 進位制轉換 - 解答

#include <iostream>
using namespace std;
int main () {
  long long x;
  cin >> x;
  do {
    cout << x % 2;
    x /= 2;
  } while (x != 0);
  cout << endl;
  return 0;
}

Exercise III - II - Dec2bin 進位置轉換 - 1

15 mins

  • 題目:給你一個十進位數字 (範圍從 0 到 2^60 次方),
    請你將這個數字轉成二進位後倒著印
  • 小提示:像考拉茲猜想一樣,想辦法把重複結構用do-while做出來。
  • 記得到0跳出。

To do or not to do?

先做判斷 vs 後做判斷?

小小複習 - 語法

To do or not to do ? - 0

  • 再繼續往下之前,我們來複習一下吧!

    • goto:跳轉到你曾經設下的標記點。

    • do{}while();:做{},判斷()決定要不要跳轉到一開始。

do-while流程圖

goto流程圖

// 設下標記點
labelA:       
do_something();
// 跳到標記點
goto labelA;  

// =======================

do {
  do_something();
  // 如果true會跳到do{那行
} while (true 或 false);

小小複習 - 題目

To do or not to do ? - 1

  • 再繼續往下之前,我們來複習一下吧!

    • ​循環的猜數字 (goto)

      • ​猜數字後判斷有沒有猜中沒有回去再猜一次

    • 可控隨機數 (看著流程圖用goto實作)

      • 生亂數後判斷有沒有違反條件或還沒生五次,以此決定要不要回去重生亂數

    • 考拉茲猜想 (goto -> do-while)

      • 判斷已經是不是到1了還沒就/2或*3+1並回去重做一次

    • 進位制轉換 (do-while)

      • ​輸出第一位數後 /2判斷是否到0了,沒有就回去重做一次

做動作
做判斷
跳code

想想看之前的題目...

To do or not to do ? - 2

  • 有些情況,是需要先做事再判斷要不要跳回去的

    • 例如進位制轉換,亂數生成
       

  • 但有些情況是需要先判斷再做事,做完再重新判斷決定要不要跳回去

    • 考拉茲猜想要先判斷是不是1做完之後還要重新判斷是不是1,好麻煩

    • 有沒有更簡單的語法可以簡單做這件事情呢?
#include <iostream>
using namespace std;
int main() {
  int n;
  cin >> n;
  if (n != 1) {
    do {
      cout << n << " -> ";
      if (n % 2 == 0) 
        n /= 2;
      else
        n = 3 * n + 1;
    } while(n != 1);
  }
  cout << "1 -> end" << endl;
  return 0;
}

用do-while實作考拉茲猜想。

做動作
做判斷
跳code

while

先判斷再做事重複結構

先判斷條件的迴圈 - while

while - 0

while流程圖

// while 一開始,如果判斷為F則跳到}後
while (T / F) {
  // 做事情
} // 結束會回到while(){重新判斷
  • while就是一開始有先檢查的do-while。
    • ​do-while 一定會做第一次
      • }while(T/F);決定要不要回去。
    • while 不一定,一開始就會檢查
      • }會直接回while(T/F){判斷。
        • 如果為T,就繼續往下做。
        • 如果為F,會直接跳到while(){}之後
      • ​while結尾不用分號,但do-while要。

do-while流程圖

先判斷條件的迴圈 - while

while - 1

  • while 可以把事前檢查的 ifdo-while 合併。
#include <iostream>
using namespace std;
int main() {
  int n;
  cin >> n;
  if (n != 1) {
    do {
      cout << n << " -> ";
      if (n % 2 == 0) 
        n /= 2;
      else
        n = 3 * n + 1;
    } while(n != 1);
  }
  cout << "1 -> end" << endl;
  return 0;
}
#include <iostream>
using namespace std;
int main() {
  int n;
  cin >> n;
  while (n != 1) {
    cout << n << " -> ";
    if (n % 2 == 0) 
      n /= 2;
    else
      n = 3 * n + 1;
  }
  cout << "1 -> end" << endl;
  return 0;
}

考拉茲猜想用whlie實作的code。

一個用 while 的小例子 - 1+...+n

while - 2

  • 如何算出                  呢? (就是+1+2+...+n的總和)

     
  • 嚴格上來說,你可以用                                             ,但先不要,
    試著用重複結構來寫寫看。
\sum^n_{i=1} i
1+2+3+4+5+6+....+n為止
int sum = 0;
sum += 1;
sum += 2;
sum += 3;
sum += 4;
.... 加到n為止。

你可能會想這樣想

\sum^n_{i=1} i = \frac{n \times (n+1)}2

但這個東西不是重複結構啊?
code不一樣不能重複QQ

int sum = 0, i = 1;
sum += i;
i += 1;

sum += i;
i += 1;

sum += i;
i += 1;
.... 加到i=n為止。

耶?怎麼突然可以了?

  • 觀察規律,想辦法讓他變重複結構,這就是迴圈最重要的技巧,也是迴圈難的地方。

一個用 while 的小例子 - 1+...+n

while - 2

int sum = 0, i = 1;
do {
  sum += i;
  i += 1;
} while (i != n+1);
int sum = 0, i = 1;
while (i != n+1) {
  sum += i;
  i += 1;
}
  • 如何算出                  呢? (就是+1+2+...+n的總和)
  • 觀察規律,想辦法讓他變重複結構,這就是迴圈最重要的技巧,也是迴圈難的地方。
\sum^n_{i=1} i
int sum = 0, i = 1;
sum += i;
i += 1;

sum += i;
i += 1;

sum += i;
i += 1;
.... 加到i=n為止。

耶?怎麼突然可以了?

do-while的寫法

while的寫法

但n=0呢?

Exercise IV

isPrime - 質數判斷

Exercise IV

程式練習題

接下來有一題請你用
程式實做重複結構的題目。

  • 我會花1分鐘讀題。
  • 我會花1分鐘教你開平方根
  • 總共計時20分鐘
  • 第10分鐘給個提示
  • 開好你的程式環境吧!

173 - 質數驗證 - 題目

Exercise IV - isPrime - 質數判斷 - 0

  • 題目:給你一個數字,判斷其是否為質數。是輸出"Yes",否輸出"No"。(皆需換行)

20 mins

數字範圍:

1\sim 2^{40}

173 - 質數驗證 -    How2

Exercise IV - isPrime - 質數判斷 - 1

  • 題目:給你一個數字,判斷其是否為質數。是輸出"Yes",否輸出"No"。(皆需換行)

20 mins

#include <iostream>
#include <cmath>
using namespace std;
int main() {
  long long x;
  cin >> x;
  cout << sqrt(x) << endl;
  return 0;
}
  • 如何對一個數字開平方根呢?
    • 在最前面加上 #include <cmath>
    • 使用sqrt(變數),就可以得到該變數的平方根了! 簡單不?
    • 為什麼要用到平方根來加速?你怎麼不問問你自己?

輸入一個數字,輸出平方根的小小程式例子

\sqrt{ \text{ \, \, \, \, \, \, \, \, \,\, \, \, \, \, }}

數字範圍:

1\sim 2^{40}

173 - 質數驗證 - 小提示

Exercise IV - isPrime - 質數判斷 - 2

  • 題目:給你一個數字,判斷其是否為質數。是輸出"Yes",否輸出"No"。(皆需換行)

20 mins

  • 結合剛剛的 1 + 2 + 3 + ... + n 的概念,我們可以寫出一個程式
    輸出 x % 2 , x % 3,x % 4 直到不超過x的平方根(sqrt(x))為止。
#include <iostream>
#include <cmath>
using namespace std;
int main() {
  long long i = 2, x;
  cin >> x;
  while (i <= sqrt(x)) {
    cout << (x % i) << " ";
    i += 1;
  }
  return 0;
}

小小提示

數字範圍:

1\sim 2^{40}

173 - 質數驗證 - 答案

Exercise IV - isPrime - 質數判斷 - 3

  • 題目:給你一個數字,判斷其是否為質數。是輸出"Yes",否輸出"No"。(皆需換行)

20 mins

#include <iostream>
#include <cmath>
using namespace std;
int main() {
  long long i = 2, x;
  cin >> x;
  bool is_prime = (x != 1);
  while (i <= sqrt(x)) {
    if (x % i == 0)
      is_prime = false;
    i += 1;
  }
  cout << (is_prime ? "Yes" : "No") << endl;
  return 0;
}
  • 外面多設一個布林值來判斷到底是不是就可以了。 (記得1要特判)

質數驗證的解答code

數字範圍:

1\sim 2^{40}

173 - 質數驗證 - 接續

Exercise IV - isPrime - 質數判斷 - 4

  • 我們仔細想想看1+2+...+n還有質數驗證的程式:
    • 我們發現其實一直有個變數 i 在控制著迴圈的次數。
    • 但是它散落在while的上面跟下面:(,有沒有辦法可以讓程式變的好看一點呢?

20 mins

long long i = 2, x;
cin >> x;
bool is_prime = (x != 1);
while (i <= sqrt(x)) {
  if (x % i == 0)
    is_prime = false;
  i += 1;
}
cout << (is_prime ? "Yes" : "No") << endl;

質數驗證的解答code

int sum = 0, i = 1;
while (i != n+1) {
  sum += i;
  i += 1;
}

數字加總的code

for

一個被整合迴圈語法

整合起來的迴圈 - for

for - 0

for 流程圖

  • 我們稍微來回去看 1 + 2 + ... + n 的例子:




     
  • 我們發現很多時候都需要依賴某變數控制迴圈,為了讓程式變的更看的懂一點,程式猿們就做出了for的語法。


     
    • 這樣一來,你就可以很明顯知道i是控制迴圈的
      可讀性upup!
int sum = 0, i = 1;
while (i != n+1) {
  sum += i;
  i += 1;
}
int sum = 0;
for (int i=1; i != n+1; i+=1) {
  sum += i;
}

整合起來的迴圈 - for

for 流程圖

  • 我們稍微來回去看 1 + 2 + ... + n 的例子:



     
  • for迴圈的語法總共分三個區域,用分號切割


     
    • 你可以想像for迴圈就是while的進階版:
      • 初始設置:在一開始設置控制迴圈變數。
      • 條件判斷:這裡跟while一模一樣。
      • 控制運算:做完事情後,操作一次控制迴圈的變數。
int sum = 0, i = 1;
while (i != n+1) {
  sum += i;
  i += 1;
}
int sum = 0;
for (int i=1; i != n+1; i+=1) {
  sum += i;
}
for (初始設置; True / False; 控制運算) {
  // 做事情
}

for - 1

整合起來的迴圈 - for

for 流程圖

  • 我們來模擬看看如果 n = 2 ,for的過程:
int sum = 0;
for (int i=1; i != n+1; i+=1) {
  sum += i;
}

for - 2

  1. 初始程式: int i = 1
  2. 判斷條件: i != n+1 (True,因為 1 != 3)
  3. sum += i (sum從0變成1)
  4. 控制運算: i+=1 (i變成2)
  5. 判斷條件: i != n+1 (True,因為 2 != 3)
  6. sum += i (sum從1變成3)
  7. 控制運算: i+=1 (i變成3)
  8. 判斷條件: i != n+1 (False,因為 3==3)
  9. 結束程式

for 常用的方法

  • 變數 ia 跑到 b。 (不包含 b)

for - 3

for 流程圖

  • 變數 ia 跑到 b。 (包含 b)。
for (int i=a; i<=b; i++) { }
  • 變數 i1 一直 * 2,直到超過 b 為止
for (int i=1; i<=b; i*=2) { }
[a, a+1, a+2, ..., b-1]
[a, a+1, ..., b-1, b]
[1, 2, 4, 8, 16, ..., 2^{\lfloor \log_2 b \rfloor}]

* i++ 和 ++i 和 i+=1; 和 i=i+1 在只有單獨出現的時候沒有什麼不同。

  • 變數 i0 跑到 n。 (總共跑n次)
for (int i=a; i<b; i++) { }
[0, 1, 2, 3, ..., n-1]

變數 i 轉變的過程

for (int i=0; i<n; i++) { }

使用時機 / code

173 - 質數驗證 - 接續

long long i = 2, x;
cin >> x;
bool is_prime = (x != 1);
while (i <= sqrt(x)) {
  if (x % i == 0)
    is_prime = false;
  i += 1;
}
cout << (is_prime ? "Yes" : "No") << endl;

質數驗證的解答code (while)

  • 題目:給你一個數字,判斷其是否為質數。是輸出"Yes",否輸出"No"。(皆需換行)
    • 你能夠改寫成 for 迴圈嗎?
    • 仔細想想,這份code完美嗎?
      • 你不覺得只要知道不是質數就可以不用判斷了嗎?

數字範圍:

1\sim 2^{40}
long long x;
cin >> x;
bool is_prime = (x != 1);
for (int i=2; i <= sqrt(x); i++) {
  if (x % i == 0)
    is_prime = false;
}
cout << (is_prime ? "Yes" : "No") << endl;

質數驗證的解答code (for)

for - 4

5 mins

173 - 質數驗證 - 更快的答案

5 mins

long long x;
cin >> x;
bool is_prime = x != 1;
for (int i=2; is_prime && i<=sqrt(x); i++) {
  if (x % i == 0)
    is_prime = false;
  i += 1;
}
cout << (is_prime ? "Yes" : "No") << endl;  
  • 其實如果is_prime是false,就可以不用跑了,這樣速度會變得更快。
    • 在while多附加上條件就好了!
       
  • 那有沒有方法可以直接跳出去呢?
    • 還真的有:那就是利用goto。
      (但我們不建議。)
    • 有沒有比較不暴力的方法?
      • 也有,叫做break;
      • 接著讓我們直接講break吧!

使用變數判斷提早跳出迴圈。

long long x;
cin >> x;
bool is_prime = x != 1;
for (int i=2; i<=sqrt(x); i++) {
  if (x % i == 0) {
    is_prime = false;
    goto out;
  }
  i += 1;
}
out:
cout << (is_prime ? "Yes" : "No") << endl;  

使用goto提早跳出迴圈。

for - 5

break & continue

迴圈的流程控制

跳脫迴圈 - break

break & continue - 0

while + break

for + break

  • break;
    • 加上這樣一行,
      就可以跳出一層迴圈。
    • 總之就是跳到迴圈的}後面。
long long x;
cin >> x;
bool is_prime = x != 1;
for (int i=2; i<=sqrt(x); i++) {
  if (x % i == 0) {
    is_prime = false;
    break;
  }
  i += 1;
}
cout << (is_prime ? "Yes" : "No");
cout << endl;  

強制重來 - continue

break & continue - 1

whlie + continue

for + continue

  • continue;
    • ​加上這樣一行,
      就可以直接重來這個迴圈
    • for + continue會回去多做控制運算。
long long x;
cin >> x;
bool is_prime = x != 1;
for (int i=1; i<=sqrt(x); i++) {
  if (i == 1)
    continue;
  if (x % i == 0) {
    is_prime = false;
    break;
  }
  i += 1;
}
cout << (is_prime ? "Yes" : "No");
cout << endl;  

break & continue 的實驗

break & continue - 2

  • break; 強制跳出這層迴圈。
  • continue; 直接重來這個迴圈。
  • 舉例來說,下面這份code會跑什麼呢?
#include <iostream>
using namespace std;
int main () {
  for (int i=0; i<10; i++) {
    if (i % 2 == 0)
      continue;
    if (i == 7)
      break;
    cout << i << " ";
  }
}
1 3 5

for + break

for + continue

2 mins

Loop p.s.

一些需要注意的細節

p.s. 0 - 大括號省略問題

Loop p.s. - 0

  • 我們來看以前的在講if-else的大括號省略。
  • 其實for / while迴圈的大括號都是可以省略的
    不過一樣只能作用接下來的一行code或者一個結構 (選擇/重複都可以)。

p.s. 1 - for的三個區塊都必要嗎?

Loop p.s. - 1

  • 事實上,for迴圈的三個區塊都可以省略。
    • 初始程式:省略就是什麼都不做。
    • 條件判斷:省略的話,預設為True。
    • 控制運算:省略就是什麼都不做。
  • 如果你打算要做無窮迴圈,你可能會用while這樣寫。


     
  • 但其實你可以用for這樣寫。比上面省下1個字(?)
while (1) {

}

for 流程圖

for (;;) {

}

p.s. 2 - 多變數控制迴圈

Loop p.s. - 2

  • 控制迴圈的變數都寫一個i而已,可不可以有多個變數呢?
    • 可以!用逗號隔開就好。
    • 例如以下的 code,利用變數 i 和變數 j 一起控制迴圈。


       
  • 如你所見,第一個跟第三個區段都可以用 , 來同時做事
    • 如果想同時宣告不同型態呢?
      • 基本來說,沒有(簡單的)辦法 :(
for (int i = 0, j = 1; i < n && j <= m; i++, j*=2) {
  // 做事情
}

p.s. 3 - 變數生命週期

Loop p.s. - 3

  • 變數都有他的生命週期。
    • 在大括號裡面宣告的變數,就只能活在大括號裡面
    • 這個裡面宣告的變數跟外面的變數是完全獨立,
      是完全不一樣的個體
    • for / if / while / 甚至是main的大括號都一樣。
  • 像右邊這份code,會輸出什麼呢?

variable scope

#include <iostream>
using namespace std;
int main () {
    int i = 7;
    if (true) {
        cout << i;
        int i = 5;
        cout << i;
    }
    cout << i;
}
757
  • 第6行的cout:
    • 因為還沒看到第7行,所以這個i是指第4行的i。
  • 第8行的cout:
    • 這個i就是大括號裡面第7行的 int i。
  • 第10行的cout:
    • 因為變數生命週期,他看不到第7行的 int i,
      所以是輸出第4行的 i。

一份看起來很混亂,實際上也很混亂的code

p.s. 3 - 變數生命週期

Loop p.s. - 4

  • 看看右邊的這份code,會印出什麼呢?

variable scope

為什麼呢?

  1. for 只能接下面那一行,所以只會印出4個數字。
  2. 第5行的 for 迴圈的 int i; 只能作用在迴圈裡面
    所以第7行的 i 是吃到第4行的 int i;。

(A)

(B)

(C)

0 1 2 3 7

(D)

(C)

#include <iostream>
using namespace std;
int main () {
  int i = 7, n = 3;
  for (int i=0; i<n; i++)
    cout << i << " ";
    cout << i << endl;
}
0 0
1 1
2 2
3 3
Compiler Error
0 1 2 7

2 mins

Loop sp.

盤點那些你可能會遇到的神奇迴圈

sp. 0 - 輸入至EOF

Loop sp. - 0

  • 有時候你會看到這種東西:輸入直到EOF結束。那麼什麼是EOF呢?
    • EOFEnd Of File,直翻檔案結尾,也就是請你的程式要輸入到沒東西後停止
  • 怎麼判斷已經沒東西輸入了呢?
    • 好巧不巧,cin任何一個東西,要是它是false,就代表輸入完了。
    • 下面這份程式代表如果cin >> x是false (表示沒東西了),就會跳出迴圈。

取自 zerojudge a004 的輸入說明

int x;
while (cin >> x) {
  // 做事情
}

輸入一個數字直到EOF的範例

sp. 1 - while(Q--)

  • 如果題目說"接下來有Q筆操作"...
    • 那麼我們會習慣寫while(Q--){...}
       
  • 接著我們來複習一下:

Loop sp. - 1

*例如loop12

你曾經看過的 if-else 的投影片

  • 所以如果Q=2,模擬迴圈的話...
  1. while(2),進迴圈,並且Q會變1。
  2. while(1),進迴圈,並且Q會變0。
  3. while(0),出迴圈,並且Q會變-1。
  • 迴圈會跑兩次,恰好跟Q的次數一樣,這樣你懂while(Q--)的用法了嗎?

sp. 2 - --> / <-- 是什麼鬼語法?

Loop sp. - 2

  • 我們先來看一則stackoverflow上的奇聞軼事。

我沒有故意要湊表情符號@@

"-->"是什麼運算子?

在讀完...,我驚奇的發現下面這份code可以編譯&成功執行

我想這是C的語法,因為GCC編譯器也可以執行。請問他定義在編譯器標準的哪裡?

// x 趨近於0

stackoverflow
一個(通常)發問程式問題的地方。

sp. 2 - --> / <-- 是什麼鬼語法?

Loop sp. - 2

  • 所以 --> 到底是什麼?
    • 如果是 while (x --> 0) 其實就是 while ((x--) > 0)
  • <-- 呢?
    • 一樣的道理,while (0 <-- x) 其實就是 while (0 < (--x))
  • !!!!不要這樣寫,當作看故事就好!!!!

我沒有故意要湊表情符號@@

int Q = 4;
while (Q --> 0) {
  cout << "(" << Q << ")";
}
(3)(2)(1)(0)

Q --> 0 的範例 (4次)

int Q = 4;
while (Q --> -2) {
  cout << "(" << Q << ")";
}
(3)(2)(1)(0)(-1)(-2)

Q --> -2 的範例 (6次)

int Q = 4;
while (0 <-- Q) {
  cout << "(" << Q << ")";
}
(3)(2)(1)

0 <-- Q 的範例 (3次)

sp. 2 - --> / <-- 是什麼鬼語法?

Loop sp. - 2

  • 我們再來看其他人還有什麼創意回答 (?)
    • 一個反斜線代表程式還沒結束,換行繼續寫。
    • 雖然不能 x ----> 0,但是可以 0 <---- x。
      • 這跟 x-- 和 --x 的本質有關係,
        有興趣可以自己查 (?)

我沒有故意要湊表情符號@@

你長度多一點遞減比較快(?)

用反斜線畫斜向箭頭。

Exercise V

來看看APCS觀念題吧!

Exercise V

APCS觀念考古題

接下來有四題重複結構的APCS觀念題目。

  • 每題計時2~3分鐘
  • 在開始之前先複習一下之前的投影片喔!

APCS 105/3 - 觀念題 - 15

15. 若以f(22)呼叫右側 f()函式,總共會印出多少數字?

3 mins

void f(int n) {
  printf ("%d\n", n);
  while (n != 1) {
    if ((n%2)==1) {
      n = 3*n + 1;
    }
    else {
      n = n / 2;
    }
    printf ("%d\n", n);
  }
}

因為你可能看不懂,在這邊先解釋一下:

  • f(22) 的意思就是把n代22進去看就對了。
  •                                   就是
printf("%d\n", n);
cout << n << endl;

Exercise V - APCS觀念題 - I

這不就是考拉茲猜想 (3n+1問題)?

  • 22 -> 11 -> 34 -> 17 -> 52 -> 26 -> 13 -> 40 -> 20 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1。
  • 答案:總共16次。

APCS 105/3 - 觀念題 - 21

21. 右側程式碼,執行時的輸出為何?

2 mins

void main() {
  for (int i=0; i<=10; i=i+1) {
    // printf ("%d ", i);
    cout << i << " ";
    i = i + 1;
  }
  // printf ("%d ", i);
  cout << endl;
}
0 2 4 6 8 10
0 1 2 3 4 5 6 7 8 9 10

(A)

(B)

0 1 3 5 7 9

(C)

0 1 3 5 7 9 11

(D)

(A)

  1. 一開始 i = 0,所以印出0。
  2. 因為第5行, i 變成 1。

  3. 因為for迴圈的i=i+1,i 變成 2。

  4. 輸出 2。 (接著下去直到 i = 12才會跳出去。)

Exercise V - APCS觀念題 - II

APCS 105/10 - 觀念題 - 12

12. 右側程式片段執行過程中的輸出為何?

2 mins

int a = 5;
// ...
for (int i=0; i<20; i=i+1){
  i = i + a;
  // printf ("%d ", i);
  cout << i << " ";
}
5 10 15 20
5 11 17 23

(A)

(B)

6 12 18 24

(C)

6 11 17 22

(D)

(B)

Exercise V - APCS觀念題 - III

  1. 一開始進for迴圈不會跑控制運算 (i = i + 1),所以第一個數字是5。
  2. 接下來因為 i = i + a; 還有 i =i + 1; ,所以每次輸出前都會+6。

APCS 106/03 - 觀念題 - 13

13. 右側程式片段無法正確列印 20 次的"Hi!",請問下 列哪一個修正方式仍無法正確列印 20 次的"Hi!"?

for (int i=0; i<=100; i=i+5) {
  // printf ("%s\n", "Hi!");
  cout << "Hi!" << endl;
}
需要將 i<=100 和 i=i+5 分別修正為 i<20 和 i=i+1
需要將 i=0 修正為 i=5

(A)

(B)

需要將 i<=100 修正為 i<100;

(C)

需要將 i=0 和 i<=100 分別修正為 i=5 和 i<100

(D)

(D)

  • (A) 最常用的跑20次的迴圈寫法。
  • (B) 變數i的轉變: [5, 10, 15, 20, ... ,100] (到105就出去了) -> 20次。
  • (C) 變數i的轉變: [0, 5, 10, 15, 20, ..., 95] (到100就出去了) -> 也是20次。
  • (D) 變數i的轉變: [5, 10, ..., 95] (到100就出去了) -> 19次。

Exercise V - APCS觀念題 - IV

2 mins

Exercise VI

第二大數字判斷

Exercise VI

程式練習題

接下來有一題請你用
程式實做重複結構的題目。

  • 我會花1分鐘讀題。
  • 總共計時20分鐘
  • 10分鐘我會給個提示
  • 開好你的程式環境吧!

174 - 第二大數判斷 - 前導

Exercise V - 第二大數字判斷 - -1

20 mins

174 - 第二大數判斷 - 題目

Exercise V - 第二大數字判斷 - 0

  • 題目:給你一串數字,判斷第二大的數字是誰,重複也算。(數字範圍為0 ~ 2^64-1)

20 mins

  • 提示:先想想看怎麼找出最大的數字
  • 提示的提示:你可以在外面開一個變數紀錄,像判斷質數的is_prime一樣。
  • 不是提示的建議:如果你已經會陣列,不要開陣列做這題喔!
  • 例如,題目輸入5個數字,5 4 3 2 1,那麼你應該輸出4。

174 - 第二大數判斷 - 提示的解答

20 mins

#include <iostream>
using namespace std;
int main () {
  unsigned long long n, fmax;
  cin >> n;
  for (int i=0; i<n; i++) {
    unsigned long long x;
    cin >> x;
    if (i == 0 || x > fmax)
    	fmax = x;
  }
  cout << fmax << endl;
}

找出最大的數字,想想看為甚麼要加上判斷 i == 0?

  • 下一個提示:想想x,目前最大的數字,還有目前第二大數字,這三者關係
  • 提示:先想想看怎麼找出最大的數字
  • 提示的提示:你可以在外面開一個變數紀錄,像判斷質數的is_prime一樣。
  • 題目:給你一串數字,判斷第二大的數字是誰,重複也算。(數字範圍為0 ~ 2^64-1)

Exercise V - 第二大數字判斷 - 1

174 - 第二大數判斷 - 引導

20 mins

  • 下一個提示:想想x,目前最大的數字,還有目前第二大數字,這三者關係

目前最大值
fmax

目前第二大值
smax

x >= fmax

fmax >= x x >= smax

smax >= x

  • x 取代最大值
  • 最大值淪為第二大值
  • x 取代第二大值
  • 第二大值消失:(
  • x 不需要啦
if (x > fmax)
  smax = fmax, fmax = x;
else if (x > smax)
  smax = x;

大概寫出來的樣子

  • 奇怪,那麼一開始變數設置怎麼辦呢?
  • 題目:給你一串數字,判斷第二大的數字是誰,重複也算。(數字範圍為0 ~ 2^64-1)

Exercise V - 第二大數字判斷 - 2

174 - 第二大數判斷 - 解答

20 mins

#include <iostream>
using namespace std;
int main () {
  unsigned long long n, fmax, smax;
  cin >> n;
  for (int i=0; i<n; i++) {
    unsigned long long x;
    cin >> x;
    if (i == 0 || x > fmax)
      smax = fmax, fmax = x;
    else if (i == 1 || x > smax)
      smax = x;
  }
  cout << smax << endl;
}
  • 題目:給你一串數字,判斷第二大的數字是誰,重複也算。(數字範圍為0 ~ 2^64-1)

最終解答的長相

  • 奇怪,那麼一開始變數設置怎麼辦呢?
if (x > fmax)
  smax = fmax, fmax = x;
else if (x > smax)
  smax = x;

大概寫出來的樣子

if (i == 0 || x > fmax)
  smax = fmax, fmax = x;
else if (x > smax)
  smax = x;

如果是第一個數字,強制設成fmax就可以了,那smax...?

Exercise V - 第二大數字判斷 - 3

174 - 第二大數判斷 - 更方便的解答

20 mins

  • 題目:給你一串數字,判斷第二大的數字是誰,重複也算。(數字範圍為0 ~ 2^64-1)
  • 奇怪,那麼一開始變數設置怎麼辦呢?
  • 其實如果你知道最小值,
    直接把兩個設成最小值就好了。
unsigned long long fmax = 0;
unsigned long long smax = 0;
if (x > fmax)
  smax = fmax, fmax = x;
else if (x > smax)
  smax = x;

大概寫出來的樣子

Exercise V - 第二大數字判斷 - 4

#include <iostream>
using namespace std;
int main () {
  unsigned long long n, fmax=0, smax=0;
  cin >> n;
  for (int i=0; i<n; i++) {
    unsigned long long x;
    cin >> x;
    if (x > fmax)
      smax = fmax, fmax = x;
    else if (x > smax)
      smax = x;
  }
  cout << smax << endl;
}

最終解答的長相

Review

重複結構的語法小總結

複習 - 看過的題目們

Review - 0

  • 再變得更難之前,我們來看看我們曾經做過什麼吧!

    • ​循環的猜數字 (goto)

    • 可控隨機數 (看著流程圖用goto實作 -> for + do-while)

    • 考拉茲猜想 (goto -> do-while -> while)

    • 進位制轉換 (do-while)

    • 累加數字 1+2+...+n (while -> for)

    • ​判斷質數 (while -> +break)​

    • 第二大數字判斷 (for + 複雜的if判斷)
       
  • ​接著我們來想想看什麼時候適合用哪些語法吧!

goto

Review - 1

  • 最簡單的重複結構。
    • 如果有多個情況都會跳到同個地方,比較適合。
      • 在猜數字的時候,不管是猜的比較大還是比較小,都要回到一開始重猜一次。
      • 在處理輸入錯誤的時候,不管是哪種錯誤訊息都要回去請使用者重輸入一次。
        • 如果你熟悉後面的語法的話,這用try-catch會比較好。
    • 還有一個很神奇的用法,我們之後會提到。
    • goto並不能break和continue。程式不知道你要bre啥。

goto流程圖

// 設下標記點
labelA:       
do_something();
// 跳到標記點
goto labelA;  

goto 語法

do-while

Review - 2

  • 最簡單帶有條件的重複結構。
    • 不固定次數,且至少做一次的時候使用。
      • 例如生成亂數,就至少要做一次。
      • 例如進位制轉換,因為就算是0也要輸出一個0,所以至少要做一次
  • ​小括號後最後有分號,不要忘記。
do {
  do_something();
  // 如果true會跳到do{那行
} while (true 或 false);

do-while流程圖

do-while 語法

while

Review - 3

while流程圖

// while 一開始,如果判斷為F則跳到}後
while (T / F) {
  // 做事情
} // 結束會回到while(){重新判斷

while 語法

  • 事先檢查的 do-while
    • 不固定次數,需要事先檢查的時候使用。
      • 例如3n+1問題,一開始就要判斷是不是1。

for

Review - 4

for 流程圖

  • 控制變數放在一起的的重複結構。
    • 常用在有固定次數的迴圈上。
      • 例如固定跑n次的1到n加總。
    • 最重要的重複結構。
  • 三個區塊要用分號隔開。
for (初始設置; True / False; 控制運算) {
  // 做事情
}

for 語法

break & continue

Review - 5

for + break

for + continue

  • break; 強制跳出這層迴圈。
  • continue; 直接重來這個迴圈。
  • 當時的example code:
#include <iostream>
using namespace std;
int main () {
  for (int i=0; i<10; i++) {
    if (i % 2 == 0)
      continue;
    if (i == 7)
      break;
    cout << i << " ";
  }
}
輸出: 1 3 5

Nested Loop

該來的終究是要來的 - 巢狀迴圈

什麼是巢狀迴圈

Nested Loop - 0

  • 我們先來看看什麼是巢狀結構。

  • 巢狀迴圈其實就是迴圈裡面還有迴圈。 That's it.
  • 但很不好理解,好好學喔!

巢狀選擇結構的投影片。

在這之前,先看看第一個例題!

Nested Loop - 1

  • 還記得Exercise I - 可控隨機數嗎?

    • 你能不使用goto,用其他種迴圈實作出來嗎?

int cnt = 0;
start:
  int x = LFSR() % (n+1);
  if (x % k == 0)
    goto start;
  cout << x << endl;
  cnt ++;
  if (cnt != 5)
    goto start;

可控隨機數的流程圖

可控隨機數的code

在這之前,先看看第一個例題!

Nested Loop - 2

  • 還記得Exercise I - 可控隨機數嗎?

    • do-while處理生亂數,因為次數不定,而且要先做一次

int x, cnt = 0;
start:
  do {
    x = LFSR() % (n+1);
  } while (x % k == 0);
  cout << x << endl;
  cnt ++;
  if (cnt != 5)
    goto start;

可控隨機數的流程圖

生成亂數轉成比較可讀的迴圈

在這之前,先看看第一個例題!

Nested Loop - 3

  • 還記得Exercise I - 可控隨機數嗎?

    • for處理計數字,因為固定要5次

    • 你會發現,這竟然是for + do-while雙重迴圈。

    • Wow!竟然我們一開始學的例題就是巢狀迴圈嗎?

int x;
for (int cnt=0; cnt < 5; cnt++) {
  do {
    x = LFSR() % (n+1);
  } while (x % k == 0);
  cout << x << endl;
}

可控隨機數的流程圖

計數五次轉成比較可讀的迴圈

Double for

最經典的巢狀迴圈 - nm雙重迴圈

什麼是笛卡爾座標系?

Double for - 0

  • 你知道什麼是一維座標系嗎?

    • 我們可以用一個迴圈印出所有的點。

一個笛卡爾坐標系的範例圖

一個一維座標系(數線)的範例圖

for (int i=0; i<=n; i++) {
  cout << "(" << i << ")\t";
}
  • 那你知道什麼是二維(笛卡爾)座標系嗎?
    • 我們可以用一個雙層迴圈印出所有的點。

* '\t'叫做水平定位符,簡單來說就是幫你空格到下一個八的倍數的地方。

印出笛卡爾座標

Double for - 1

一個笛卡爾坐標系的圖

int n, m;
cin >> n >> m;
//==========================
for (int j=0; j<=m; j++) {
  cout << "(" << j << ")\t";
}
cout << endl;
//==========================
for (int j=0; j<=m; j++) {
  cout << "(" << j << ")\t";
}
cout << endl;
//==========================
for (int j=0; j<=m; j++) {
  cout << "(" << j << ")\t";
}
cout << endl;
// 重複做n+1次
  • 那你知道什麼是二維(笛卡爾)座標系嗎?
    • 我們可以用一個雙層迴圈印出所有的點。
    • 把它想成是做n次的一維迴圈就可以了。
int n, m;
cin >> n >> m;
for (int i=0; i<=n; i++) {
  for (int j=0; j<=m; j++) {
    cout << "(" << j << ")\t";
  }
  cout << endl;
}

Double for - 2

一個笛卡爾坐標系的圖

  • 那你知道什麼是二維(笛卡爾)座標系嗎?
    • 在把 i 和 j 印出來,這裡的 i 就是在指
      現在是第幾次跑裡面的迴圈。
(0,0)   (0,1)   (0,2)   (0,3)   (0,4)
(1,0)   (1,1)   (1,2)   (1,3)   (1,4)
(2,0)   (2,1)   (2,2)   (2,3)   (2,4)
(3,0)   (3,1)   (3,2)   (3,3)   (3,4)
(4,0)   (4,1)   (4,2)   (4,3)   (4,4)

印出笛卡爾座標

#include <iostream>
using namespace std;
int main () {
  int n, m;
  cin >> n >> m;
  for (int i=0; i<=n; i++) {
    for (int j=0; j<=m; j++) {
      cout << "(" << i << "," << j << ")\t";
    }
    cout << endl;
  }
}

Double for - 3

  • 印出來你會發現這個座標好像是順序怪怪的?
    • 沒有錯,因為印字順序的關係,
      這就是程式裡面常用的座標方向。
    • 也就是x為往下縱軸,y為往右橫軸
(0,0)   (0,1)   (0,2)   (0,3)   (0,4)
(1,0)   (1,1)   (1,2)   (1,3)   (1,4)
(2,0)   (2,1)   (2,2)   (2,3)   (2,4)
(3,0)   (3,1)   (3,2)   (3,3)   (3,4)
(4,0)   (4,1)   (4,2)   (4,3)   (4,4)

關於程式的笛卡爾座標

輸入n, m,印出nm平面座標的程式。

#include <iostream>
using namespace std;
int main () {
  int n, m;
  cin >> n >> m;
  for (int i=0; i<=n; i++) {
    for (int j=0; j<=m; j++) {
      cout << "(" << i << "," << j << ")\t";
    }
    cout << endl;
  }
}
(0, 0)
j (y)
i (x)

n, m = 4, 4的二維座標系

笛卡爾座標 → 九九乘法表

Double for - 4

  • 接著我們利用座標系的雙層迴圈,做點轉換。

  • 題目:你能夠依據右邊的程式,寫出一個
    九九乘法表嗎? (從1到9,沒有0。)

  • 提示:觀察印出來的座標和99乘法表的關係。

    • 把範圍跟輸出稍微改一下就可以了!

#include <iostream>
using namespace std;
int main () {
  for (int i=1; i<=9; i++) {
    for (int j=1; j<=9; j++)
      cout << i * j << "\t";
    cout << endl;
  }
}

10 mins

輸入n, m,印出nm平面座標的程式。

#include <iostream>
using namespace std;
int main () {
  int n, m;
  cin >> n >> m;
  for (int i=0; i<=n; i++) {
    for (int j=0; j<=m; j++) {
      cout << "(" << i << "," << j << ")\t";
    }
    cout << endl;
  }
}
(0,0)   (0,1)   (0,2)   (0,3)   (0,4)
(1,0)   (1,1)   (1,2)   (1,3)   (1,4)
(2,0)   (2,1)   (2,2)   (2,3)   (2,4)
(3,0)   (3,1)   (3,2)   (3,3)   (3,4)
(4,0)   (4,1)   (4,2)   (4,3)   (4,4)

n, m = 4, 4的二維座標系

一般的九九乘法表。

一份code告訴你為甚麼不要濫用goto

  • 如果你把九九乘法表的迴圈全部都用 goto 實作,你會覺得這到底是三小==。

goto 實作九九乘法表。

#include <iostream>
using namespace std;
int main() {
  int i = 1;
  outer:
    int j = 1;
    inner:
      cout << i * j << "\t";
      j += 1;
      if (j <= 9)
        goto inner;
    cout << endl;
    i += 1;
    if (i <= 9)
      goto outer;
}

Double for - 5

Exercise VII

指數表

Exercise VII

程式練習題

接下來有一題請你用
程式實做多重迴圈的題目。

  • 我會花1分鐘讀題。
  • 總共計時20分鐘
  • 10分鐘我會給個提示
  • 開好你的程式環境吧!

179 - 指數表 - 題目

Exercise VII - 指數表 - 0

  • 題目:給n, m,請輸出n, m的指數表 (中間用空格隔開,從0, 0開始),輸出都是int。

20 mins

  • 提示1:使用像剛剛笛卡爾座標→九九乘法表的思考方式。
  • 提示2:你的迴圈有可能會變三層。
"1\t0\t0\t0\t0\t0\n"
"1\t1\t1\t1\t1\t1\n"
"1\t2\t4\t8\t16\t32\n"
"1\t3\t9\t27\t81\t243\n"
"1\t4\t16\t64\t256\t1024\n"
"1\t5\t25\t125\t625\t3125\n"

輸入 5 5 應該要輸出的表

1       0       0       0       0       0
1       1       1       1       1       1
1       2       4       8       16      32
1       3       9       27      81      243
1       4       16      64      256     1024
1       5       25      125     625     3125

怕你格式有問題,這邊印出C++表達式。

(記得行末沒有\t。)

179 - 指數表 - 提示

20 mins

  • 給定數字n, m,你會算出n的m次方嗎? 應該不難。
    • 盡量不要使用cmath的pow,避免有浮點數誤差。(雖然還是會AC...)

Exercise VII - 指數表 - 1

int x = 1;
for (int k=1; k<=m; k++) {
  x *= n;
}
  • 題目:給n, m,請輸出n, m的指數表 (中間用空格隔開,從0, 0開始),輸出都是int。
n,m
n^m

給定數字       ,算出      

179 - 指數表 - 答案

20 mins

Exercise VII - 指數表 - 2

#include <iostream>
using namespace std;
int main () {
  int n, m;
  cin >> n >> m;
  for (int i=0; i<=n; i++) {
    for (int j=0; j<=m; j++) {
      cout << "(" << i << "," << j << ")\t";
    }
    cout << endl;
  }
}

輸入       ,印出       平面座標的程式。

#include <iostream>
using namespace std;
int main () {
  int n, m;
  cin >> n >> m;
  for (int i=0; i<=n; i++) {
    for (int j=0; j<=m; j++) {
      // 想要印出 i^j
      int x = 1;
      for (int k=1; k<=j; k++) {
        x *= i;
      }
      cout << x << (j != m ? '\t' : '\n');
    }
  }
}
  • 題目:給n, m,請輸出n, m的指數表 (中間用空格隔開,從0, 0開始),輸出都是int。

把     的程式貼上去改一改就可以了

n^m
n,m
nm

179 - 指數表 - 更快的答案

20 mins

Exercise VII - 指數表 - 2

#include <iostream>
using namespace std;
int main () {
  int n, m;
  cin >> n >> m;
  for (int i=0; i<=n; i++) {
    int x = 1;
    for (int j=0; j<=m; j++) {
      cout << x  << (j != m ? '\t' : '\n');
      x *= i;
    }
  }
}

其實      可以透過         來算出,
利用前面算過的值就不用跑這麼多次了!

#include <iostream>
using namespace std;
int main () {
  int n, m;
  cin >> n >> m;
  for (int i=0; i<=n; i++) {
    for (int j=0; j<=m; j++) {
      // 想要印出 i^j
      int x = 1;
      for (int k=1; k<=j; k++) {
        x *= i;
      }
      cout << x  << (j != m ? '\t' : '\n');
    }
  }
}

把     的程式貼上去改一改就可以了

  • 題目:給n, m,請輸出n, m的指數表 (中間用空格隔開,從0, 0開始),輸出都是int。
n^m
n^{m-1}
n^m

Double Break !!

如何一次break兩個迴圈?

Double Break !!

Double Break!! - 0

  • 有時候我們會使用break來提早結束運算。
    • 在質數判斷的時候,我們用break來跳出一層迴圈。
    • 那兩層呢?該怎麼辦?

10 mins

i \times j = 49
#include <iostream>
using namespace std;
int main () {
  for (int i=1; i<=9; i++) {
    for (int j=1; j<=9; j++)
      cout << i * j << "\t";
    cout << endl;
  }
}

一般的九九乘法表。

  • 題目:試著在九九乘法表裡面,                   的時候直接跳出雙層迴圈

Double Break !! - flag

Double Break!! - 1

10 mins

i \times j = 49
for (int i=1; i<=9; i++) {
  bool flag = false;
  for (int j=1; j<=9; j++) {
    cout << i * j << "\t";
    if (i*j == 49) {
      flag = true;
      break;
    }
  }
  cout << endl;
  if (flag)
    break;
}

利用變數來跳脫雙重迴圈

  • 題目:試著在九九乘法表裡面,                   的時候直接跳出雙層迴圈
  • 你可以試著這樣思考:要怎麼樣才可以讓外面的迴圈知道要跳出呢?
    • 用flag紀錄到底該不該跳出去。

Double Break !! - goto

Double Break!! - 2

10 mins

i \times j = 49
for (int i=1; i<=9; i++) {
  bool flag = false;
  for (int j=1; j<=9; j++) {
    cout << i * j << "\t";
    if (i*j == 49)
      goto end;
  }
  cout << endl;
}
// 稍微注意一下,標記點之後
// 沒有code要加分號。
end:;

利用 goto 來跳脫雙重迴圈

  • 題目:試著在九九乘法表裡面,                   的時候直接跳出雙層迴圈
  • 你其實還可以使用 goto。 驚喜不?
    • 「沒有不該用的語法,只有用的好不好而已。」

Exercise VIII - I

孤單的機器人

Exercise VIII

程式練習題

接下來有一題請你用
程式實做多重迴圈的題目。

  • 我會花1分鐘讀題。
  • 總共計時20分鐘
  • 10分鐘我會給個提示
  • 開好你的程式環境吧!

180 - 孤單的機器人 - 題目

  • 題目:給定地圖的高n與寬m,機器人的座標x, y,以及指令數量Q,
    接下來Q個指令只會是"WASD"其中一個,代表要對機器人往哪個方向移動。
  • 對於每個指令,輸出目前地圖長相,機器人的位置為'@',空地為'.'。
  • 如果機器人的下一步會卡在牆裡面,則忽略該指令。

20 mins

Exercise VIII - I - 孤單的機器人 - 1

.@..
....
....

輸入:
n, m = 3, 4
x, y = 0, 1
Q = 3

初始地圖
(不用輸出)

..@.
....
....
..@.
....
....
....
..@.
....

D

W

S

往右移動

往上移動
但碰壁,不能移動

往下移動

結束!

忽略指令
也要輸出。

180 - 中場影片

Exercise VIII - I - 孤單的機器人 - 2

180 - 孤單的機器人 - 提示

  • 題目:給定地圖的高n與寬m,機器人的座標x, y,以及指令數量Q,
    接下來Q個指令只會是"WASD"其中一個,代表要對機器人往哪個方向移動。
  • 對於每個指令,輸出目前地圖長相,機器人的位置為'@',空地為'.'。
  • 如果機器人的下一步會卡在牆裡面,則忽略該指令。

20 mins

  • 怎麼輸出地圖呢?
    • 輸出nm格子,預設為'.',座標等於機器人就輸出'@'。
for (int i=0; i<n; i++) {
  for (int j=0; j<m; j++) {
    if (i == x && j == y)
      cout << '@';
    else
      cout << '.';
  }
  cout << endl;
}

Exercise VIII - I - 孤單的機器人 - 3

180 - 孤單的機器人 - 引導

  • 題目:給定地圖的高n與寬m,機器人的座標x, y,以及指令數量Q,
    接下來Q個指令只會是"WASD"其中一個,代表要對機器人往哪個方向移動。
  • 對於每個指令,輸出目前地圖長相,機器人的位置為'@',空地為'.'。
  • 如果機器人的下一步會卡在牆裡面,則忽略該指令。

20 mins

  • 你只要會畫圖,它其實就是一般的if-else題目了!
    • 如果指令要往上 ('W'),且機器人還可以往上走 (x != 0)就往上走 (x--;)
    • 四個方向寫完就像下面這樣 :
char cmd;
cin >> cmd;
if (cmd == 'W' && x != 0) x--;
if (cmd == 'A' && y != 0) y--;
if (cmd == 'S' && x != n-1) x++;
if (cmd == 'D' && y != m-1) y++;

Exercise VIII - I - 孤單的機器人 - 4

180 - 孤單的機器人 - 答案

  • 題目:給定地圖的高n與寬m,機器人的座標x, y,以及指令數量Q,
    接下來Q個指令只會是"WASD"其中一個,代表要對機器人往哪個方向移動。
  • 對於每個指令,輸出目前地圖長相,機器人的位置為'@',空地為'.'。
  • 如果機器人的下一步會卡在牆裡面,則忽略該指令。

20 mins

  • 引導(改座標)提示(畫圖)結合起來,就可以做出答案了!
#include <iostream>
using namespace std;
int main () {
  int n, m, x, y, Q;
  cin >> n >> m >> x >> y >> Q;
  while (Q--) {
    char cmd;
    cin >> cmd;
    if (cmd == 'W' && x != 0) x--;
    if (cmd == 'A' && y != 0)  y--;
    if (cmd == 'S' && x != n-1) x++;
    if (cmd == 'D' && y != m-1) y++; 
    for (int i=0; i<n; i++) {
      for(int j=0; j<m; j++) {
        if (i == x && j == y) 
          cout << '@';
        else
          cout << '.';
      }
      cout << endl;
    }
  }
  return 0;
}

Exercise VIII - I - 孤單的機器人 - 5

感覺很長的解答code

Exercise VIII - II

孤單的機器人 (補充)

接下來我們要教的東西...

  • 只有Windows可以用!

  • Linux / MacOS 也有對應的方法,只是比較難寫😢,你可能要上網查。

    • 如果你沒有Windows電腦可能就只能聽我們講了😢

  • 你只要稍微改一下就變成keylogger,這是一種惡意程式,好孩子不要亂用喔😎

  • 我學會的當下都覺得自己是Hackerman。

Exercise VIII - II - 孤單的機器人 (補充) - 0

GetKeyState(VK) - 介紹

  • 你不覺得一直W⏎A⏎S⏎D⏎很麻煩嗎?
    • ​所以我們在這邊教你一個可以不用按Enter的方法。

Exercise VIII - II - 孤單的機器人 (補充) - 1

#include <windows.h>
#include <iostream>
using namespace std;
int main () {
  while (true) {
    cout << GetKeyState(VK_LEFT);
    // 停0.1秒
    Sleep(100);
  }
}

跑跑看這份code!然後按按看左鍵(?)

5 mins

GetKeyState(VK) - 觀察

  • 你不覺得一直W⏎A⏎S⏎D⏎很麻煩嗎?
    • ​所以我們在這邊教你一個可以不用按Enter的方法。
    • 你會得到左邊的規律。
    • 那該怎麼讓他按一下觸發一次呢?例如按一下左鍵就輸出一次LEFT?

Exercise VIII - II - 孤單的機器人 (補充) - 2

#include <windows.h>
#include <iostream>
using namespace std;
int main () {
  while (true) {
    cout << GetKeyState(VK_LEFT);
    // 停0.1秒
    Sleep(100);
  }
}

跑跑看這份code!然後按按看左鍵(?)

0

-127

1

-128

按下

按下



10 mins

GetKeyState(VK) - 使用

  • 你不覺得一直W⏎A⏎S⏎D⏎很麻煩嗎?
    • 那該怎麼讓他按一下觸發一次呢?例如按一下左鍵就輸出一次LEFT?
    • 開一個變數紀錄到底是不是從沒有按 -> 按下。

Exercise VIII - II - 孤單的機器人 (補充) - 3

#include <windows.h>
#include <iostream>
using namespace std;
int main () {
  bool pressed = false;
  while (true) {
    int getKeyLeft = GetKeyState(VK_LEFT);
    if (getKeyLeft >= 0)
      pressed = false;
    else if (!pressed) {
      pressed = true;
      cout << "Left!" << endl;
    }    
  }
}

0

-127

1

-128

按下

按下



10 mins

pressed =
false

pressed =
true

pressed =
false

pressed =
true

GetKeyState(VK) - 應用

  • 題目:請將剛剛你AC的code,稍加修改一下。變成只要有按上下左右任何一個按鍵,就輸出現在的地圖。
    • 下面這是節錄虛擬按鍵(Virtual Key)的表,全部的表連結在這裡
    • 接著來看看實作出來的程式大概長怎樣吧!

Exercise VIII - II - 孤單的機器人 (補充) - 4

10 mins

按下A: GetKeyState('A'),只能用大寫,A-Z同理。
按上下左右:  VK_UP / VK_DOWN / VK_LEFT / VK_RIGHT
滑鼠左/右鍵: VK_LBUTTON / VK_RBUTTON
Enter: VK_RETURN
左/右Shift: VK_LSHIFT / VK_RSHIFT
F1...F12: VK_F1 ... VK_F12
...

跑出來大概的樣子

GetKeyState(VK) - 應用

  • 如果你學會陣列,你就可以不用寫這麼長的code喔!
  • 一直往下印,好沒實感...
    • 你可以用                             來清空輸出,就像這份程式
    • 有沒有覺得漂亮很多了呢?

Exercise VIII - II - 孤單的機器人 (補充) - 5

10 mins

system("cls");

可以做到像這樣的事情。

Exercise IX

簡單的小應用 - 解析幾何小幫手

Exercise IX

程式練習題

接下來有一題請你用
程式實做重複結構的題目。

  • 我會花1分鐘讀題。
  • 總共計時20分鐘
  • 10分鐘我會給個提示
  • 開好你的程式環境吧!

182 - 解析幾何小幫手 - 題目

  • 題目:給定畫布               ,以及不等式
    請在不等式成立的地方畫上'*',不是請留空白' '。

20 mins

Exercise IX - 解析幾何小幫手 - 1

ax^2 + bxy + cy^2 + dx +ey \ge f
x^2 + 2y \ge 5
*****
*****
*****
*****
 ****
  ***
  ***
  ***
   **
n=4, m=8

範例測資1的輸入

不等式的長相

畫布該畫的區域

你的程式該輸出...

* 左下角(0, 0),
右上角(n, m)

(n, m)

182 - 解析幾何小幫手 - 解答

  • 題目:給定畫布               ,以及不等式
    請在不等式成立的地方畫上'*',不是請留空白' '。
  • 注意雙層迴圈的順序就可以了。
    • 先是y軸,從m印到0,再來才是x軸,0印到n。

20 mins

Exercise IX - 解析幾何小幫手 - 2

ax^2 + bxy + cy^2 + dx +ey \ge f
(n, m)
#include <iostream>
using namespace std;
int main() {
  int n, m, a, b, c, d, e, f;
  cin >> n >> m >> a >> b >> c >> d >> e >> f;
  for(int y=m; y>=0; y--) {
    for(int x=0; x<=n; x++){
      bool constraint = (a*x*x + b*x*y + c*y*y + d*x + e*y >= f);
      cout << (constraint ? '*' : ' ');
    }
    cout << endl;
  }
  return 0;
}

Exercise X

內外迴圈的交纏 - 數字三角形

Exercise VII

程式練習題

接下來有一題請你用
程式實做重複結構的題目。

  • 我會花1分鐘讀題。
  • 總共計時20分鐘
  • 開好你的程式環境吧!

181 - 數字三角形 - 題目

  • 題目:給定n,請輸出一個兩股長度為n的數字三角形。
  • 提示:剛好印出的是n*n的格子也?加上if會怎麼樣?

20 mins

Exercise X - 數字三角形 - 0

__+
_++
+++

n = 3 的樣子

n = 5 的樣子

___________+
__________++
_________+++
________++++
_______+++++
______++++++
_____+++++++
____++++++++
___+++++++++
__++++++++++
_+++++++++++
++++++++++++

n = 12 的樣子

____+
___++
__+++
_++++
+++++

181 - 數字三角形 - 引導 - 1

  • 題目:給定n,請輸出一個兩股長度為n的數字三角形。
  • 提示:剛好印出的是n*n的格子也?加上if會怎麼樣?
    • 那些點要印出+呢?
    • 那有什麼規律呢?
      •  

20 mins

____+
___++
__+++
_++++
+++++

n = 5的樣子

(0,0)\, \, (0,1)\, \, (0,2)\, \, (0,3)\, \, (0,4) \\ (1,0)\, \, (1,1)\, \, (1,2)\, \, (1,3)\, \, (1,4) \\ (2,0)\, \, (2,1)\, \, (2,2)\, \, (2,3)\, \, (2,4) \\ (3,0)\, \, (3,1)\, \, (3,2)\, \, (3,3)\, \, (3,4) \\ (4,0)\, \, (4,1)\, \, (4,2)\, \, (4,3)\, \, (4,4)
(0,0)\, \, (0,1)\, \, (0,2)\, \, (0,3)\, \, \red{(0,4)} \\ (1,0)\, \, (1,1)\, \, (1,2)\, \, \red{(1,3)}\, \, \red{(1,4)} \\ (2,0)\, \, (2,1)\, \, \red{(2,2)}\, \, \red{(2,3)}\, \, \red{(2,4)} \\ (3,0)\, \, \red{(3,1)}\, \, \red{(3,2)}\, \, \red{(3,3)}\, \, \red{(3,4)} \\ \red{(4,0)}\, \, \red{(4,1)}\, \, \red{(4,2)}\, \, \red{(4,3)}\, \, \red{(4,4)}
i+j \ge n-1
j (y)
i (x)

Exercise X - 數字三角形 - 1

181 - 數字三角形 - 解答 - 1

  • 題目:給定n,請輸出一個兩股長度為n的數字三角形。
  • 那些點要印出+呢?

20 mins

i+j \ge n-1
#include <iostream>
using namespace std;
int main() {
  int n;
  cin >> n;
  for (int i=0; i<n; i++) {
    for (int j=0; j<n; j++) {
       if (i+j >= n-1)
         cout << "+";
       else
         cout << "_";
    }
    cout << endl;
  }
}

雙層迴圈 + if-else 的解答

(0,0)\, \, (0,1)\, \, (0,2)\, \, (0,3)\, \, \red{(0,4)} \\ (1,0)\, \, (1,1)\, \, (1,2)\, \, \red{(1,3)}\, \, \red{(1,4)} \\ (2,0)\, \, (2,1)\, \, \red{(2,2)}\, \, \red{(2,3)}\, \, \red{(2,4)} \\ (3,0)\, \, \red{(3,1)}\, \, \red{(3,2)}\, \, \red{(3,3)}\, \, \red{(3,4)} \\ \red{(4,0)}\, \, \red{(4,1)}\, \, \red{(4,2)}\, \, \red{(4,3)}\, \, \red{(4,4)}
j (y)
i (x)

Exercise X - 數字三角形 - 2

181 - 數字三角形 - 解答 - 2

20 mins

#include <iostream>
using namespace std;
int main() {
  int n;
  cin >> n;
  for (int i=0; i<n; i++) {
    for (int j=0; j<n-i-1; j++)
      cout << "_";
    for (int j=0; j<=i; j++)
      cout << "+";
    cout << endl;
  }
}

內層迴圈拆解的解答

  • 題目:給定n,請輸出一個兩股長度為n的數字三角形。
  • 仔細想想,_要印幾次呢?
  • 每一行 + 又要印幾次呢?
  • 接著把迴圈分解是不是就好了呢?
n-i-1次
i+1 次
(0,0)\, \, (0,1)\, \, (0,2)\, \, (0,3)\, \, \red{(0,4)} \\ (1,0)\, \, (1,1)\, \, (1,2)\, \, \red{(1,3)}\, \, \red{(1,4)} \\ (2,0)\, \, (2,1)\, \, \red{(2,2)}\, \, \red{(2,3)}\, \, \red{(2,4)} \\ (3,0)\, \, \red{(3,1)}\, \, \red{(3,2)}\, \, \red{(3,3)}\, \, \red{(3,4)} \\ \red{(4,0)}\, \, \red{(4,1)}\, \, \red{(4,2)}\, \, \red{(4,3)}\, \, \red{(4,4)}
j (y)
i (x)

Exercise X - 數字三角形 - 3

j \ge n-i-1 \rightarrow

結語

兩天的迴圈終於結束了!

最終複習

  • 在一般的重複結構,你學到了四種語法:
    • goto - 一種可以達到簡單重複結構的語法。
    • do-while - goto 結合 if-else 的迴圈。
    • while - 事先檢查的 do-while 迴圈。
    • for - 一種把控制迴圈的變數放在一起的簡化迴圈。
  • 在巢狀的重複結構,你主要學到了靠座標化去思考問題。
    • 指數表中,你會用雙層迴圈 + 一層迴圈(n^m) 變成三層迴圈!
    • 數字三角形中,你會用規律來決定要印+還是_,還有拆解迴圈。
  • ​你還學到了一些奇奇怪怪的知識:
    • ​在可控隨機數 Controllable RNG,你學到了什麼是RNG
    • 孤單的機器人,你學到了怎麼做鍵盤偵測

Review - 0

接下來的課程 - 陣列

  • 接下來你會上到甚麼呢?
    • 陣列 / 多維陣列, 大致上就是讓你可以一次存一個數列或多維矩陣。
    • 陣列很常利用迴圈來處理,所以希望大家回家可以好好複習迴圈喔 ~

Review - 1

Peipei的投影片

接下來的課程 - 陣列

  • 陣列真的非常重要(?)
    • 我們來看看多維陣列的例子:
    • 陣列很重要,所以迴圈也很重要。

Review - 2

影片: 四維陣列 (時間, RGBA, X軸, Y軸)

格子狀地圖: 二維陣列 ((X,Y)放了甚麼)

End

明天就交給裴裴講師了(?)

題目表 - D1 - 單層迴圈

題目表 - 0

題號 名稱 註記
170 考拉茲猜想 上課習題
171 可控隨機數 上課習題
172 二進位制轉換 - I 上課習題
173 判斷質數 - I 上課習題
174 電神帆哥的煩惱 上課習題
175 最有價值玩家  
176 吃甜點好幸福  
177 吃西瓜 APCS 考古題
178 破譯密文 APCS 考古題

題目表 - D2 - 巢狀迴圈

題目表 - 1

題號 名稱 註記
179 指數表 上課習題
180 孤單的機器人 上課習題
181 みるく的疑惑 上課習題
182 解析幾何小幫手 上課習題
183 質因數分解  
184 印菱形  
185 開店的奇怪法律問題  

APCS camp - Loop (+ Nested)

By Arvin Liu

APCS camp - Loop (+ Nested)

APCS Camp Loop

  • 839