# 2020q3 Homework4(quiz4)
contributed by < `Uduru0522` >
###### tags: `perspective and application of computer systems 2020`
----
# 測驗一:計算 Hamming Distance
:::success
```c=
int hammingDistance(int x, int y) {
return __builtin_popcount(x ^ y);
}
```
:::
## 程式說明
Hamming Distance 可以透過計算兩個 string 的相異處數量而得。
由於題目之 x 及 y 長度相同,且 $L = \{0, 1\}$,
於是我們可以利用 XOR 逕直計算滿足 $X[I] \neq Y[I]$ 的 $I$ 的數量。
## 延伸問題
### 不使用 gcc extension 的 C99 實做
```c=
int hammingDistance(int x, int y){
uint32_t mask = 0x01, temp = x ^ y;
int h_distance = 0;
for(int i = 0;i < 32;++i){
h_distance += (mask & temp);
temp >>= 1;
}
return
}
```
計算 `x ^ y` 之後再透過 `mask` 以及右移得出 Set Bit, 即為兩數的 Hamming Distance。
### LeetCode 477. Total Hamming Distance
:::success
Input 為一長度為 `numsSize` 的 int array,
求陣列中所有 pair 的 Hamming Distance 總和。
:::
#### 程式碼
```c=
int totalHammingDistance(int* nums, int numsSize){
char mask = 0x01;
int count_ones, total_distance = 0;
for(int i = 0; i < 32; ++i){
count_ones = 0;
for(int j = 0; j < numsSize; ++j){
count_ones += (nums[j] >> i & mask);
}
total_distance += (count_ones * (numsSize - count_ones));
}
return total_distance;
}
```
#### 說明
測驗一之中我們可以看出來,計算 Hamming Distance 時,可以每個位元分開計算。
因此,可以將問題簡化為 **對 numsSize 個 bit 來說,Hamming Distance 為何?**,
並進行 32 次 (int 的長度) 這個運算。
接著,**由於 Hamming Distance 即是算 (0, 1) 及 (1, 0) 的組數**,
當每個 input 只有一個位元時便很好計算:
$\text{num of 1's} \times \text{num of 0's}$
因此,我們只要對 array 中每個數的每個 bit 都掃過一次即可計算出 Hamming Distance 的總和。
時間複雜度為 $O(n)$。
![](https://i.imgur.com/mR8YM3l.png)
### 錯誤更正碼簡介及抽象代數的實務應用
**TODO**
### Linux 核心中的 Reed–Solomon Error Correction
----
# 測驗二:計算 Tree 之中 Node 的第 K 個父節點
> [LeetCode 1483. Kth Ancestor of a Tree Node](https://leetcode.com/problems/kth-ancestor-of-a-tree-node/)
:::success
```c=
#include <stdlib.h>
typedef struct {
int **parent;
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 + (-2)] == -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 & (1))
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);
}
```
:::
## 程式說明
## 延伸問題
### 記憶體浪費檢測
### Top 25% 實做
----
# 測驗三:FizzBuzz 問題之解答優化
:::success
```c=
#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 >> div5) >> (div3 << 2)], length);
fmt[length] = '\0';
printf(fmt, i);
printf("\n");
}
return 0;
}
```
:::
## 程式說明
### 檢測可被 3 / 5 整除 (無求餘運算)
觀察程式碼,
可以注意到**以下的不等式用來得知一 32-bit 整數是否能被 d($d\in\{3,5\}$) 整除**:
> $\text{Assume } S_{i, 64}=\text{0xiiiiiiiiiiiiiiii}, i\in{[0_{16},F_{16}]}$
$n\times (S_{F,64}\div 3+1)\leq S_{F,64}\div 3$
經過化簡,可得
$n\times S_{5,64}+n\leq S_{5,64}$
接著,考慮三個不同 n 的值:整除、除三餘一、除三於二:
| $n\equiv$ | 0 (mod 3) | 1 (mod 3) | 2 (mod 3) |
| :-------: | :-------: | :-------: | :-------: |
| LHS ($n\times S_{F,64}+n$) | $\xrightarrow[]{\text{overflow}} n$ | $\xrightarrow[]{\text{overflow}}S_{5, 64}+n$ | $\xrightarrow[]{\text{overflow}}S_{A,64}+n$ |
利用 overflow 來取代除法運算中重複減去的過程,節省時間,
其中只有 $n\equiv 0(\text{mod 3})$ 時, 等式才成立。
:::info
此等式可以應用於檢測所有 $S_{F,64}$ 之因數 $M$ 且 $S_{F,64}\div M > L, L=$ **檢測範圍 bit 長度** 的
整數的因數上,舉例來說,
若要檢測是否可被 15 整除,M15 即可設成 $S_{1,64}+1$。
:::
<span style="color:red">**優點:$M_k$ 可提前計算,大幅減少除法運算的次數(僅一次)**</span>
### 字串輸出優化
程式中字串輸出以『同個字串,不同起點和終點』來輸出,如以下表格:
| **字串內容** | F | i | z | z | B | u | z | z | %u | \0 |
| :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: |
| **起始點 (included)** | $0(mod 3)$ $0(mod 15)$| | | | $0 (mod 5)$ | | | | Others | |
| **終點 (excluded)** | | | | | $0(mod 3)$ | | | | $0 (mod 5)$ $0(mod 15)$ | Others |
#### 目標
由 `div5` 以及 `div3` 兩個值計算出**起點**以及**字串長**,`div5` $,$`div3` $\in\{0,1\}$
將上方表格轉換成數值:
| `div3` | `div5` | **起點** | `length` |
| :------: | :------: | :------: | :------: |
| 0 | 0 | 8 | $\geq$ 1 |
| 0 | 1 | 4 | 4 |
| 1 | 0 | 0 | 4 |
| 1 | 1 | 0 | 8 |
:::info
**起點方程式**
透過觀察,可以發現滿足以下方條件的方程式可以為解:
0. $\text{start_point}(0,0)=8$
1. **只要 `!div3` 便為0**
2. `div5` 會造成除以 2 的效果
可得 `start_point(div3, div5) = !div3 & (8 >> div5)`。(我的算法)
與題目中解答的相異之處為條件 ㄅ 的處理,題目使用的方式為
『若 `div3`,向右移 4-bit』,
無論運算值為何 (8 或 4) 皆會被消除。
#### `length` 方程式
透過觀察,可以發現滿足以下方條件的方程式可以為解:
0. $\text{length}(0,0)\geq 1$
1. `div3` 和 `div5` 任一為 1,結果為 4
2. `div3` 和 `div5` 同時成立會造成額外乘以 2 的效果
可得 `length = 2 << div3 << div5`。
:::
## 延伸問題
### 更換 bitmask 減少指令維持 branchless
----
# 測驗四:
:::success
考慮到整數 `PAGE_QUEUES` 可能有以下數值:
+ (1 or 2 or 3 or 4)
+ (5 or 6 or 7 or 8) $\times$ (2n), n from 1 to 14
給定 size_t offset,計算 `blockidx = offset / PAGE_QUEUES`
<br>
```c=
#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) {
case 3: blockidx /= 3;
break;
case 5: blockidx /= 5;
break;
case 7: blockidx /= 7;
break;
}
```
:::
## 程式說明
### `ffs32(n)`
> 根據 Gcc Online Documentation, 6.59 Other Built-in Functions Provided by GCC:
> > **`int __builtin_fss(int x)`**
> > Returns one plus the index of the least significant 1-bit of x, or if x is zero, returns zero.
此 Macro 與 `__builtin__ctz(x)` 有類似作用(計算末尾 0 的數量),然而後者對 `x == 0` 時之應對為 Undefined.
### 消除公因數 $2^n$ 及計算
將 `offset` 以及 `divient` 皆右移 `ffs32(divient)`,消除末尾所有的 0,也就是公因數 $2^n$。
觀察 `PAGE_QUEUES` 範圍,看得出可以以 $2^n\times3^a\times5^b\times5^c$ 來表示,因此 switch block 僅需處理 3、5、7 三個質因數。
## 延伸問題
### LeetCode 51. N-Queens ,使用 `__builtin_ffs()`
TODO