2020q3 Homework4 (quiz4)
===
contribute by `Liao712148`
* 1.從新回答[第4周測驗題](https://hackmd.io/@sysprog/2020-quiz4)
###### tags: `進階電腦系統理論與實作`
## 測驗1
```cpp=
int hammingDistance(int x, int y) {
return __builtin_popcount(x OP y);
}
```
此題要找出2個數轉成2進位後有幾個不一樣的bit,透過
`XOR`的特性:
* 具有交換性
`A^B==B^A`
* 與0作XOR會得到自己
`A^0==A`
* 與自己作XOR會得到0
`A^A==0`
所以我們可以利用`XOR`將2個數不同的部分找出來,再利用`__builtin_popcount()`,將不一樣的總數計算出來。
所以`OP`選擇`^`
### 延伸問題
* 不使用`gcc extension`的`C99`實作程式碼
```cpp=
int hammingDistance(int x, int y) {
int temp=x^y;
int sum=0;
while(temp){
if(temp&0x1) sum++;
temp>>=1;
}
return sum;
}
```
我用了不斷向左位移,並與`0x1`作用來計算不相同的次數,藉此取代`__builtin_popcount()`
* 練習LeerCode[477. Total Hamming Distance](https://leetcode.com/problems/total-hamming-distance/)
此題如果用值觀的方式的話,
```cpp=
int totalHammingDistance(int* nums, int numsSize){
int sum=0;
for(int i=0;i<numsSize-1;i++){
for(int j=1;(i+j)<numsSize;j++){
int32_t temp=(*(nums+i))^(*(nums+i+j));
sum+=__builtin_popcount(temp);
}
}
return sum;
}
```
運算複雜度是$\ O(n^2)$,計算時間會過長。
所以改成
```cpp=
int totalHammingDistance(int* nums, int numsSize) {
int ret=0;
int temp=0,temp2=0;
int max=0;
for(int i=0;i<32;i++){
temp=0;
temp2=0;
for(int j=1;j<numsSize;j+=2){
temp=(((nums[j-1]>>i)&0x01)+((nums[j]>>i)&0x01));
temp2+=temp;
if(nums[j]>=max){max=nums[j];};
}
if(numsSize&0x01){temp2=temp2+((nums[numsSize-1]>>i)&0x01);}
ret=ret+temp2*(numsSize-temp2);
if(!(max>>i)){break;};
}
return ret;
}
```
`6~9`行走訪,陣列的每個數值,計入其二進位的least significiant bit是不是1,這樣子我們就可以知道,單就這個位子,此陣列中有幾個是不一樣的。
舉例來說`2=0010`,`7=0111`,`1=0001`,如果執行`6~9`行可以得到`temp=2`,ret=$2\times{(3-2)}$,`temp`代表出現幾個1,`ret`代表單就這個位子,此陣列中有幾個是不一樣的。
* 研讀[錯誤更正碼簡](https://w3.math.sinica.edu.tw/math_media/d184/18404.pdf)及[抽象代數的實務應用](https://www.math.sinica.edu.tw/www/file_upload/summer/crypt2017/data/2017/%E4%B8%8A%E8%AA%B2%E8%AC%9B%E7%BE%A9/[20170710][%E6%8A%BD%E8%B1%A1%E4%BB%A3%E6%95%B8%E7%9A%84%E5%AF%A6%E5%8B%99%E6%87%89%E7%94%A8].pdf)摘錄其中 Hamming Distance 和 Hamming Weight 的描述並重新闡述,舉出實際的 C 程式碼來解說;
* 研讀[Reed–Solomon error correction](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction),再閱讀 Linux 核心原始程式碼的[lib/reed_solomon 目錄](https://github.com/torvalds/linux/tree/master/lib/reed_solomon),抽離後者程式碼為單獨可執行的程式,作為 ECC 的示範。
---
## 測驗2
```cpp=
#include <stdlib.h>
typedef struct {
AAA;
int max_level;
} TreeAncestor;
TreeAncestor *treeAncestorCreate(int n, int *parent, int parentSize)
{
TreeAncestor *obj = malloc(sizeof(TreeAncestor));
obj->parent = malloc(n * sizeof(int *));
int max_level = 32 - __builtin_clz(n) + 1;
for (int i = 0; i < n; i++) {
obj->parent[i] = malloc(max_level * sizeof(int));
for (int j = 0; j < max_level; j++)
obj->parent[i][j] = -1;
}
for (int i = 0; i < parentSize; i++)
obj->parent[i][0] = parent[i];
for (int j = 1;; j++) {
int quit = 1;
for (int i = 0; i < parentSize; i++) {
obj->parent[i][j] = obj->parent[i][j + BBB] == -1
? -1
: obj->parent[obj->parent[i][j - 1]][j - 1];
if (obj->parent[i][j] != -1) quit = 0;
}
if (quit) break;
}
obj->max_level = max_level - 1;
return obj;
}
int treeAncestorGetKthAncestor(TreeAncestor *obj, int node, int k)
{
int max_level = obj->max_level;
for (int i = 0; i < max_level && node != -1; ++i)
if (k & (CCC))
node = obj->parent[node][i];
return node;
}
void treeAncestorFree(TreeAncestor *obj)
{
for (int i = 0; i < obj->n; i++)
free(obj->parent[i]);
free(obj->parent);
free(obj);
}
```
`AAA`:由第`11`行可以知道`malloc()`會回傳`a pointer to a pointer to int`,所以被指定的對象的型別應該也要是`a pointer to a pointer to int`,所以答案要選`int** parent`。
`BBB`,`CCC`:參考自`RinHizakura`同學
建立一個二維矩陣來存放各個`node`的祖先,`14~17`初始化這個二維矩陣。存放第`1`,`2`,`4`... $\ 2^n$的祖先,如果此`node`的$2^{n-1}$的祖先就已經到根節點了,這樣`node`的$2^{n}$的祖先也會是根結點。如果不是的話`node`的$2^{n-1}$的祖先的$2^{n-1}$的祖先,就是`node`的$2^{n}$的祖先,所以`BBB`選擇`-1`。
假設`K=7`代表要找`node`的第7個祖先,那就是先找((`node`的第4個祖先)的第2個祖先)的第一個祖先。所以`CCC`選擇`i<<1`。
### 延伸問題
* 指出可改進的地方,並實作對應的程式碼
空間再越上層的地方矩陣存放了很多`-1`,造成空間上的浪費。
* 在 LeetCode[1483. Kth Ancestor of a Tree Node](https://leetcode.com/problems/kth-ancestor-of-a-tree-node/)提交你的實作,你應該要在 C 語言項目中,執行時間領先 75% 的提交者
---
## 測驗3
```cpp=
#define MSG_LEN 8
static inline bool is_divisible(uint32_t n, uint64_t M) {
return n * M <= M - 1;
}
static uint64_t M3 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1;
static uint64_t M5 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 5 + 1;
int main(int argc, char *argv[]) {
for (size_t i = 1; i <= 100; i++) {
uint8_t div3 = is_divisible(i, M3);
uint8_t div5 = is_divisible(i, M5);
unsigned int length = (2 << div3) << div5;
char fmt[MSG_LEN + 1];
strncpy(fmt, &"FizzBuzz%u"[(MSG_LEN >> KK1) >> (KK2 << KK3)], length);
fmt[length] = '\0';
printf(fmt, i);
printf("\n");
}
return 0;
}
```
此題用到的`wrap around`的方式來判斷是`3,5`的倍數,透過
```cpp=
static inline bool is_divisible(uint32_t n, uint64_t M) {
return n * M <= M - 1;
}
static uint64_t M3 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1;
static uint64_t M5 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 5 + 1;
```
舉例來說,假設`i=3n,n>0`,代入`is_divisible()`會得到 `n*M3=3n`,會小於`M3-1`,如果`j=3n+1,n>0`,代入`is_divisible()`會得到`n*M3=3n+M3`會大於`M3-1`,以此可以延伸至5的倍數。
透過第`12,13`行確認是多少的倍數後,再透過`14`行算出===要複製的大小=。
倘若是`3`的倍數則要印出`Fizz`,所以要從位子0往後複製4的字元。
倘若是`5`的倍數則要印出`Buzz`,所以要從位子4往後複製4的字元。
倘若是`15`的倍數則要印出`FizzBuzz`,所以要從位子0往後複製8的字元。
倘若是都不是則要印出`該數字`,所以要從位子8往後複製42字元。
所以`KK1`要選`div5`,`KK2`要選`div3`,`KK3`要選`2`
---
## 測驗4
```cpp=
#include <stdint.h>
#define ffs32(x) ((__builtin_ffs(x)) - 1)
size_t blockidx;
size_t divident = PAGE_QUEUES;
blockidx = offset >> ffs32(divident);
divident >>= ffs32(divident);
switch (divident) {
CASES
}
```
由於已知`PAGE_QUEUES`的分布範圍,可以先對除數進行處理,
* (1 or 2 or 3 or 4)
* (5 or 6 or 7 or 8) × (2n), n from 1 to 14
可以求的除數的形式為:
* $1\times2^{n}$,n from=0,1,2,and 4 to 14
* $3\times2^{n}$,n from=0,2,and to 14
* $5\times2^{n}$,n from=1 to 14
* $7\times2^{n}$,n from=1 to 14
再來觀察`5,6`行,可以知道是在執行把除數,被除數中$2^n$的因式預先除掉,再根據`divident`選擇`case`除以除數剩下的因式,所以`CASEA`答案要選擇`3`,`5`,`7`,如果除數剩下的因式是`1`那就不需要再除了。