# 2020q3 Homework2 (quiz2) contributed by < `ptzling310` > ## 事前作業 - [ ] 何為 ASCII? - [ ] 數值系統 - [ ] bitwise 操作 - [ ] C99 : array object, string literal ## ASCII (American Standard Code for Information Interchange) table - 字元集及其編碼。 - ASCII 碼有7位碼和8位碼兩種形式。 - ASCII 表格 | 範圍 | 代表 | | -------- | -------- | -------- | | 第0~32號及第127號(共34個) | 控制字元或通訊專用字元 | | 第33~126號(共94個) | 字元(含標點符號) | | 第48~57號 | 字元:0~9 | | 第65~90號|字元:26個大寫英文字母| | 第97~122號|字元:26個小寫英文字母 - Extended Character Set : 第 128~255 號為擴展字元 ## 測驗 `1`:判斷指定的記憶體範圍內是否全是有效的 ASCII 字元 ### 版本一:一次比較一字元 - ASCII 碼的取值範圍是 ==0~127== ```c= #include <stddef.h> bool is_ascii(const char str[], size_t size) { if (size == 0) return false; for (int i = 0; i < size; i++) if (str[i] & 0x80) /* i.e. (unsigned) str[i] >= 128 */ return false; return true; } ``` - 參數 1. `const char str[]`:第一個參數為==開頭的記憶體地址==,前面加上 `const` 表示該參數不可再變動。 > 我的問題: > str[ ] 長怎樣?他是一個儲存 char 的 array。 > 所以 str[ ] = {"c" , " " , "*", "w" } > str[0] = "c", str[1] = " " ...... 而一個 `char` 大小 = 8 bits 。 :::danger 翻閱 C99 規格書,理解上述 array object, string literal 和有效的生命週期等議題,不要臆測 :notes: jserv ::: ::: warning TODO: study C99 (°ཀ°) ! ::: 2. `size_t size` :記憶體範圍 - 程式 1. `str[i]` 所儲存的會是一個 `char` 的字,又每個字可以轉成一個對應的數字,故可以用來和`0x80`做運算。 2. `0x80` 的含義? `0x80` 是(`80`)~16~ 指 (`1000 0000`)~2~,所以將`str[i]` 與 (`1000 0000`)~2~ 做 `AND` 的計算。所以要 `str[i]` **的二進制**的最左 bit為 1 才會 `if` 成立。 又(`1000 0000`)~2~ = (128)~10~。 都轉為十進制來看,可理解成 `(unsigned) str[i] >= 128`。 > 假設比對 "A" 是否屬於 ASCII,則我們要先知道 "A" 轉為數字對應到 (`0x41`)~16~ = (`1000001`)~2~。 再來做 **(`1000 0000`)~2~ AND (`0100 0001`)~2~** > 0x80 : 1000 0000 > 0x41 : 0100 0001 > AND結果:0000 0000 > 所以 return true, "A" 為 ASCII 3. 由於這個版本是一個字元逐一比對,故效率較低,為了改善這個問題,所以有了第二個版本去嘗試一次比對 64 位元的資料。 > bit:位元 > byte:位元組 = 8 bits > word:字組 = 2 bytes = 16 bits ### 版本二:一次比對 64 位元( bits ) ```c=1 #include <stdbool.h> #include <stddef.h> #include <stdint.h> #include <string.h> bool is_ascii(const char str[], size_t size) { if (size == 0) return false; int i = 0; while ((i + 8) <= size) { uint64_t payload; memcpy(&payload, str + i, 8); if (payload & 0x8080808080808080 ) return false; i += 8; } while (i < size) { if (str[i] & 0x80) return false; i++; } return true; } ``` ```c=11 while ((i + 8) <= size) ``` 一次要比較 8 個 char,用 `while` 來控制。 ```c=12 uint64_t payload; memcpy(&payload, str + i, 8); ``` 新增一個`uint64_t` 的變數 `payload`,將 `str` 中的8個 char 複製到 payload 所在的記憶體位址。 所以`payload` 所存的會是複製過來的 char。 ```c=14 if (payload & 0x8080808080808080 ) return false; ``` 將 `payload` 與 `0x8080808080808080` 做 `AND` 運算,為何是`0x8080808080808080`呢?從版本一可以知道一次比對1個字元需要使用一個 `80` ,比對8個字元則要使用 8 個 `80`。 - `uint64_t` 為何? 不只有 `uint64_t` ,亦有`uint32_t`, `uint16_t`, `uint8_t`。後面的 `_t` 代表他們是透過 `typedef` 定義出來的。 [cstdint.hpp](https://github.com/boostorg/config/blob/master/include/boost/cstdint.hpp) ```c typedef unsigned long uint64_t; ``` 所以 `uint64_t` 的範圍:0 ~ $2 ^ {64}$ - 1 (0x0000 0000 0000 0000~0xFFFF FFFF FFFF FFFF)~16~,轉乘二進制後會有 64 個 bits 。 #### ==問題一==:為何使用memcpy()? - 先理解 `memcpy()` 的功能 The memcpy() function **copies** n bytes **from memory area** src to memory area dest. **The memory areas should not overlap**. Use memmove(3) if the memory areas do overlap. ```c #include <string.h> void *memcpy(void *str1, const void *str2, size_t n) ``` `void *str1`:destination `const void *str2`:source `size_t n`:要複製的字元數 範例: ```c= #include <stdio.h> #include <string.h> //要使用 memcpy() int main () { const char src[50] = "http://www.gitbook.net/html"; char dest[50]; printf("Before memcpy dest = %s", dest); //"+1"是要複製'/0' memcpy(dest, src, strlen(src)+1); printf("After memcpy dest = %s", dest); return(0); } ``` ```c Before memcpy dest = After memcpy dest = http://www.gitbook.net/html ``` - 再了解**data alignment** - 定義:data 的 address 可以公平的被 1, 2, 4, 8,這些數字整除。 - `memcpy()` 與 aligement 的關係: `memcpy()` 在複製資料前會先檢查是否 **word aligned** ,有利於 Single Instruction Multiple Data (SIMD, 一個指令可同時處理多筆資料)。但在 `memcpy()` 使用上必須注意在不同處理器上未必皆可正確執行,例如: ARM 。 - 參考: 1. [PCMan](https://www.facebook.com/pcman.im/posts/1623916640981340/) 2. [ARMCC: problems with memcpy (alignment exceptions)](https://stackoverflow.com/questions/24883410/armcc-problems-with-memcpy-alignment-exceptions) 3. [我是軟體那些處理器教我的事](https://coscup.org/2008/files/trap-in-processors.pdf) #### ==問題二==:將上述技巧應用於「給予一個已知長度的字串,檢測裡頭是否包含有效的英文大小寫字母」 [github_quiz2_test1](https://github.com/ptzling310/quiz2/blob/master/test1.c) ```c= #define PACKED_BYTE(b) (((uint64_t)(b) & (0xff)) * 0x0101010101010101u) bool is_alpha(const char str[],size_t size) { if(size==0) return false; int i = 0; while( i+8 <=size){ uint64_t payload; memcpy(&payload, str + i, 8); uint64_t A = payload + PACKED_BYTE(128 - 'A' + 0 ); uint64_t Z = payload + PACKED_BYTE(128 - 'Z' + (-1)); uint64_t a = payload + PACKED_BYTE(128 - 'a' + 0); uint64_t z = payload + PACKED_BYTE(128 - 'z' + (-1)); uint64_t mask_up = ((A ^ Z) & PACKED_BYTE(0x80)); uint64_t mask_low = ((a ^ z) & PACKED_BYTE(0x80)); if(mask_up == PACKED_BYTE(0x00) && mask_low== PACKED_BYTE(0x00)) return false; i+=8; } while (i < size) { if ( (str[i] < 65 || (str[i] > 90 && str[i] < 97) || str[i] > 122)) return false; i++; } return true; } ``` ``` a [] = {testING} #will return true b[]= {"@@deF"}; #will return false ``` 利用 測驗 `5` 判斷字母是否屬於 '`A`'~'`Z`' 之間來完成此題。 若該 char 為大寫英文,則 `mask_up` 會為 `0x80`、 `mask_low` 會為 `0x00`。 若該 char 為小寫英文,則 `mask_low` 會為 `0x80`、 `mask_up` 會為 `0x00`。 若該 char 非英文字母,則 `mask_low` == `mask_up` == `0x00`。 #### ==問題三==:承 (2),考慮常見的英文標點符號,判斷輸入字串是否為有效的字元集,避免逐一字元比對 --------------------------- ## 測驗 `2`: 十六進制轉十進制 ```c= uint8_t hexchar2val(uint8_t in) { const uint8_t letter = in & 0x40; //挑出0~9 const uint8_t shift = (letter >> 3) | (letter >> 6); return (in + shift) & 0xf; } ``` `0xf` = (0000 1111)~2~ ,再取 `&` 表示保留最後 4 個 bits。 ::: spoiler ASCII 表 | input char|ASCII - 10進制 |ASCII - 16進制 | b~7~ |b~6~ |b~5~ |b~4~ |b~3~ |b~2~ |b~1~ |b~0~ | output | |--------| -------- | -------- |-------- |-------- |-------- |-------- |-------- |-------- | -------- |-------- |-------- | |'`0`' | 48 | 0x30 |0 |0|1|1|0|0|0|0|0 |'`1`' | 49 | 0x31 |0 |0|1|1|0|0|0|1|1 |'`2`' | 50 | 0x32 |0 |0|1|1|0|0|1|0|2 |'`3`' | 51 | 0x33 |0 |0|1|1|0|0|1|1|3 |'`9`' | 57 | 0x3A |0 |0|1|1|1|0|0|1|4 |... | ... | ... |... |...|...|...|...|...|...|...| |'`A`' | 65 | 0x41 |0 |1|0|0|0|0|0|1|10 |'`B`' | 66 | 0x42 |0 |1|0|0|0|0|1|0|11 |'`F`' | 70 | 0x46 |0 |1|0|0|0|1|1|0|15 |... | ... | ... |... |...|...|...|...|...|...|...| |'`a`' | 97 | 0x61 |0 |1|1|0|0|0|0|1|10 |'`b`' | 98 | 0x62 |0 |1|1|0|0|0|1|0|11 |'`f`' | 102 | 0x66 |0 |1|1|0|0|1|1|0|15 ::: - 觀察 '`0`' ~ '`9`' - 值就是最左四個 bits - 和大寫英文、小寫英文的區別在於 b~6~ 這個bit - 觀察 '`A`' ~ '`F`' 以及 '`a`' ~ '`f`' - 相同字母之 b~3~ b~2~ b~1~ b~0~ 相同 - 大小寫字母差別在 b~5~ - 再觀察最後 4 個 bits (b~3~ b~2~ b~1~ b~0~),會發現最後與真正要 return 的值差 9!所以如果是屬於英文字母,則將最後 4 個 bits 取出再加上 9 = (1001)~2~。 | char | b~3~ b~2~ b~1~ b~0~ | b~3~ b~2~ b~1~ b~0~之十進制值 | 想轉換成的值 | 差值| | -------- | -------- | -------- |-------- |-------- | | A or a | 0001 | 1 |10 |9| | B or b | 0010 | 2 |11 |9| | ... | ... | ... |... |...| | F or f | 0110 | 6 |15 |9| ``` in = (0011 0001) = 1 letter = in & 0x40 = (0011 0001) & (0100 0000) = (0000 0000) 由 0x40 可以過濾出 b6 這個 bit,如果是 0 代表是數字,是 1 代表是英文字母。 shift = ( letter>>3 ) | (letter >> 6) = (0000 0000) | (0000 0000) = (0000 0000) in + shift = (0011 0001) + (0000 0000) = (0011 0001) (in + shift) & 0xf = (0011 0001) & (0000 1111) = (0000 0001) = 1 ``` - 流程 1. 先區分數字以及英文字母。 2. 是英文字母的,要再加上 9 = (1001)~2~ 。 3. 保留最後四個 bit ,其值即為所求。 #### ==問題一==:解釋上述程式的運作原理 ```c typedef unsigned char uint8_t; ``` 所以 `uint8_t` 是一個 `8 bits` 的 `char`。 ```c=3 const uint8_t letter = in & 0x40; ``` 利用 `0x40` = (0100 0000)~2~ 抓出 b~6~ ```c=4 const uint8_t shift = (letter >> 3) | (letter >> 6); ``` `shift` 用來做到加上 9 = (1001)~2~ 這件事,如果 letter 非 0 ,就可以利用 letter 位移組合出 (0000 1001)~2~ ; 若 letter 則此值會為 (0000 0000)~2~ 。 ```c=5 return (in + shift) & 0xf; ``` `in + shift` 就是在補 9 ,因為只保留最後 4 個 bits ,所以再和 `0xf` = (0000 1111)~2~ 取 `&` 運算,則前面的 4 個 bits 一定會被清成 (0000)~2~。 #### ==問題二==:將 hexchar2val 擴充為允許 "0xFF", "0xCAFEBABE", "0x8BADF00D" 等字串輸入,且輸出對應的十進位數值 [Github_quiz2_test2](https://github.com/ptzling310/quiz2/blob/master/test2.c) i 從 2 開始是為了要跳過 "0x" 的部分。 這裡是一個 byte 轉換,因為 1 byte = 4 bits,所以要 `<<4` 。 ```c= uint64_t hexchar2val(const char str[]) { uint64_t result = 0 ; int i=0; for(int i = 2 ; i < strlen(str) ; i++){ const uint8_t letter = str[i]& 0x40; const uint8_t shift = (letter >> 3 ) | (letter >> 6); result = (result << 4) | ( (str[i]+shift)&0xf); } return result; } ``` --------------------------- ## 測驗 `3`: 快速除法,回傳 `n mod 3` ```c= const uint32_t D = 3; #define M ((uint64_t)(UINT64_C(0xFFFFFFFFFFFFFFFF) / (D) + 1)) /* compute (n mod d) given precomputed M */ uint32_t quickmod(uint32_t n) { uint64_t quotient = ((__uint128_t) M * n) >> 64; return n - quotient * D; } ``` :::info 為何要 __uint128_t 因為在 intel 處理器上,會使用到 128 位元。 ::: `uint32_t D = 3`,D 是一個 32 bits 的 unsigned integer,給定為3。 `uint64_t M`,M 是一個 64 bits 的 unsigned integer。 `uint32_t n` ,傳入參數是一個 32 bits 的 unsigned integer,為 n 。 `uint64_t quotient`,商數是 64 buts 的 unsigned integer。 - UINTN_C(value) 把無符號整型值 value 擴展以適應數據類型 uint_leastN_t。 #### ==問題一==:解釋上述程式的運作原理 - 先以答題的方向來來看: 從 `return n - quotient * D` 推, quotient 即為 $\dfrac{n}{d}$ 的整數部分 $= n \times \dfrac{ \dfrac{2^N}{d} }{2^N}$,再依照程式中 `quotient` 的算法,$\dfrac{n}{d} = n \times \dfrac{ \dfrac{2^N}{d} }{2^N} = M \times n \times \dfrac{1}{2^{64}}$。 對應來看: $N = 64$ 以及 $M=\dfrac{2^{64}}{d}$。 回到在 line 2 我們如何定義 M: `#define M ((uint64_t)(UINT64_C(XXX) / (D) + 1))`也就是$M =\dfrac{xxx}{d}+1 = \dfrac{xxx+d}{d}$,所以$xxx+1 ≈2^{64}$,最終我們會選擇`0xFFFFFFFFFFFFFFFF`。 - 再來從數學的角度來看: :::warning TODO 數學部分 ::: #### ==問題二==:閱讀[div.h](https://github.com/jemalloc/jemalloc/blob/dev/include/jemalloc/internal/div.h) 及 [div.c](https://github.com/jemalloc/jemalloc/blob/dev/src/div.c) 並且抽離快速除法部分為獨立程式,比較透過處理器的整數除法指令及本技巧的效能差距以及理解。 假設:$n=q\times d$ (可整除) \begin{split} \lfloor \lceil \dfrac{2^k}{d} \rceil \times \dfrac{n}{2^k} \rfloor &= \lfloor \dfrac{2^k+r}{d} \times \dfrac{n}{2^k} \rfloor …(1)\\ &= \lfloor \dfrac{2^k}{d} \times \dfrac{n}{2^k} + \dfrac{r}{d} \times \dfrac{n}{2^k} \rfloor …(2)\\ &= \lfloor \dfrac{n}{d} + \dfrac{r}{d} \times \dfrac{n}{2^k} \rfloor …(3)\\ &= \dfrac{n}{d} + \lfloor \dfrac{r}{d} \times \dfrac{n}{2^k} \rfloor …(4) \end{split} $(1)$ $r = {(-2)^k} \quad mod \quad d$ $(2)$ 將 $\dfrac{n}{2^k}$ 乘入 $\dfrac{2^k+r}{d}$ $(3)$ 提 $\dfrac{n}{d}$ $(4)$ 因為 $\dfrac{n}{d}$ 為整數,小數部分為 $0$ 當 $r < d$ ,則 $\dfrac{r}{d} < 1$,再假設 $\dfrac{n}{2^k}< 1$ 則$\dfrac{r}{d}\times\dfrac{n}{2^k}<1$,最後 $\dfrac{n}{d} + \lfloor \dfrac{r}{d} \times \dfrac{n}{2^k} \rfloor = \dfrac{n}{d}$ 如何使 $\dfrac{n}{2^k}< 1$ 成立呢,這邊做法是只要 $k$ 夠大即可,所以取 $k=32$ 來只這個條件成立。 --------------------------- ## 測驗 `4`: 判斷某個數值能否被指定的除數所整除 ```c= bool divisible(uint32_t n) { return n * M <= M - 1; } ``` 已知: 1. `D = 3` 2. `M = 0x5555555555555556` n 可分類為三種可能: n % 3 = 0; n % 3 = 1 ; n % 3 = 2。 可以記錄為 n = {3k , 3k+1 , 3k+2 } ,k 為 $\ge0$ 之正整數。 ##### case 1: n = 3k `n * M` = 3k * M = 3k * (0x5...5 + 1) = k * ( 0xf...f + 3) = 2k ##### case 2: n = 3k + 1 `n * M` = (3k+1) * M = 3km + M = 2k + M ##### case 3: n = 3k + 2 `n * M` = (3k+2) * M = 3km + 2M = 2k + 2M 觀察 3 個 case ,會發現只有 case 1 中 沒有 `M` , 所以 n * M < M 成立,則可被整除。又因為程式碼內使用的是 `<=` 所以才選擇 `M-1`。 --------------------------- ## 測驗 `5`: 將字串的的英文字母全改為小寫 [ASCII Code - The extended ASCII table](https://www.ascii-code.com/) ### 版本一:一次比較一字元 ```c= #include <ctype.h> #include <stddef.h> /* in-place implementation for converting all characters into lowercase. */ void strlower(char *s, size_t n) { for (size_t j = 0; j < n; j++) { if (s[j] >= 'A' && s[j] <= 'Z') s[j] ^= 1 << 5; else if ((unsigned) s[j] >= '\x7f') /* extended ASCII */ s[j] = tolower(s[j]); } } ``` - tolower():將字串轉乘小寫 ```c #include <ctype.h> int tolower(int c); ``` ```c=7 if (s[j] >= 'A' && s[j] <= 'Z') s[j] ^= 1 << 5; ``` 此段處理 `'A'` ~ `'Z'` 之間的字母,將其移到 `'a'` ~ `'z'` 的部分就好。 我們回頭看 測驗`2` 中的 ASCII 表: | input char|ASCII - 10進制 |ASCII - 16進制 | b~7~ |b~6~ |b~5~ |b~4~ |b~3~ |b~2~ |b~1~ |b~0~ | |--------| -------- | -------- |-------- |-------- |-------- |-------- |-------- |-------- | -------- |-------- | |'`A`' | 65 | 0x41 |0 |1|0|0|0|0|0|1| |'`a`' | 97 | 0x61 |0 |1|1|0|0|0|0|1| 所以 `'A'` → `'a'` 只要在 b~5~ 補上 bit `1`。 ```c=9 else if ((unsigned) s[j] >= '\x7f') /* extended ASCII */ s[j] = tolower(s[j]); ``` `'\x7f'` 是對應到 ASCII 中的 127 這個,所以就是 >127 (超出 ASCII 編碼範圍),則使用 `tolower()` 來轉為小寫。 ### 版本二:一次比對 64 位元( bits ) ```c= #include <ctype.h> #include <stddef.h> #include <stdint.h> #define PACKED_BYTE(b) (((uint64_t)(b) & (0xff)) * 0x0101010101010101u) /* vectorized implementation for in-place strlower */ void strlower_vector(char *s, size_t n) { size_t k = n / 8; for (size_t i = 0; i < k; i++, s += 8) { uint64_t *chunk = (uint64_t *) s; if ((*chunk & PACKED_BYTE(0x80)) == 0) { /* is ASCII? */ uint64_t A = *chunk + PACKED_BYTE(128 - 'A' + 0 ); uint64_t Z = *chunk + PACKED_BYTE(128 - 'Z' + (-1)); uint64_t mask = ((A ^ Z) & PACKED_BYTE(0x80)) >> 2; *chunk ^= mask; } else strlower(s, 8); } k = n % 8; if (k) strlower(s, k); } ``` - `PACKED_BYTE(b)` 為何? `(b) & (0xff)` 是 b 與 (0...0 1111 1111)~2~ 做 `AND` 運算,也就是只保留最後 8 個 bits。 在呈上 `0x0101010101010101u` 就會把剛剛所算之結果複製 8 次。 > b = (1111...1111 1010 1111) > b and (0xff) = (0000...0000 1010 1111) > ( b and (0xff) ) * (0x0101010101010101u) = (1010 1111 1010 1111 .... 1010 1111) #### ==問題一==:解釋上述程式的原理 ```c=10 size_t k = n / 8; ``` 用 k 來紀錄要比較字串的次數,因為每次都 8 bytes 比,所以 `/8`。 ```c=11 for (size_t i = 0; i < k; i++, s += 8) ``` 只要我還沒做到所需之比較次數,就會執行 for 迴圈以內的事。 ```c=13 if ((*chunk & PACKED_BYTE(VV1)) == 0) { /* is ASCII? */ ``` 去觀察 ASCII 表,我們將所有的值都轉成二進制來看,那麼從共同特點就是,b~7~ (最左 bit) 皆為 `0`。 所以這行要用來篩選是不是 ASCII 就是用 (`1000 0000`)~2~ = `0x80` 來將 b~7~ 單獨抓出來看, 如果 b~7~ 是 1 ,則必不屬 ASCII 。 同版本一,我們也分成為`'A'`~`'Z'` 的部分以及其他。 ```c=16 uint64_t mask = ((A ^ Z) & PACKED_BYTE(VV4)) >> 2; *chunk ^= mask; ``` 先做 `%` 後 `>>`。 回推: 1. Line 17 是為了轉換為小寫,所以要將 b~6~ 設為 1 ,所以 mask 應為 (`0010 0000 ... 0010 0000`)~2~。 2. `(0010 0000 ... 0010 0000)`~2~ 為 line 16 之結果,先將位移還原(也就是做 `<<2`)得: `(A ^ Z) & PACKED_BYTE(VV4))` = `(1000 0000 ... 1000 0000)`~2~。 3. 為了達到上述結果,`PACKED_BYTE(VV4)` 應是用來修正 `(A ^ Z)` 的結果, 所以 `PACKED_BYTE(VV4)` 應為 (`1000 0000 ... 1000 0000`)~2~ = `0x80...80`。 VV4 為 `0x80` 。 4. `(A ^ Z)` 之結果也必為(`1xxx xxxx .... 1xxx xxxx`)~2~(否則`&PACKED_BYTE(0x80)`後,b~7~ 無法為 1 ),為了使 `(A ^ Z)` 的b~7~ 為 1,`A` 與 `Z` 的 b~7~ 必為相反,如此做完 `XOR` 後, b~7~ 才會為 1。 ```c=14 uint64_t A = *chunk + PACKED_BYTE(128 - 'A' + VV2); uint64_t Z = *chunk + PACKED_BYTE(128 - 'Z' + VV3); ``` 假設一個 char 他是落在 '`A`'~'`Z`' 之間,則該 char - '`A`' + 128 + VV2 應該 `>=128` ; 以及該 char - '`Z`' + 128 + VV3 應該 `< 128` :::warning 為何 char - ‘Z’ + 128 + VV3 不能 <= 128 呢? 假設今天將 char 取 'Z' 好了,那麼: char - '`A`' + 128 = 95-65+128 = 158 >= 128 則 A 會為 (1001 1110)~2~ char - '`Z`' + 128 = 128 則 Z 會為 ( 1000 0000 )~2~ 再將 `(A^Z)` = (0001 1110 )~2~ ,那麼就不為第 4 點所要求的(1xxx xxxx)~2~ ,所以我必須要確認 Z 值不能為 128 ::: 根據上述 VV2 = 0 ; VV3 = (-1) 。 #### ==問題二==:main 函式的 char str[] 更換為 char *str,會發生什麼事? >Segmentation fault (core dumped) `char str[]` 會回傳陣列;而`char *str` 是一個指標。 >EXAMPLE 8 The declaration char s[] = "abc", t[3] = "abc"; defines ‘‘plain’’ char array objects s and t whose elements are initialized with character string literals. This declaration is identical to char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' }; The contents of the arrays are modifiable. On the other hand, the declaration char *p = "abc"; defines p with type ‘‘pointer to char’’ and initializes it to point to an object with type ‘‘array of char’’ with length 4 whose elements are initialized with a character string literal. If an attempt is made to use p to ==modify the contents of the array, the behavior is undefined.== --------------------------- ## 測驗 `6`: LeetCode 137. Single Number II: 找出只出現 1 次的 element ```c= int singleNumber(int *nums, int numsSize) { int lower = 0, higher = 0; for (int i = 0; i < numsSize; i++) { lower ^= nums[i]; lower &= ~higher; higher ^= nums[i]; higher &= ~lower; } return lower; } ``` - 複習一下 `XOR` 的概念 ``` 1. 和自己 XOR 是 0 a ^ a = 0 2. 和 0 XOR 是 自己 a ^ 0 = 0 3. 交換律 a ^ b = b ^ a 4. 結合律 (a ^ b) ^ c = a ^ (b ^ c) = a ^ (c ^ b) = a ^ c ^ b ``` - LeetCode 136. Single Number Every element appears **two** times except for one. ```c= int singleNumber(int *nums, int numsSize) { int count = 0; for(int i=0;i<numsSize;i++){ count ^= nums[i]; } return count; } ``` 解釋 ``` 假設一個 int array = [1 , 2 , 3 , 4 , 1 ,2 ,3] 則 count 即為: 1 ^ 2 ^ 3 ^ 4 ^ 1 ^ 2 ^ 3 再依照交換律 1 ^ 1 ^ 2 ^ 2 ^ 3 ^ 3 ^ 4 再依照結合律 (1 ^ 1) ^ (2 ^ 2) ^ (3 ^ 3) ^ 4 又因為 a ^ a = 0 ,故 0 ^ 0 ^ 0 ^ 4 = 4 所以 count = 4,代表 4 因為沒執行兩次的 XOR 所以沒有被消除。 ``` #### ==問題一==:解釋上述程式的原理 - 回到 LeetCode 137. Single Number Every element appears **three** times except for one. 承 LeetCode 136. Single Number 概念,想要創造一組計算,使得 a ? a ? a = 0 ,這樣子未做到 3 次的 number 會留下。欲產生 `00 → 01 → 10 → 00` 的這個運算。 ```graphviz digraph graphname{ rankdir="LR"; 00 [label="00",shape = circle] 01 [label="01",shape = circle] 10 [label="10",shape = circle] 10 [label="10",shape = circle] Init[label="Initial",shape=plaintext] 00->01[label="1"] 00->00[label="0"] 01->10[label="1"] 01->01[label="0"] 10->00[label="1"] 10->10[label="0"] Init->00 } ``` 為了變更這兩個值,我們利用 heigher 以及 low 設定。 當某數第一次出現時,會被加在 lower 中,若出現第二次時,lower 中會扣除,並且加在 heigher 中。當第三次出現時,就會從 heigher 中刪除。 當 b~n~ 出現一次時, b~n~ 在 lower 中會設為 1 ; 當 b~n~ 出現第二次時, b~n~ 在 lower 會設為 0 且在 higher 中設為 1 ; 當 b~n~ 出現第三次時, b~n~ 在 higher 會設為 0。 #### ==問題二==:請撰寫不同上述的程式碼 #### ==問題三==:將重複 3 次改為改為重複 4 次或 5 次,並找出可容易推廣到多次的程式碼 ## 時間表 - [x] 2020/9/21 測驗 `1` 程式理解 - [x] 2020/9/22 測驗 `2` 程式理解 - [x] 2020/9/23 測驗 `3` 程式理解、影片 - [x] 2020/9/24 測驗 `5` 程式理解 - [x] 2020/9/26 測驗 `6` 、`4` 程式理解、影片 - [x] 2020/9/27 測驗 `1` 延伸問題二 、 測驗 `2` 延伸問題 (到作業截止時間) - [ ] 2020/9 ###### tags: `sysprog2020`