├── ExistsOrDuplicateProblem ├── IsExistsOrNot │ ├── answer │ │ ├── IsExistsOrNot.go │ │ └── IsExistsOrNot_test.go │ └── lab │ │ ├── IsExistsOrNot_lab.go │ │ ├── IsExistsOrNot_lab_test.go │ │ └── README.md └── SearchUniqueNumbers │ ├── answer │ ├── SearchUniqueNumbers.go │ └── SearchUniqueNumbers_test.go │ └── lab │ ├── README.md │ ├── SearchUniqueNumbers_lab.go │ └── SearchUniqueNumbers_lab_test.go ├── LICENSE ├── MaxValueProblem └── MaxCountIP │ ├── answer │ ├── MaxCountIP.go │ └── MaxCountIP_test.go │ └── lab │ ├── MaxCountIP_lab.go │ ├── MaxCountIP_lab_test.go │ └── README.md ├── NoKProblem └── FindTheKthNumber │ ├── answer │ ├── FindTheKthNumber.go │ └── FindTheKthNumber_test.go │ └── lab │ ├── FindTheKthNumber_lab.go │ ├── FindTheKthNumber_lab_test.go │ └── README.md ├── README.md ├── TopKProblem └── FindTop10Numbers │ ├── answer │ ├── FindTop10Numbers.go │ └── FindTop10Numbers_test.go │ └── lab │ ├── FindTop10Numbers_lab.go │ ├── FindTop10Numbers_lab_test.go │ └── README.md ├── go.mod └── go.sum /ExistsOrDuplicateProblem/IsExistsOrNot/answer/IsExistsOrNot.go: -------------------------------------------------------------------------------- 1 | package IsExistsOrNotAnswer 2 | 3 | import ( 4 | "bufio" 5 | "math/rand" 6 | "os" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | var ( 12 | bm bitmap 13 | srcPath = "SourceFile.txt" 14 | ) 15 | 16 | func GenerateBigFile(Row uint64) { 17 | // First: clear file (if it exists) 18 | // 首先清空文件内容(如果文件存在的话) 19 | err := os.Truncate(srcPath, 0) 20 | if err != nil { 21 | panic(err) 22 | } 23 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 24 | defer f.Close() 25 | if err != nil { 26 | panic(err) 27 | } 28 | // Generate the big file 29 | // 生成大文件 30 | rand.Seed(time.Now().UnixMilli()) 31 | for i := uint64(0); i < Row; i++ { 32 | str := strconv.FormatUint(i, 10) 33 | _, err := f.WriteString(str + "\n") 34 | if err != nil { 35 | panic(err) 36 | } 37 | i += rand.Uint64() % 2 38 | } 39 | } 40 | 41 | type bitmap struct { 42 | bitmap []byte 43 | } 44 | 45 | // grow is used for grow bitmaps' size 46 | // grow 用于扩展 bitmap 的空间 47 | func (b *bitmap) grow(size uint64) { 48 | if size < uint64(cap(b.bitmap)) { 49 | // No need to grow capacity 50 | // 无需扩大容量 51 | return 52 | } 53 | old := b.bitmap 54 | New := make([]byte, size+1) 55 | copy(New, old) 56 | b.bitmap = New 57 | } 58 | 59 | // SetBit set the bitPos bit to 1 60 | // SetBit 将第 bitPos 位设置为 1 61 | func (b *bitmap) SetBit(bitPos uint64) { 62 | bytePos := bitPos / 8 63 | if bytePos < uint64(cap(b.bitmap)) { 64 | b.bitmap[bytePos] |= 1 << ((bitPos) % 8) 65 | } else { 66 | b.grow(bytePos) 67 | b.bitmap[bytePos] |= 1 << ((bitPos) % 8) 68 | } 69 | } 70 | 71 | // GetBit get the value at bitPos (0 or 1) 72 | // GetBit 获取第 bitPos 位的值(0或1) 73 | func (b *bitmap) GetBit(bitPos uint64) int { 74 | bytePos := bitPos / 8 75 | if bytePos >= uint64(cap(b.bitmap)) { 76 | return 0 77 | } else { 78 | bit := b.bitmap[bytePos] & (1 << ((bitPos) % 8)) 79 | if bit != 0 { 80 | return 1 81 | } 82 | return 0 83 | } 84 | } 85 | 86 | // ReadData read the data in big file,and store the data in bitmap 87 | // ReadData 读取大文件中的数据,并将这些数据存储在 bitmap 中 88 | func ReadData() { 89 | f, err := os.Open(srcPath) 90 | if err != nil { 91 | panic(err) 92 | } 93 | scanner := bufio.NewScanner(f) 94 | for scanner.Scan() { 95 | raw := scanner.Text() 96 | data, err := strconv.ParseUint(raw, 10, 64) 97 | if err != nil { 98 | panic(err) 99 | } 100 | bm.SetBit(data) 101 | } 102 | } 103 | 104 | func ExistsQuery(num uint64) bool { 105 | if bm.GetBit(num) == 0 { 106 | return false 107 | } 108 | return true 109 | } 110 | -------------------------------------------------------------------------------- /ExistsOrDuplicateProblem/IsExistsOrNot/answer/IsExistsOrNot_test.go: -------------------------------------------------------------------------------- 1 | package IsExistsOrNotAnswer 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "testing" 7 | ) 8 | 9 | // This is not actually a test, but a reference to the implementation steps 10 | // If you want to test the answer code, you can copy the code to lab code and run lab test 11 | // 这并不是测试,而是实现步骤的参考 12 | // 若你要测试 answer 代码,你可以将 answer 代码复制到 lab 代码中,并运行 lab 的测试 13 | func TestIsExistsOrNot(t *testing.T) { 14 | fmt.Println("---Lab Test: Is the number exists or Not---") 15 | Row := uint64(1000000) 16 | GenerateBigFile(Row) 17 | fmt.Println("Process: GenerateBigFile is completed.") 18 | 19 | // When we read the data in the big file, we store the data in bitmap at the same time. 20 | // 当我们读取大文件中的数据时,同时将数据存储在 bitmap 中 21 | ReadData() 22 | fmt.Println("Process: ReadData is completed.") 23 | 24 | // Query whether the number exists 25 | // 查询数字是否存在 26 | Num := uint64(rand.Uint64() % Row) 27 | if ExistsQuery(Num) == true { 28 | fmt.Printf("Number: %v is exists.\n", Num) 29 | } else { 30 | fmt.Printf("Number: %v is not exists.\n", Num) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ExistsOrDuplicateProblem/IsExistsOrNot/lab/IsExistsOrNot_lab.go: -------------------------------------------------------------------------------- 1 | package IsExistsOrNotLab 2 | 3 | import ( 4 | "bufio" 5 | "math/rand" 6 | "os" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | var ( 12 | bm bitmap 13 | srcPath = "SourceFile.txt" 14 | ) 15 | 16 | func GenerateBigFile(Row uint64) { 17 | // First: clear file (if it exists) 18 | // 首先清空文件内容(如果文件存在的话) 19 | err := os.Truncate(srcPath, 0) 20 | if err != nil { 21 | panic(err) 22 | } 23 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 24 | defer f.Close() 25 | if err != nil { 26 | panic(err) 27 | } 28 | // Generate the big file 29 | // 生成大文件 30 | rand.Seed(time.Now().UnixMilli()) 31 | for i := uint64(0); i < Row; i++ { 32 | str := strconv.FormatUint(i, 10) 33 | _, err := f.WriteString(str + "\n") 34 | if err != nil { 35 | panic(err) 36 | } 37 | i += rand.Uint64() % 2 38 | } 39 | } 40 | 41 | // GenerateBigFileForTest used in lab testing 42 | func GenerateBigFileForTest(Row, NotExistsNum uint64) { 43 | err := os.Truncate(srcPath, 0) 44 | if err != nil { 45 | panic(err) 46 | } 47 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 48 | defer f.Close() 49 | if err != nil { 50 | panic(err) 51 | } 52 | rand.Seed(time.Now().UnixMilli()) 53 | for i := uint64(0); i < Row; i++ { 54 | if i == NotExistsNum { 55 | continue 56 | } 57 | str := strconv.FormatUint(i, 10) 58 | _, err := f.WriteString(str + "\n") 59 | if err != nil { 60 | panic(err) 61 | } 62 | } 63 | } 64 | 65 | type bitmap struct { 66 | bitmap []byte 67 | } 68 | 69 | // Task 1: Complete the functions of bitmap(grow, SetBit, GetBit) 70 | // 任务 1: 完成 bitmap 的 grow, SetBit, GetBit 函数 71 | 72 | // grow is used for grow bitmaps' size 73 | // grow 用于扩展 bitmap 的空间 74 | func (b *bitmap) grow(size uint64) { 75 | 76 | } 77 | 78 | // SetBit set the bitPos bit to 1 79 | // SetBit 将第 bitPos 位设置为 1 80 | func (b *bitmap) SetBit(bitPos uint64) { 81 | 82 | } 83 | 84 | // GetBit get the value at bitPos (0 or 1) 85 | // GetBit 获取第 bitPos 位的值(0或1) 86 | func (b *bitmap) GetBit(bitPos uint64) int { 87 | 88 | } 89 | 90 | // Task 2: Complete the ReadData function 91 | // 任务 2: 完善 ReadData 函数 92 | 93 | // ReadData read the data in big file,and store the data in bitmap 94 | // ReadData 读取大文件中的数据,并将这些数据存储在 bitmap 中 95 | func ReadData() { 96 | f, err := os.Open(srcPath) 97 | if err != nil { 98 | panic(err) 99 | } 100 | scanner := bufio.NewScanner(f) 101 | for scanner.Scan() { 102 | // Read data and store them in bitmap 103 | // 读取数据并将它们存储在 bitmap 中 104 | 105 | } 106 | } 107 | 108 | func ExistsQuery(num uint64) bool { 109 | if bm.GetBit(num) == 0 { 110 | return false 111 | } 112 | return true 113 | } 114 | -------------------------------------------------------------------------------- /ExistsOrDuplicateProblem/IsExistsOrNot/lab/IsExistsOrNot_lab_test.go: -------------------------------------------------------------------------------- 1 | package IsExistsOrNotLab 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "testing" 7 | ) 8 | 9 | func TestIsExistsOrNot(t *testing.T) { 10 | fmt.Println("---Lab Test: Is the number exists or Not---") 11 | // Only 100000 numbers are generated for fast testing 12 | // 为了快速测试,所以只生成 100000 个数字,当然你也可以自己修改 13 | Row := uint64(100000) 14 | NotExistsNum := rand.Uint64() % Row 15 | GenerateBigFileForTest(Row, NotExistsNum) 16 | fmt.Println("Process: GenerateBigFile is completed.") 17 | 18 | // When we read the data in the big file, we store the data in bitmap at the same time. 19 | // 当我们读取大文件中的数据时,同时将数据存储在 bitmap 中 20 | ReadData() 21 | fmt.Println("Process: ReadData is completed.") 22 | 23 | // Query whether the number exists 24 | // 查询数字是否存在 25 | // First test number does not exist 26 | // 首先测试数字不存在的情况 27 | Num := NotExistsNum 28 | actual := ExistsQuery(Num) 29 | if actual == true { 30 | t.Errorf("expected result: %v ,actual:%v", false, actual) 31 | } 32 | 33 | // Test number exists 34 | // 测试数字存在的情况 35 | if Num-1 >= 0 { 36 | Num = Num - 1 37 | actual = ExistsQuery(Num) 38 | if actual == false { 39 | t.Errorf("expected result: %v ,actual:%v", true, actual) 40 | } 41 | } else if Num+1 < Row { 42 | Num = Num + 1 43 | actual = ExistsQuery(Num) 44 | if actual == false { 45 | t.Errorf("expected result: %v ,actual:%v", true, actual) 46 | } 47 | } 48 | 49 | fmt.Println("---Congratulations: your answer is correct!---") 50 | } 51 | -------------------------------------------------------------------------------- /ExistsOrDuplicateProblem/IsExistsOrNot/lab/README.md: -------------------------------------------------------------------------------- 1 | ## lab 题目:给出海量的不重复整数,之后指定一个数,快速判断指定数字是否存在 2 | ### 思路解析: 3 | 假设前提是内存不足以使用哈希表等常规方法存储所有的不重复整数,所以我们使用节省内存的数据结构 bitmap 来存储这些数字。如果使用 bitmap 可以 1 bit 代表1个数字,即使给出40亿个不重复数字,我们也只需要大约 2^32(42.9亿) bit = 512M 内存便可存储。
4 | Step 1:在读取大文件中的不重复数字时,同时使用 bitmap 进行记录,即到该数字对应的 bit 位置1。
5 | Step 2:查询数字是否存在时,取出相应的 bit 位的值,若为1则已存在,为0则不存在。 6 | 7 | ### 任务说明: 8 | **Task 1: 完善 bitmap 的 grow, SetBit, GetBit 函数**
9 | 本题的重点其实在于完成简单的 bitmap 功能,我们完成足以用于存储和查询的函数即可。
10 | grow 用于扩展 bitmap 的空间。
11 | SetBit 用于设置 bitmap 中指定位置的值为1。
12 | GetBit 用于获取 bitmap 中指定位置的值。
13 | **Task 2: 完善 ReadData 函数**
14 | ReadData 函数用于读取大文件中的数据,我们需要在读取的同时进行 bitmap 的存储。 15 | 16 | **测试说明**
17 | 当你完成了以上的函数之后,在该 lab 文件夹下执行`go test`即可进行 lab 测试!祝贺您一次通过哦!另外,鼓励大家使用不一样的方法实现 lab。 18 | -------------------------------------------------------------------------------- /ExistsOrDuplicateProblem/SearchUniqueNumbers/answer/SearchUniqueNumbers.go: -------------------------------------------------------------------------------- 1 | package SearchUniqueNumbersAnswer 2 | 3 | import ( 4 | "bufio" 5 | "math/rand" 6 | "os" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | var ( 12 | bm TwoBitmap 13 | srcPath = "SourceFile.txt" 14 | ) 15 | 16 | func GenerateBigFile(Row uint64) { 17 | // First: clear file (if it exists) 18 | // 首先清空文件内容(如果文件存在的话) 19 | err := os.Truncate(srcPath, 0) 20 | if err != nil { 21 | panic(err) 22 | } 23 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 24 | defer f.Close() 25 | if err != nil { 26 | panic(err) 27 | } 28 | // Generate the big file 29 | // 生成大文件 30 | for i := uint64(0); i < Row; i++ { 31 | rand.Seed(time.Now().UnixNano()) 32 | num := rand.Uint32() 33 | str := strconv.FormatUint(uint64(num), 10) 34 | _, err := f.WriteString(str + "\n") 35 | if err != nil { 36 | panic(err) 37 | } 38 | } 39 | } 40 | 41 | type TwoBitmap struct { 42 | bitmap []byte 43 | } 44 | 45 | // grow is used for grow TwoBitmaps' size 46 | // grow 用于扩展 TwoBitmap 的空间 47 | func (b *TwoBitmap) grow(size uint64) { 48 | if size < uint64(cap(b.bitmap)) { 49 | // No need to grow capacity 50 | // 无需扩大容量 51 | return 52 | } 53 | old := b.bitmap 54 | New := make([]byte, size+1) 55 | copy(New, old) 56 | b.bitmap = New 57 | } 58 | 59 | // Get2Bit get the value of the two bit binary number composed of bitPos*2 (low bit) and bitPos*2+1 (high bit) 60 | // the Binary values represent meaning: 61 | // 00: not exist 62 | // 01: exist, but only one 63 | // 11: exist, but more than one 64 | // 10: Meaningless 65 | // Get2Bit 获取第 bitPos*2(低位) 与 bitPos*2+1(高位) 组成的两位二进制数的值 66 | // 该二进制值表示的意义: 67 | // 00: 不存在 68 | // 01: 存在,但只有一个 69 | // 11: 存在,但多于一个 70 | // 10: 无意义 71 | func (b *TwoBitmap) Get2Bit(bitPos uint64) int { 72 | bitPos *= 2 73 | bytePos := bitPos / 8 74 | if bytePos >= uint64(cap(b.bitmap)) { 75 | return 0 76 | } else { 77 | result := 0 78 | bit1 := b.bitmap[bytePos] & (1 << ((bitPos) % 8)) 79 | if bit1 > 0 { 80 | bit1 = 1 81 | } 82 | bit2 := b.bitmap[bytePos] & (1 << ((bitPos + 1) % 8)) 83 | if bit2 > 0 { 84 | bit2 = 1 << 1 85 | } 86 | result |= int(1 & bit1) 87 | result |= int(1 << 1 & bit2) 88 | return result 89 | } 90 | } 91 | 92 | // Set2Bit Set the value of bitPos*2 bit and bitPos*2+1 bit 93 | // Please see the comments of Get2Bit for how to set 94 | // Set2Bit 设置第 bitPos*2 位和第 bitPos*2+1 位的值 95 | // 如何设置请看 Get2Bit 函数的注释 96 | func (b *TwoBitmap) Set2Bit(bitPos uint64) { 97 | bitPos *= 2 98 | bytePos := bitPos / 8 99 | if bytePos < uint64(cap(b.bitmap)) { 100 | val := b.Get2Bit(bitPos / 2) 101 | if val == 0 { 102 | b.bitmap[bytePos] |= 1 << ((bitPos) % 8) // 00 -> 01 103 | } else if val == 1 { 104 | b.bitmap[bytePos] |= 1 << ((bitPos + 1) % 8) // 01 -> 11 105 | } 106 | } else { 107 | b.grow(bytePos) 108 | b.bitmap[bytePos] |= 1 << ((bitPos) % 8) // 00 -> 01 109 | } 110 | } 111 | 112 | // ReadData read the data in big file,and store the data in TwoBitmap 113 | // ReadData 读取大文件中的数据,并将这些数据存储在 TwoBitmap 中 114 | func ReadData() { 115 | f, err := os.Open(srcPath) 116 | if err != nil { 117 | panic(err) 118 | } 119 | scanner := bufio.NewScanner(f) 120 | for scanner.Scan() { 121 | // Read data and store them in bitmap 122 | // 读取数据并将它们存储在 bitmap 中 123 | raw := scanner.Text() 124 | data, err := strconv.ParseUint(raw, 10, 64) 125 | if err != nil { 126 | panic(err) 127 | } 128 | bm.Set2Bit(data) 129 | } 130 | } 131 | 132 | // SearchUniqueNumbers return a slice containing all unique integers(exists,but only one) 133 | // SearchUniqueNumbers 返回一个包含所有只出现过一次的整数的切片 134 | func SearchUniqueNumbers() []uint32 { 135 | var result []uint32 136 | f, err := os.Open(srcPath) 137 | if err != nil { 138 | panic(err) 139 | } 140 | scanner := bufio.NewScanner(f) 141 | for scanner.Scan() { 142 | // Read data and store them in TwoBitmap 143 | // 读取数据并将它们存储在 TwoBitmap 中 144 | raw := scanner.Text() 145 | data, err := strconv.ParseUint(raw, 10, 64) 146 | if err != nil { 147 | panic(err) 148 | } 149 | val := bm.Get2Bit(data) 150 | if val == 1 { 151 | // Here we can safely convert uint64 to uint32 152 | // because we ensure that all integers are uint32 when generating source files 153 | // 这里我们可以放心将 uint64 强制转换为 uint32,是因为我们生成源文件时保证了整数都是 uint32 类型的 154 | result = append(result, uint32(data)) 155 | } 156 | } 157 | return result 158 | } 159 | -------------------------------------------------------------------------------- /ExistsOrDuplicateProblem/SearchUniqueNumbers/answer/SearchUniqueNumbers_test.go: -------------------------------------------------------------------------------- 1 | package SearchUniqueNumbersAnswer 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // This is not actually a test, but a reference to the implementation steps 9 | // If you want to test the answer code, you can copy the code to lab code and run lab test 10 | // 这并不是测试,而是实现步骤的参考 11 | // 若你要测试 answer 代码,你可以将 answer 代码复制到 lab 代码中,并运行 lab 的测试 12 | func TestUniqueNumber(t *testing.T) { 13 | fmt.Println("---Search Unique Numbers---") 14 | Row := uint64(1000000) 15 | GenerateBigFile(Row) 16 | fmt.Println("Process: GenerateBigFile is completed.") 17 | // When we read the data in the big file, we store the data in TwoBitmap at the same time. 18 | // 当我们读取大文件中的数据时,同时将数据存储在 TwoBitmap 中 19 | ReadData() 20 | fmt.Println("Process: ReadData is completed.") 21 | 22 | // Use TwoBitmap to find the integers that only appears once 23 | // 利用在 ReadData 建立好的 TwoBitmap 来查找只出现过一次的整数 24 | res := SearchUniqueNumbers() 25 | fmt.Println("Process: SearchUniqueNumbers is completed.") 26 | 27 | fmt.Println("Unique numbers:") 28 | fmt.Println(res) 29 | } 30 | -------------------------------------------------------------------------------- /ExistsOrDuplicateProblem/SearchUniqueNumbers/lab/README.md: -------------------------------------------------------------------------------- 1 | ## lab 题目:海量整数中找出不重复的整数 2 | ### 思路解析: 3 | 假设前提是内存不足以使用哈希表等常规方法存储所有的整数。在本题中一个整数的状态有三种:不存在、存在一个、存在多个,则我们可以使用2个 bit 来代表一个数字,就可以表示3种状态。所以我们可以采用 2-BitMap 数据结构来进行存储,每个数分配2 bit,这2 bit 的意义可以为:00 不存在,01 出现一次,11 出现多次,10 无意义。假设有 2^32(42.9亿)个整数,则需要内存为 2^32 * 2 bit=1 GB,这个内存占用大多数情况下还是可以接受的。当我们寻找不重复的整数时,实际上就是在建立好 2-BitMap 后,用来获取每个整数在 2-BitMap 中的值,若为代表出现一次的值(如前面提到的 01)则是不重复的整数。
4 | Step 1:在读取大文件中的整数时,同时使用 TwoBitmap 进行记录,即到该数字对应的2 bit 位置1。
5 | Step 2:查询数字时,取出相应的2 bit 位的值,若为1(01)则为不重复的整数。 6 | 7 | ### 任务说明: 8 | **Task 1: 完善 2-Bitmap 的 grow, SetBit, GetBit 函数**
9 | 本题的重点其实在于完成简单的 2-Bitmap 功能,我们完成足以用于存储和查询的函数即可。
10 | grow 用于扩展 2-Bitmap 的空间。
11 | SetBit 用于设置 2-Bitmap 中指定位置的值(设置2位)。
12 | GetBit 用于获取 2-Bitmap 中指定位置的值(取出2位)。
13 | **Task 2: 完善 ReadData 函数**
14 | ReadData 函数用于读取大文件中的数据,我们需要在读取的同时进行 2-BitMap 的存储。
15 | **Task 3: 完善 SearchUniqueNumbers 函数**
16 | SearchUniqueNumbers 函数找出所有只出现过一次的整数,并返回一个包含所有只出现过一次的整数的切片。 17 | 18 | **测试说明**
19 | 当你完成了以上的函数之后,在该 lab 文件夹下执行`go test`即可进行 lab 测试!祝贺您一次通过哦!另外,鼓励大家使用不一样的方法实现 lab。 20 | -------------------------------------------------------------------------------- /ExistsOrDuplicateProblem/SearchUniqueNumbers/lab/SearchUniqueNumbers_lab.go: -------------------------------------------------------------------------------- 1 | package SearchUniqueNumbersLab 2 | 3 | import ( 4 | "bufio" 5 | "math/rand" 6 | "os" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | var ( 12 | bm TwoBitmap 13 | srcPath = "SourceFile.txt" 14 | ) 15 | 16 | func GenerateBigFile(Row uint64) { 17 | // First: clear file (if it exists) 18 | // 首先清空文件内容(如果文件存在的话) 19 | err := os.Truncate(srcPath, 0) 20 | if err != nil { 21 | panic(err) 22 | } 23 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 24 | defer f.Close() 25 | if err != nil { 26 | panic(err) 27 | } 28 | // Generate the big file 29 | // 生成大文件 30 | for i := uint64(0); i < Row; i++ { 31 | rand.Seed(time.Now().UnixNano()) 32 | num := rand.Uint32() 33 | str := strconv.FormatUint(uint64(num), 10) 34 | _, err := f.WriteString(str + "\n") 35 | if err != nil { 36 | panic(err) 37 | } 38 | } 39 | } 40 | 41 | // GenerateBigFileForTest used in lab testing 42 | func GenerateBigFileForTest(Row uint64, UniqueNum []uint32) { 43 | // First: clear file (if it exists) 44 | // 首先清空文件内容(如果文件存在的话) 45 | err := os.Truncate(srcPath, 0) 46 | if err != nil { 47 | panic(err) 48 | } 49 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 50 | defer f.Close() 51 | if err != nil { 52 | panic(err) 53 | } 54 | // Generate the big file 55 | // 生成大文件 56 | for i := uint64(0); i < Row; i += 2 { 57 | rand.Seed(time.Now().UnixNano()) 58 | num := rand.Uint32() 59 | str := strconv.FormatUint(uint64(num), 10) 60 | _, err := f.WriteString(str + "\n") 61 | if err != nil { 62 | panic(err) 63 | } 64 | _, err = f.WriteString(str + "\n") 65 | if err != nil { 66 | panic(err) 67 | } 68 | } 69 | for i := 0; i < len(UniqueNum); i++ { 70 | str := strconv.FormatUint(uint64(UniqueNum[i]), 10) 71 | _, err := f.WriteString(str + "\n") 72 | if err != nil { 73 | panic(err) 74 | } 75 | } 76 | } 77 | 78 | type TwoBitmap struct { 79 | bitmap []byte 80 | } 81 | 82 | // Task 1: Complete the functions of TwoBitmap(grow, SetBit, GetBit) 83 | // 任务 1: 完成 TwoBitmap 的 grow, SetBit, GetBit 函数 84 | 85 | // grow is used for grow TwoBitmaps' size 86 | // grow 用于扩展 TwoBitmap 的空间 87 | func (b *TwoBitmap) grow(size uint64) { 88 | 89 | } 90 | 91 | // Get2Bit get the value of the two bit binary number composed of bitPos*2 (low bit) and bitPos*2+1 (high bit) 92 | // the Binary values represent meaning: 93 | // 00: not exist 94 | // 01: exist, but only one 95 | // 11: exist, but more than one 96 | // 10: Meaningless 97 | // Get2Bit 获取第 bitPos*2(低位) 与 bitPos*2+1(高位) 组成的两位二进制数的值 98 | // 该二进制值表示的意义: 99 | // 00: 不存在 100 | // 01: 存在,但只有一个 101 | // 11: 存在,但多于一个 102 | // 10: 无意义 103 | func (b *TwoBitmap) Get2Bit(bitPos uint64) int { 104 | 105 | } 106 | 107 | // Set2Bit Set the value of bitPos*2 bit and bitPos*2+1 bit 108 | // Please see the comments of Get2Bit for how to set 109 | // Set2Bit 设置第 bitPos*2 位和第 bitPos*2+1 位的值 110 | // 如何设置请看 Get2Bit 函数的注释 111 | func (b *TwoBitmap) Set2Bit(bitPos uint64) { 112 | 113 | } 114 | 115 | // Task 2: Complete the ReadData function 116 | // 任务 2: 完善 ReadData 函数 117 | 118 | // ReadData read the data in big file,and store the data in TwoBitmap 119 | // ReadData 读取大文件中的数据,并将这些数据存储在 TwoBitmap 中 120 | func ReadData() { 121 | f, err := os.Open(srcPath) 122 | if err != nil { 123 | panic(err) 124 | } 125 | scanner := bufio.NewScanner(f) 126 | for scanner.Scan() { 127 | // Read data and store them in TwoBitmap 128 | // 读取数据并将它们存储在 TwoBitmap 中 129 | 130 | } 131 | } 132 | 133 | // Task 3: Complete the SearchUniqueNumbers function 134 | // 任务 3: 完善 SearchUniqueNumbers 函数 135 | 136 | // SearchUniqueNumbers return a slice containing all unique integers(exists,but only one) 137 | // SearchUniqueNumbers 返回一个包含所有只出现过一次的整数的切片 138 | func SearchUniqueNumbers() []uint32 { 139 | 140 | } 141 | -------------------------------------------------------------------------------- /ExistsOrDuplicateProblem/SearchUniqueNumbers/lab/SearchUniqueNumbers_lab_test.go: -------------------------------------------------------------------------------- 1 | package SearchUniqueNumbersLab 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestUniqueNumber(t *testing.T) { 11 | fmt.Println("---Lab Test: Search Unique Numbers---") 12 | // Only 100000 numbers are generated for fast testing 13 | // 为了快速测试,所以只生成 100000 个数字,当然你也可以自己修改 14 | Row := uint64(100000) 15 | UniqueNumbersCount := 3 16 | UniqueNumbers := make([]uint32, UniqueNumbersCount) 17 | rand.Seed(time.Now().Unix()) 18 | num := rand.Uint32() 19 | UniqueNumbers[0] = num 20 | UniqueNumbers[1] = num + 1 21 | UniqueNumbers[2] = num + 2 22 | GenerateBigFileForTest(Row, UniqueNumbers) 23 | fmt.Println("Process: GenerateBigFile is completed.") 24 | 25 | // When we read the data in the big file, we store the data in TwoBitmap at the same time. 26 | // 当我们读取大文件中的数据时,同时将数据存储在 TwoBitmap 中 27 | ReadData() 28 | fmt.Println("Process: ReadData is completed.") 29 | 30 | // Use TwoBitmap to find the integers that only appears once 31 | // 利用在 ReadData 建立好的 TwoBitmap 来查找只出现过一次的整数 32 | actual := SearchUniqueNumbers() 33 | fmt.Println("Process: SearchUniqueNumbers is completed.") 34 | 35 | fmt.Println("Unique numbers:") 36 | fmt.Println(actual) 37 | if len(actual) != len(UniqueNumbers) { 38 | t.Errorf("length of actual is not equal to UniqueNumbers,expected: %v,actual: %v", len(UniqueNumbers), len(actual)) 39 | } 40 | 41 | set := make([]bool, UniqueNumbersCount) 42 | for i := 0; i < len(actual); i++ { 43 | flag := false 44 | for j := 0; j < len(UniqueNumbers); j++ { 45 | if set[j] == false && actual[i] == UniqueNumbers[j] { 46 | flag = true 47 | set[j] = true 48 | break 49 | } 50 | } 51 | if flag == false { 52 | t.Errorf("Wrong value: %v", actual[i]) 53 | } 54 | } 55 | fmt.Println("---Congratulations: your answer is correct!---") 56 | } 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Lu JJ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MaxValueProblem/MaxCountIP/answer/MaxCountIP.go: -------------------------------------------------------------------------------- 1 | package MaxCountIPAnswer 2 | 3 | import ( 4 | "bufio" 5 | "container/heap" 6 | "github.com/dchest/siphash" 7 | "math/rand" 8 | "os" 9 | "strconv" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | const ( 15 | IPMod = 256 16 | baseKey = "MassDataProcess1" // For sipHash (用于sipHash) 17 | ) 18 | 19 | // NumPartFile: Number of partition file (小文件/分割文件数量) 20 | // partFile: An array that holds pointers to partition files (存放指向小文件的文件指针的数组) 21 | // partMaxVal: A map that stores the maximum count of IP in each partition file and its count (保存每个小文件中出现次数最多的 IP 和它的次数的 map) 22 | // srcPath: Source file path 源文件(大文件)路径 23 | // partPathPrefix: partition path prefix 小文件的路径前缀 24 | var ( 25 | NumPartFile = uint64(100) 26 | partFile = make([]*os.File, NumPartFile) 27 | partMaxVal = make(map[string]uint64, NumPartFile) 28 | srcPath = "SourceFile.txt" 29 | partPathPrefix = "partFile" 30 | ) 31 | 32 | func GenerateIP() string { 33 | var build strings.Builder 34 | rand.Seed(time.Now().UnixNano()) 35 | for i := 0; i < 4; i++ { 36 | num := rand.Intn(IPMod) 37 | field := strconv.Itoa(num) 38 | build.WriteString(field) 39 | build.WriteString(":") 40 | } 41 | IP := build.String() 42 | IP = strings.TrimRight(IP, ":") 43 | return IP 44 | } 45 | 46 | func GenerateBigFile(Row int) { 47 | // First: clear file (if it exists) 48 | // 首先清空文件内容(如果文件存在的话) 49 | err := os.Truncate(srcPath, 0) 50 | if err != nil { 51 | panic(err) 52 | } 53 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 54 | defer f.Close() 55 | if err != nil { 56 | panic(err) 57 | } 58 | // Generate the big file 59 | // 生成大文件 60 | for i := 0; i < Row; i++ { 61 | str := GenerateIP() + "\n" 62 | _, err := f.WriteString(str) 63 | if err != nil { 64 | panic(err) 65 | } 66 | } 67 | } 68 | 69 | func SplitBigFile(NumPartFile uint64) { 70 | srcFile, err := os.Open(srcPath) 71 | defer srcFile.Close() 72 | if err != nil { 73 | panic(err) 74 | } 75 | scanner := bufio.NewScanner(srcFile) 76 | // We use sipHash as our hash algorithm 77 | // 我们使用 sipHash 作为我们要用的 hash 算法 78 | h := siphash.New([]byte(baseKey)) 79 | 80 | // Create partFile 81 | // 创建小文件 82 | for i := 0; i < len(partFile); i++ { 83 | file, err := os.OpenFile(partPathPrefix+strconv.Itoa(i), os.O_CREATE|os.O_RDWR, 0777) 84 | partFile[i] = file 85 | if err != nil { 86 | panic(err) 87 | } 88 | } 89 | 90 | // Read SourceFile 91 | // 读取源文件(大文件) 92 | for scanner.Scan() { 93 | IP := scanner.Text() 94 | 95 | // Use IP as hash key 96 | // 将 IP 作为哈希用的 key 97 | _, err = h.Write([]byte(IP)) 98 | if err != nil { 99 | panic(err) 100 | } 101 | // get hash 102 | // 获取读到的 IP 对应的哈希值 103 | hash := h.Sum64() % NumPartFile 104 | h.Reset() // Reset hash key(重置 key) 105 | 106 | // Append IP to the partFile corresponding to the hash 107 | // 将 IP 追加写入到哈希值所对应的小文件上 108 | _, err = partFile[hash].WriteString(IP + "\n") 109 | if err != nil { 110 | panic(err) 111 | } 112 | } 113 | } 114 | 115 | func GetPartMax() { 116 | for i := 0; i < len(partFile); i++ { 117 | tempMap := make(map[string]uint64) 118 | f := partFile[i] 119 | // Reset offset to 0 (重置文件指针偏移量为0,即回到起始位置) 120 | _, err := f.Seek(0, 0) 121 | if err != nil { 122 | panic(err) 123 | } 124 | scanner := bufio.NewScanner(f) 125 | var MaxIP string 126 | var MaxCount uint64 127 | 128 | // Scan the current partition file to get the MaxIP and MaxCount 129 | // 扫描当前小文件,获取该文件中出现次数最多的 IP 和 出现次数 130 | for scanner.Scan() { 131 | IP := scanner.Text() 132 | tempMap[IP]++ 133 | if tempMap[IP] > MaxCount { 134 | MaxIP = IP 135 | MaxCount = tempMap[IP] 136 | } 137 | } 138 | partMaxVal[MaxIP] = MaxCount 139 | } 140 | } 141 | 142 | type Item struct { 143 | IP string 144 | count uint64 145 | } 146 | 147 | type ItemHeap []Item 148 | 149 | // The following is the implementation of heap.Interface 150 | // 以下是对堆的接口的实现 151 | 152 | func (h ItemHeap) Len() int { return len(h) } 153 | 154 | func (h ItemHeap) Less(i, j int) bool { 155 | if h[i].count != h[j].count { 156 | return h[i].count > h[j].count 157 | } else { 158 | return h[i].IP > h[j].IP 159 | } 160 | } 161 | func (h ItemHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 162 | 163 | func (h *ItemHeap) Push(val interface{}) { 164 | *h = append(*h, val.(Item)) 165 | } 166 | 167 | func (h *ItemHeap) Pop() interface{} { 168 | old := *h 169 | n := len(old) 170 | x := old[n-1] 171 | *h = old[0 : n-1] 172 | return x 173 | } 174 | 175 | func GetMax() Item { 176 | h := &ItemHeap{} 177 | 178 | // heap sort (我们使用堆排序来找出最大值) 179 | for i, v := range partMaxVal { 180 | heap.Push(h, Item{ 181 | IP: i, 182 | count: v, 183 | }) 184 | } 185 | 186 | // get the Item(IP,count) which has the maximum count 187 | // 获取保存了出现次数最多的 IP 与它的出现次数的二元组 Item 188 | res, ok := heap.Pop(h).(Item) 189 | if !ok { 190 | panic("error: Type Error") 191 | } 192 | return res 193 | } 194 | 195 | func RemoveAndClosePartFile() { 196 | for i := 0; i < len(partFile); i++ { 197 | partFile[i].Close() 198 | err := os.Remove(partPathPrefix + strconv.Itoa(i)) 199 | if err != nil { 200 | panic(err) 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /MaxValueProblem/MaxCountIP/answer/MaxCountIP_test.go: -------------------------------------------------------------------------------- 1 | package MaxCountIPAnswer 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | // This is not actually a test, but a reference to the implementation steps 10 | // If you want to test the answer code, you can copy the code to lab code and run lab test 11 | // 这并不是测试,而是实现步骤的参考 12 | // 若你要测试 answer 代码,你可以将 answer 代码复制到 lab 代码中,并运行 lab 的测试 13 | func TestMaxCountIP(t *testing.T) { 14 | fmt.Println("---Get the IP with the maximum count---") 15 | 16 | // This process could take a while, you can set RowSum smaller to speed up. 17 | // 生成大文件会花上一段时间,你可以调小 RowSum 使生成 IP 数量少一些来加速生成 18 | Row := 1000000 // We generate 1 million IPs for testing 我们生成100万个 IP 进行测试 19 | GenerateBigFile(Row) 20 | fmt.Println("Process: GenerateBigFile is completed.") 21 | 22 | // Step 1: split source file to each partition file 23 | // 第一步:分而治之 24 | defer RemoveAndClosePartFile() 25 | SplitBigFile(NumPartFile) 26 | fmt.Println("Process: SplitBigFile is completed.") 27 | 28 | // Step 2: get the IP with the maximum count in each partition file, 29 | // the IP and its count will be saved to the 'partMaxVal'. 30 | // 第二步:获取每个小文件中出现次数最多的 IP,该 IP 与计数保存到 partMaxVal 哈希表中 31 | GetPartMax() 32 | fmt.Println("Process: GetPartMax is completed.") 33 | 34 | // Step 3: use partMaxVal and heap sort to get the Item(IP,count) which has the maximum count. 35 | // Note that if there are multiple max values, we only get one of them 36 | // 第三步:在上一步我们获得了每个分区文件中出现次数最多的 IP 和它的次数,它们被保存在 partMaxVal 中, 37 | // 这一步我们使用 partMaxVal 和堆排序获取出现次数最多的 IP 与它的出现次数,并保存在 Item 二元组中返回 38 | // 注意如果有多个 IP 出现次数都为最大值,我们只返回其中一个即可 39 | result := GetMax() 40 | fmt.Println("Process: GetMax is completed.") 41 | fmt.Println("Result IP: " + result.IP) 42 | fmt.Println("Result count: " + strconv.FormatUint(result.count, 10)) 43 | } 44 | -------------------------------------------------------------------------------- /MaxValueProblem/MaxCountIP/lab/MaxCountIP_lab.go: -------------------------------------------------------------------------------- 1 | package MaxCountIPLab 2 | 3 | import ( 4 | "bufio" 5 | "math/rand" 6 | "os" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | const ( 13 | IPMod = 256 14 | ) 15 | 16 | // NumPartFile: Number of partition file (小文件/分割文件数量) 17 | // partFile: An array that holds pointers to partition files (存放指向小文件的文件指针的数组) 18 | // partMaxVal: A map that stores the maximum count of IP in each partition file and its count (保存每个小文件中出现次数最多的 IP 和它的次数的 map) 19 | // srcPath: Source file path 源文件(大文件)路径 20 | // partPathPrefix: partition path prefix 小文件的路径前缀 21 | var ( 22 | NumPartFile = uint64(100) 23 | partFile = make([]*os.File, NumPartFile) 24 | partMaxVal = make(map[string]uint64, NumPartFile) 25 | srcPath = "SourceFile.txt" 26 | partPathPrefix = "partFile" 27 | ) 28 | 29 | func GenerateIP() string { 30 | var build strings.Builder 31 | rand.Seed(time.Now().UnixNano()) 32 | for i := 0; i < 4; i++ { 33 | num := rand.Intn(IPMod) 34 | field := strconv.Itoa(num) 35 | build.WriteString(field) 36 | build.WriteString(":") 37 | } 38 | IP := build.String() 39 | IP = strings.TrimRight(IP, ":") 40 | return IP 41 | } 42 | 43 | func GenerateBigFile(Row int) { 44 | // First: clear file (if it exists) 45 | // 首先清空文件内容(如果文件存在的话) 46 | err := os.Truncate(srcPath, 0) 47 | if err != nil { 48 | panic(err) 49 | } 50 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 51 | defer f.Close() 52 | if err != nil { 53 | panic(err) 54 | } 55 | // Generate the big file 56 | // 生成大文件 57 | for i := 0; i < Row; i++ { 58 | str := GenerateIP() + "\n" 59 | _, err := f.WriteString(str) 60 | if err != nil { 61 | panic(err) 62 | } 63 | } 64 | } 65 | 66 | // GenerateBigFileForTest used in lab testing 67 | func GenerateBigFileForTest(maxIP string, maxCount int) { 68 | err := os.Truncate(srcPath, 0) 69 | if err != nil { 70 | panic(err) 71 | } 72 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 73 | defer f.Close() 74 | if err != nil { 75 | panic(err) 76 | } 77 | 78 | for i := 0; i < maxCount; i++ { 79 | str := maxIP + "\n" 80 | _, err := f.WriteString(str) 81 | if err != nil { 82 | panic(err) 83 | } 84 | } 85 | 86 | for i := 0; i < maxCount-1; i++ { 87 | str := GenerateIP() + "\n" 88 | _, err := f.WriteString(str) 89 | if err != nil { 90 | panic(err) 91 | } 92 | } 93 | } 94 | 95 | // Task 1: Complete the SplitBigFile function 96 | // 任务 1: 完善 SplitBigFile 函数 97 | 98 | // SplitBigFile Splitting source files into multiple partition files 99 | // SplitBigFile 实现将源文件(大文件)分割成多个小文件 100 | func SplitBigFile(numPartFile uint64) { 101 | srcFile, err := os.Open(srcPath) 102 | defer srcFile.Close() 103 | if err != nil { 104 | panic(err) 105 | } 106 | 107 | // Step1: Create partition files and store these file pointers in the partFile array. 108 | // Note that the path of partition files is the same as the path in RemoveAndClosePartFile 109 | // 第一步: 创建小文件,并把这些文件指针存放到 partFile 数组中 110 | // 注意这些文件路径的写法要和 RemoveAndClosePartFile 中的保持一致 111 | 112 | // Step2: Read SourceFile and split into multiple partition files , tip: using hash mapping. 113 | // 第二步: 读取源文件(大文件)并分割成多个小文件,提示:使用哈希映射 114 | 115 | } 116 | 117 | // Task 2: Complete the GetPartMax function 118 | // 任务 2: 完善 GetPartMax 函数 119 | 120 | // GetPartMax get the IP with the maximum count in each partition file, 121 | // the IP and its count will be saved to the 'partMaxVal'. 122 | // GetPartMax 获取每个小文件中出现次数最多的 IP,该 IP 与计数保存到 partMaxVal 哈希表中 123 | func GetPartMax() { 124 | 125 | // Step1: Read each partition file 126 | // 第一步: 读取每个小文件 127 | for i := 0; i < len(partFile); i++ { 128 | tempMap := make(map[string]uint64) 129 | f := partFile[i] 130 | // Reset offset to 0 (重置文件指针偏移量为0,即回到起始位置) 131 | _, err := f.Seek(0, 0) 132 | if err != nil { 133 | panic(err) 134 | } 135 | scanner := bufio.NewScanner(f) 136 | 137 | // Step2: Scan the current partition file to get the MaxIP and MaxCount 138 | // Step2:扫描当前小文件,获取该文件中出现次数最多的 IP 和 出现次数 139 | for scanner.Scan() { 140 | 141 | } 142 | // Step3: Store the IP with the maximum count and its count in this file into partMaxVal 143 | // 第三步: 将该文件计数值最大的 IP 与它的计数值保存到 partMaxVal 中 144 | 145 | } 146 | } 147 | 148 | type Item struct { 149 | IP string 150 | count uint64 151 | } 152 | 153 | type ItemHeap []Item 154 | 155 | // The following is the implementation of heap.Interface 156 | // 以下是对堆的接口的实现,已保证是大根堆(Pop 出的是最大值) 157 | 158 | func (h ItemHeap) Len() int { return len(h) } 159 | 160 | func (h ItemHeap) Less(i, j int) bool { 161 | if h[i].count != h[j].count { 162 | return h[i].count > h[j].count 163 | } else { 164 | return h[i].IP > h[j].IP 165 | } 166 | } 167 | func (h ItemHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 168 | 169 | func (h *ItemHeap) Push(val interface{}) { 170 | *h = append(*h, val.(Item)) 171 | } 172 | 173 | func (h *ItemHeap) Pop() interface{} { 174 | old := *h 175 | n := len(old) 176 | x := old[n-1] 177 | *h = old[0 : n-1] 178 | return x 179 | } 180 | 181 | // Task 3: Complete the GetMax function 182 | // 任务 3: 完善 GetMax 函数 183 | 184 | // GetMax Here we use the partMaxVal and heap sort to find the IP with the largest count. 185 | func GetMax() Item { 186 | h := &ItemHeap{} 187 | 188 | // Step1: heap sort 189 | // 第一步:使用堆排序找出最大值(上面已实现堆排序相关接口,当然你也可以使用别的方式) 190 | 191 | // Step2: get the Item(IP,count) which has the maximum count 192 | // 第二步: 获取保存了出现次数最多的 IP 与它的出现次数的二元组 Item 193 | 194 | } 195 | 196 | func RemoveAndClosePartFile() { 197 | for i := 0; i < len(partFile); i++ { 198 | partFile[i].Close() 199 | err := os.Remove(partPathPrefix + strconv.Itoa(i)) 200 | if err != nil { 201 | panic(err) 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /MaxValueProblem/MaxCountIP/lab/MaxCountIP_lab_test.go: -------------------------------------------------------------------------------- 1 | package MaxCountIPLab 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestMaxCountIP(t *testing.T) { 10 | fmt.Println("---Lab Test: Get the IP with the maximum count---") 11 | MaxIP := GenerateIP() 12 | // Only 10000 IPs are generated for fast testing 13 | // 为了快速测试,所以只生成 10000 条 IP,当然你也可以自己修改 14 | MaxCount := 10000 15 | GenerateBigFileForTest(MaxIP, MaxCount) 16 | fmt.Println("Process: GenerateBigFile is completed.") 17 | 18 | defer RemoveAndClosePartFile() 19 | SplitBigFile(NumPartFile) 20 | fmt.Println("Process: SplitBigFile is completed.") 21 | 22 | GetPartMax() 23 | fmt.Println("Process: GetPartMax is completed.") 24 | 25 | result := GetMax() 26 | fmt.Println("Process: GetMax is completed.") 27 | fmt.Println("Result IP: " + result.IP) 28 | fmt.Println("Result count: " + strconv.FormatUint(result.count, 10)) 29 | 30 | if result.IP != MaxIP { 31 | t.Errorf("expected IP: %v ,actual:%v", MaxIP, result.IP) 32 | } 33 | if result.count != uint64(MaxCount) { 34 | t.Errorf("expected count: %v ,actual:%v", MaxCount, result.count) 35 | } 36 | fmt.Println("---Congratulations: your answer is correct!---") 37 | } 38 | -------------------------------------------------------------------------------- /MaxValueProblem/MaxCountIP/lab/README.md: -------------------------------------------------------------------------------- 1 | ## lab 题目:海量日志数据,提取出访问次数最多的IP 2 | ### 思路解析: 3 | 假设前提是内存不足以存储所有的 IP 地址。
4 | Step 1:首先将存储了海量 IP 地址的大文件分成多个小文件(在代码中分成了100份),通过哈希映射的方式将各个 IP 地址映射到各文件中。
5 | ps: 实际上该分成多少份应从内存进行考虑,应做到即使一个小文件内的所有 IP 地址都不相同,在内存中建立存储这些 IP 的哈希表也不会超过内存限制。
6 | Step 2:当大文件转化成了多份小文件,那么我们便可以采用哈希表来进行频率统计,然后获取每个小文件的最值。
7 | Step 3:将每个小文件的最值进行排序,最终得到访问次数最多的 IP 地址。 8 | 9 | ### 任务说明: 10 | **Task 1: 完善 SplitBigFile 函数**
11 | SplitBigFile 函数实现将源文件(大文件)分割成多个小文件,也就是我们要做的第一步:分而治之。
12 | **Task 2: 完善 GetPartMax 函数**
13 | GetPartMax 函数获取每个小文件中出现次数最多的 IP,将该 IP 与计数值保存到代码中提供的 partMaxVal 哈希表中。
14 | **Task 3: 完善 GetMax 函数**
15 | 在 GetMax 函数中,我们使用 GetPartMax 得来的 partMaxVal 哈希表与堆排序的方法,返回一个保含出现次数最多的 IP 与它的出现次数的二元组 Item(见代码)。
16 | 17 | **测试说明**
18 | 当你完成了以上的函数之后,在该 lab 文件夹下执行`go test`即可进行 lab 测试!祝贺您一次通过哦!另外,鼓励大家使用不一样的方法实现 lab。 19 | -------------------------------------------------------------------------------- /NoKProblem/FindTheKthNumber/answer/FindTheKthNumber.go: -------------------------------------------------------------------------------- 1 | package FindTheKthNumberAnswer 2 | 3 | import ( 4 | "bufio" 5 | "math/rand" 6 | "os" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | const ( 12 | UINT64SIZE = uint64(8) 13 | SAFESIZE = uint64(64) 14 | UINT64MAX = uint64(1<<64 - 1) 15 | ) 16 | 17 | var ( 18 | srcPath = "SourceFile" 19 | ) 20 | 21 | func GenerateBigFile(Row int64) { 22 | // First: clear file (if it exists) 23 | // 首先清空文件内容(如果文件存在的话) 24 | err := os.Truncate(srcPath, 0) 25 | if err != nil { 26 | panic(err) 27 | } 28 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 29 | defer f.Close() 30 | if err != nil { 31 | panic(err) 32 | } 33 | // Generate the big file 34 | // 生成大文件 35 | for i := int64(0); i < Row; i++ { 36 | rand.Seed(time.Now().UnixNano()) 37 | val := rand.Uint64() % UINT64MAX 38 | str := strconv.FormatUint(val, 10) 39 | _, err := f.WriteString(str + "\n") 40 | if err != nil { 41 | panic(err) 42 | } 43 | } 44 | } 45 | 46 | // QuickSelect is used to quickly find the kth number 47 | // Example(find 5th number): QuickSelect(s,0,int64(len(s)-1),5) 48 | // QuickSelect 用来快速寻找到从小到大的第 k 个数 49 | // 示例(找到第5个数):QuickSelect(s,0,int64(len(s)-1),5) 50 | func QuickSelect(s []uint64, start, end, k int64) uint64 { 51 | p := s[end] 52 | l := start 53 | for i := start; i < end; i++ { 54 | if s[i] <= p { 55 | s[l], s[i] = s[i], s[l] 56 | l++ 57 | } 58 | } 59 | s[l], s[end] = s[end], s[l] 60 | 61 | if l == k-1 { 62 | return s[l] 63 | } else if l < k-1 { 64 | return QuickSelect(s, l+1, end, k) 65 | } else { 66 | return QuickSelect(s, start, l-1, k) 67 | } 68 | } 69 | 70 | // SplitFileByBitThenGetKth recursively get the value of each bit from the highest bit to the lowest bit, 71 | // and then divides the numbers into different files according to the bit value (0 or 1). 72 | // SplitFileByBitThenGetKth 递归地从二进制最高位到最低位获取每一位的值,之后按比特值(0或1)将数字分到不同的文件中。 73 | func SplitFileByBitThenGetKth(f *os.File, count, k, bitPos int64, memoryLimit uint64) uint64 { 74 | defer func() { 75 | fileName := f.Name() 76 | if fileName != srcPath { 77 | os.Remove(f.Name()) 78 | } 79 | }() 80 | defer func() { 81 | fileName := f.Name() 82 | if fileName != srcPath { 83 | f.Close() 84 | } 85 | }() 86 | if bitPos < 0 || count < 0 { 87 | // When you get here, please check whether your code is correct 88 | // or whether the memoryLimit is too small 89 | // 当你的代码到了这里时,请检查你的代码是否正确,或测试使用的 memoryLimit 是否设置过小 90 | panic("unexpected error") 91 | } 92 | 93 | f.Seek(0, 0) 94 | scanner := bufio.NewScanner(f) 95 | // We checked that count is non-negative before, so it can be converted to uint64 96 | // 我们在之前已经对 count 进行了非负数的检查,所以可以放心转换为 uint64 97 | if uint64(count)*UINT64SIZE+SAFESIZE <= memoryLimit { 98 | var arr []uint64 99 | for scanner.Scan() { 100 | str := scanner.Text() 101 | val, err := strconv.ParseUint(str, 10, 64) 102 | if err != nil { 103 | panic(err) 104 | } 105 | arr = append(arr, val) 106 | } 107 | return QuickSelect(arr, 0, int64(len(arr)-1), k) 108 | } else { 109 | cnt0 := int64(0) 110 | cnt1 := int64(0) 111 | 112 | f0, err := os.OpenFile(f.Name()+"_0", os.O_CREATE|os.O_RDWR, 0777) 113 | if err != nil { 114 | panic(err) 115 | } 116 | f1, err := os.OpenFile(f.Name()+"_1", os.O_CREATE|os.O_RDWR, 0777) 117 | if err != nil { 118 | panic(err) 119 | } 120 | 121 | for scanner.Scan() { 122 | str := scanner.Text() 123 | val, err := strconv.ParseUint(str, 10, 64) 124 | if err != nil { 125 | panic(err) 126 | } 127 | if val&(1<= k { 137 | fileName := f1.Name() 138 | f1.Close() 139 | os.Remove(fileName) 140 | return SplitFileByBitThenGetKth(f0, cnt0, k, bitPos-1, memoryLimit) 141 | } else { 142 | fileName := f0.Name() 143 | f0.Close() 144 | os.Remove(fileName) 145 | return SplitFileByBitThenGetKth(f1, cnt1, k-cnt0, bitPos-1, memoryLimit) 146 | } 147 | } 148 | } 149 | 150 | // SplitFileByPivotThenGetKth compares the number in the file with the value as pivot, 151 | // and divides the number into different partition files according to the comparison results. 152 | // Tip: you can use the dichotomy method 153 | // SplitFileByPivotThenGetKth 将文件中的数字与作为轴点的值进行比较,根据比较结果的不同将数字分到不同的分区文件中。 154 | // 提示:你可以使用二分法 155 | func SplitFileByPivotThenGetKth(f *os.File, count, k int64, left, right, memoryLimit uint64) uint64 { 156 | defer func() { 157 | fileName := f.Name() 158 | if fileName != srcPath { 159 | os.Remove(f.Name()) 160 | } 161 | }() 162 | defer func() { 163 | fileName := f.Name() 164 | if fileName != srcPath { 165 | f.Close() 166 | } 167 | }() 168 | 169 | if left >= right || count < 0 { 170 | // When you get here, please check whether your code is correct 171 | // or whether the memoryLimit is too small 172 | // 当你的代码到了这里时,请检查你的代码是否正确,或测试使用的 memoryLimit 是否设置过小 173 | panic("unexpected error") 174 | } 175 | 176 | f.Seek(0, 0) 177 | scanner := bufio.NewScanner(f) 178 | // We checked that count is non-negative before, so it can be converted to uint64 179 | // 我们在之前已经对 count 进行了非负数的检查,所以可以放心转换为 uint64 180 | if uint64(count)*UINT64SIZE+SAFESIZE <= memoryLimit { 181 | var arr []uint64 182 | for scanner.Scan() { 183 | str := scanner.Text() 184 | val, err := strconv.ParseUint(str, 10, 64) 185 | if err != nil { 186 | panic(err) 187 | } 188 | arr = append(arr, val) 189 | } 190 | return QuickSelect(arr, 0, int64(len(arr)-1), k) 191 | } else { 192 | cnt0 := int64(0) 193 | cnt1 := int64(0) 194 | pivot := left + (right-left)/2 195 | f0, err := os.OpenFile(f.Name()+"_0", os.O_CREATE|os.O_RDWR, 0777) 196 | if err != nil { 197 | panic(err) 198 | } 199 | f1, err := os.OpenFile(f.Name()+"_1", os.O_CREATE|os.O_RDWR, 0777) 200 | if err != nil { 201 | panic(err) 202 | } 203 | 204 | for scanner.Scan() { 205 | str := scanner.Text() 206 | val, err := strconv.ParseUint(str, 10, 64) 207 | if err != nil { 208 | panic(err) 209 | } 210 | if val <= pivot { 211 | f0.WriteString(str + "\n") 212 | cnt0++ 213 | } else { 214 | f1.WriteString(str + "\n") 215 | cnt1++ 216 | } 217 | } 218 | 219 | if cnt0 >= k { 220 | fileName := f1.Name() 221 | f1.Close() 222 | os.Remove(fileName) 223 | right = pivot 224 | return SplitFileByPivotThenGetKth(f0, cnt0, k, left, right, memoryLimit) 225 | } else { 226 | fileName := f0.Name() 227 | f0.Close() 228 | os.Remove(fileName) 229 | left = pivot + 1 230 | return SplitFileByPivotThenGetKth(f1, cnt1, k-cnt0, left, right, memoryLimit) 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /NoKProblem/FindTheKthNumber/answer/FindTheKthNumber_test.go: -------------------------------------------------------------------------------- 1 | package FindTheKthNumberAnswer 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | // This is not actually a test, but a reference to the implementation steps 10 | // If you want to test the answer code, you can copy the code to lab code and run lab test 11 | // 这并不是测试,而是实现步骤的参考 12 | // 若你要测试 answer 代码,你可以将 answer 代码复制到 lab 代码中,并运行 lab 的测试 13 | func TestFindTheKthNumber(t *testing.T) { 14 | fmt.Println("---Find the Kth number---") 15 | Row := int64(1000000) 16 | kth := Row / 2 17 | bitPos := int64(64) 18 | memoryLimit := UINT64SIZE * 2000 19 | left, right := uint64(0), UINT64MAX 20 | GenerateBigFile(Row) 21 | fmt.Println("Process: GenerateBigFile is completed.") 22 | srcFile, err := os.Open(srcPath) 23 | if err != nil { 24 | panic(err) 25 | } 26 | 27 | result := SplitFileByBitThenGetKth(srcFile, Row, kth, bitPos, memoryLimit) 28 | fmt.Println("Process: SplitFileByBitThenGetKth is completed.") 29 | fmt.Printf("SplitFileByBitThenGetKth: The %vth number is %v.\n", kth, result) 30 | 31 | result = SplitFileByPivotThenGetKth(srcFile, Row, kth, left, right, memoryLimit) 32 | fmt.Println("Process: SplitFileByPivotThenGetKth is completed.") 33 | fmt.Printf("SplitFileByPivotThenGetKth: The %vth number is %v.\n", kth, result) 34 | } 35 | -------------------------------------------------------------------------------- /NoKProblem/FindTheKthNumber/lab/FindTheKthNumber_lab.go: -------------------------------------------------------------------------------- 1 | package FindTheKthNumberLab 2 | 3 | import ( 4 | "bufio" 5 | "math/rand" 6 | "os" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | const ( 12 | UINT64SIZE = uint64(8) 13 | SAFESIZE = uint64(64) 14 | UINT64MAX = uint64(1<<64 - 1) 15 | ) 16 | 17 | var ( 18 | srcPath = "SourceFile" 19 | ) 20 | 21 | func GenerateBigFile(Row int64) { 22 | // First: clear file (if it exists) 23 | // 首先清空文件内容(如果文件存在的话) 24 | err := os.Truncate(srcPath, 0) 25 | if err != nil { 26 | panic(err) 27 | } 28 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 29 | defer f.Close() 30 | if err != nil { 31 | panic(err) 32 | } 33 | // Generate the big file 34 | // 生成大文件 35 | for i := int64(0); i < Row; i++ { 36 | rand.Seed(time.Now().UnixNano()) 37 | val := rand.Uint64() % UINT64MAX 38 | str := strconv.FormatUint(val, 10) 39 | _, err := f.WriteString(str + "\n") 40 | if err != nil { 41 | panic(err) 42 | } 43 | } 44 | } 45 | 46 | // GenerateBigFileForTest used in lab testing 47 | func GenerateBigFileForTest(Row, kth int64, number uint64) { 48 | if Row < 0 || uint64(Row) >= UINT64MAX { 49 | panic("Row can not greater than " + strconv.FormatUint(UINT64MAX, 10)) 50 | } 51 | // First: clear file (if it exists) 52 | // 首先清空文件内容(如果文件存在的话) 53 | err := os.Truncate(srcPath, 0) 54 | if err != nil { 55 | panic(err) 56 | } 57 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 58 | defer f.Close() 59 | if err != nil { 60 | panic(err) 61 | } 62 | // Generate the big file 63 | // 生成大文件 64 | str := strconv.FormatUint(number, 10) 65 | _, err = f.WriteString(str + "\n") 66 | if err != nil { 67 | panic(err) 68 | } 69 | for i := int64(0); i < kth-1; i++ { 70 | rand.Seed(time.Now().UnixNano()) 71 | val := rand.Uint64() % number 72 | str = strconv.FormatUint(val, 10) 73 | _, err := f.WriteString(str + "\n") 74 | if err != nil { 75 | panic(err) 76 | } 77 | } 78 | for i := int64(0); i < Row-kth; i++ { 79 | rand.Seed(time.Now().UnixNano()) 80 | val := rand.Uint64()%(UINT64MAX-number) + number 81 | str = strconv.FormatUint(val, 10) 82 | _, err := f.WriteString(str + "\n") 83 | if err != nil { 84 | panic(err) 85 | } 86 | } 87 | } 88 | 89 | // QuickSelect is used to quickly find the kth number 90 | // Example(find 5th number): QuickSelect(s,0,int64(len(s)-1),5) 91 | // Challenge: try to implement QuickSelect yourself 92 | // QuickSelect 用来快速寻找到从小到大的第 k 个数 93 | // 示例(找到第5个数):QuickSelect(s,0,int64(len(s)-1),5) 94 | // 挑战:尝试自己实现 QuickSelect 95 | func QuickSelect(s []uint64, start, end, k int64) uint64 { 96 | p := s[end] 97 | l := start 98 | for i := start; i < end; i++ { 99 | if s[i] <= p { 100 | s[l], s[i] = s[i], s[l] 101 | l++ 102 | } 103 | } 104 | s[l], s[end] = s[end], s[l] 105 | 106 | if l == k-1 { 107 | return s[l] 108 | } else if l < k-1 { 109 | return QuickSelect(s, l+1, end, k) 110 | } else { 111 | return QuickSelect(s, start, l-1, k) 112 | } 113 | } 114 | 115 | // Task 1: complete the SplitFileByBitThenGetKth function 116 | // 任务 1:完善 SplitFileByBitThenGetKth 函数 117 | 118 | // SplitFileByBitThenGetKth recursively get the value of each bit from the highest bit to the lowest bit, 119 | // and then divides the numbers into different files according to the bit value (0 or 1) 120 | // When the size of all numbers in the current file is lower than the memoryLimit, 121 | // you can load all the numbers into memory, find the k-th number and return it 122 | // Parameter: 123 | // f: pointer to current file 124 | // count: the total number of numbers in the current file 125 | // k: the rank of the k-th number in the current file in descending order 126 | // bitPos: current bit to check 127 | // memoryLimit: memory limit size 128 | // SplitFileByBitThenGetKth 递归地从二进制最高位到最低位获取每一位的值,之后按比特值(0或1)将数字分到不同的文件中。 129 | // 当前文件中所有数字的大小总和低于内存限制时,便可以把所有数字加载进内存,查找第 k 个数并返回它 130 | // 参数说明: 131 | // f:指向当前文件的指针 132 | // count:当前文件内的数字总数 133 | // k: 我们要找的第 k 个数在当前文件中的排位(从小到大顺序) 134 | // bitPos: 函数当前要检查的比特位 135 | // memoryLimit: 内存限制大小 136 | func SplitFileByBitThenGetKth(f *os.File, count, k, bitPos int64, memoryLimit uint64) uint64 { 137 | defer func() { 138 | fileName := f.Name() 139 | if fileName != srcPath { 140 | os.Remove(f.Name()) 141 | } 142 | }() 143 | defer func() { 144 | fileName := f.Name() 145 | if fileName != srcPath { 146 | f.Close() 147 | } 148 | }() 149 | if bitPos < 0 || count < 0 { 150 | // When you get here, please check whether your code is correct 151 | // or whether the memoryLimit is too small 152 | // 当你的代码到了这里时,请检查你的代码是否正确,或测试使用的 memoryLimit 是否设置过小 153 | panic("unexpected error") 154 | } 155 | 156 | f.Seek(0, 0) 157 | scanner := bufio.NewScanner(f) 158 | 159 | // We checked that count is non-negative before, so it can be converted to uint64 160 | // 我们在之前已经对 count 进行了非负数的检查,所以可以放心转换为 uint64 161 | if uint64(count)*UINT64SIZE+SAFESIZE <= memoryLimit { 162 | 163 | } else { 164 | 165 | } 166 | } 167 | 168 | // Task 2: complete the SplitFileByPivotThenGetKth function 169 | // 任务 2:完善 SplitFileByPivotThenGetKth 函数 170 | 171 | // SplitFileByPivotThenGetKth compares the number in the file with the value as pivot, 172 | // and divides the number into different partition files according to the comparison results. 173 | // When the size of all numbers in the current file is lower than the memory limit, 174 | // you can load all the numbers into memory, find the k-th number and return it 175 | // We use a bisection algorithm to implement this function 176 | // Parameter: 177 | // f: pointer to current file 178 | // count: the total number of numbers in the current file 179 | // k: the rank of the k-th number in the current file in descending order 180 | // left: minimum value of the current pivot range (left endpoint of bisection algorithm) 181 | // right: maximum value of the current pivot range (right endpoint of bisection algorithm) 182 | // memoryLimit: memory limit size 183 | // SplitFileByPivotThenGetKth 将文件中的数字与作为轴点的值进行比较,根据比较结果的不同将数字分到不同的分区文件中 184 | // 当前文件中所有数字的大小总和低于内存限制时,便可以把所有数字加载进内存,查找第 k 个数并返回它 185 | // 我们使用二分法来实现该函数 186 | // 参数说明: 187 | // f:指向当前文件的指针 188 | // count:当前文件内的数字总数 189 | // k: 我们要找的第 k 个数在当前文件中的排位(从小到大顺序) 190 | // left: 当前轴点值范围的最小值(二分法中的左端点) 191 | // right: 当前轴点值范围的最大值(二分法中的右端点) 192 | // memoryLimit: 内存限制大小 193 | func SplitFileByPivotThenGetKth(f *os.File, count, k int64, left, right, memoryLimit uint64) uint64 { 194 | defer func() { 195 | fileName := f.Name() 196 | if fileName != srcPath { 197 | os.Remove(f.Name()) 198 | } 199 | }() 200 | defer func() { 201 | fileName := f.Name() 202 | if fileName != srcPath { 203 | f.Close() 204 | } 205 | }() 206 | 207 | if left >= right || count < 0 { 208 | panic("unexpected error") 209 | } 210 | 211 | f.Seek(0, 0) 212 | scanner := bufio.NewScanner(f) 213 | 214 | // We checked that count is non-negative before, so it can be converted to uint64 215 | // 我们在之前已经对 count 进行了非负数的检查,所以可以放心转换为 uint64 216 | if uint64(count)*UINT64SIZE+SAFESIZE <= memoryLimit { 217 | 218 | } else { 219 | 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /NoKProblem/FindTheKthNumber/lab/FindTheKthNumber_lab_test.go: -------------------------------------------------------------------------------- 1 | package FindTheKthNumberLab 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestFindTheKthNumber(t *testing.T) { 11 | fmt.Println("---Lab Test: Find the Kth number---") 12 | bitPos := int64(64) 13 | memoryLimit := UINT64SIZE * 1024 14 | left, right := uint64(0), UINT64MAX 15 | Row := int64(100000) 16 | 17 | fmt.Printf("Lab Info: The size of all numbers is %v bytes, The memory limit is %v bytes,good luck!\n", uint64(Row)*UINT64SIZE, memoryLimit) 18 | 19 | kth := Row / 2 20 | expected := uint64(rand.Uint64() % UINT64MAX / 2) 21 | 22 | fmt.Printf("---Test1: Find the %vth(median) number---\n", kth) 23 | 24 | GenerateBigFileForTest(Row, kth, expected) 25 | fmt.Println("Process: GenerateBigFile is completed.") 26 | 27 | srcFile, err := os.Open(srcPath) 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | result := SplitFileByBitThenGetKth(srcFile, Row, kth, bitPos, memoryLimit) 33 | fmt.Println("Process: SplitFileByBitThenGetKth is completed.") 34 | if result != expected { 35 | t.Errorf("expected: %v, actual: %v\n", expected, result) 36 | } 37 | fmt.Printf("SplitFileByBitThenGetKth: The %vth number is %v\n", kth, result) 38 | 39 | result = SplitFileByPivotThenGetKth(srcFile, Row, kth, left, right, memoryLimit) 40 | fmt.Println("Process: SplitFileByPivotThenGetKth is completed.") 41 | if result != expected { 42 | t.Errorf("expected: %v, actual: %v\n", expected, result) 43 | } 44 | fmt.Printf("SplitFileByPivotThenGetKth: The %vth number is %v\n", kth, result) 45 | 46 | kth = 1 47 | expected = uint64(rand.Uint64() % UINT64MAX / 2) 48 | 49 | fmt.Printf("---Test2: Find the %vth(minimum) number---\n", kth) 50 | 51 | GenerateBigFileForTest(Row, kth, expected) 52 | fmt.Println("Process: GenerateBigFile is completed.") 53 | 54 | srcFile, err = os.Open(srcPath) 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | result = SplitFileByBitThenGetKth(srcFile, Row, kth, bitPos, memoryLimit) 60 | fmt.Println("Process: SplitFileByBitThenGetKth is completed.") 61 | if result != expected { 62 | t.Errorf("expected: %v, actual: %v\n", expected, result) 63 | } 64 | fmt.Printf("SplitFileByBitThenGetKth: The %vth number is %v\n", kth, result) 65 | 66 | result = SplitFileByPivotThenGetKth(srcFile, Row, kth, left, right, memoryLimit) 67 | fmt.Println("Process: SplitFileByPivotThenGetKth is completed.") 68 | if result != expected { 69 | t.Errorf("expected: %v, actual: %v\n", expected, result) 70 | } 71 | fmt.Printf("SplitFileByPivotThenGetKth: The %vth number is %v\n", kth, result) 72 | 73 | kth = Row 74 | expected = uint64(rand.Uint64() % UINT64MAX) 75 | 76 | fmt.Printf("---Test3: Find the %vth(maximum) number---\n", kth) 77 | 78 | GenerateBigFileForTest(Row, kth, expected) 79 | fmt.Println("Process: GenerateBigFile is completed.") 80 | 81 | srcFile, err = os.Open(srcPath) 82 | if err != nil { 83 | panic(err) 84 | } 85 | 86 | result = SplitFileByBitThenGetKth(srcFile, Row, kth, bitPos, memoryLimit) 87 | fmt.Println("Process: SplitFileByBitThenGetKth is completed.") 88 | if result != expected { 89 | t.Errorf("expected: %v, actual: %v\n", expected, result) 90 | } 91 | fmt.Printf("SplitFileByBitThenGetKth: The %vth number is %v\n", kth, result) 92 | 93 | result = SplitFileByPivotThenGetKth(srcFile, Row, kth, left, right, memoryLimit) 94 | fmt.Println("Process: SplitFileByPivotThenGetKth is completed.") 95 | if result != expected { 96 | t.Errorf("expected: %v, actual: %v\n", expected, result) 97 | } 98 | fmt.Printf("SplitFileByPivotThenGetKth: The %vth number is %v\n", kth, result) 99 | 100 | fmt.Println("---Congratulations: You passed all the tests! ╰(*°▽°*)╯---") 101 | } 102 | -------------------------------------------------------------------------------- /NoKProblem/FindTheKthNumber/lab/README.md: -------------------------------------------------------------------------------- 1 | ## lab 题目:在海量数据中找出从小到大的第 k 个数 2 | ### 思路解析: 3 | 假设前提是内存不足以存储所有的数据,所以我们依然是选择将大文件分成小文件来处理,关于分法,本次 lab 需要完成如下两种:
4 | Method 1: 二进制位比较。从最高位到最低位依次比较每一位的值,值为0放一个文件中,值为1放另一个文件中,这样分割到越低位时,文件越小。
5 | Method 2: 轴点值比较。这里我们可以使用二分算法,将中间值作为轴点值,令当前文件中的每一个数字与轴点值进行比较,小于等于轴点值的可以放同一个文件,大于轴点值的放到另一个文件。
6 | 那么这两个方法都如何往下递归呢?我们这里借助快速排序的思想,以二进制位比较为例:假如原本有 10 亿无符号整数,要找第5亿个数,我们首先考虑最高位,将最高位为0的分到 file_0,为1的分到 file_1,一边分一边给两个文件分到的数字计数。分完之后假设 file_0 的计数值是6亿,file_1则是剩下4亿,由于是无符号,最高位为0的 file_0 文件中的数全都比 file_1 要小,则第5亿个数就在 file_0 中,且是 file_0 中从小到大的第5亿个数,则我们向 file_0 递归。如果这里是 file_0 有4亿而 file_1 有6亿的话,那么第5亿个数在 file_1 中,我们需要向 file_1 递归,但是注意!递归到 file_1 时第5亿个数的从小到大的排位变化了,第5亿个数在 file_1 中的排位应是从小到大的第1亿个数。就这样一直分割文件和递归下去,直到文件大小低于内存限制时,我们便可以使用排序或者快速选择将答案数字找出来。
7 | 8 | ### 任务说明: 9 | **Task 1: 完善 SplitFileByBitThenGetKth 函数**
10 | SplitFileByBitThenGetKth 函数使用二进制位比较的方式分割文件,当文件中所有整数的大小加上64B的安全预留大小不超过内存限制时,即可以使用排序或快速选择(已提供)等方法返回第 k 个数。
11 | **Task 2: 完善 SplitFileByPivotThenGetKth 函数**
12 | SplitFileByPivotThenGetKth 函数使用轴点值比较的方式分割文件,当文件中所有整数的大小加上64B的安全预留大小不超过内存限制时,即可以使用排序或快速选择(已提供)等方法返回第 k 个数。
13 | 14 | **测试说明**
15 | 当你完成了以上的函数之后,在该 lab 文件夹下执行`go test`即可进行 lab 测试!祝贺您一次通过哦!另外,鼓励大家使用不一样的方法实现 lab。 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mass Data Processing Lab 海量数据处理 Lab 2 | 海量数据处理是很经典且常见的一类面试题目,网络上也有很多这类题目的情景与对应方案,但大多都是在文字说明而没有人真的用代码去实现。我对海量数据处理这一类的题目很感兴趣,所以在这里我会实现一些海量数据处理的典型题目的代码 Demo 供大家参考,也欢迎大家发现问题后提 issue 或 pr 指正,当然也支持你提出一个新场景并贡献对应的 Go 代码实现!,要带上 answer、 lab 以及对应的 test 哦。😃😃
3 | ### 2022/6/28:
4 | 今天做了一个决定,就是把“海量数据处理Demo”更名为“海量数据处理lab”,因为我不希望大家只是进来看看我实现的方式后就过了,因为这类问题还有更多的实现方法,就算方法相同,不同的人写出来的代码细节上也可能不一样。或许你也想尝试亲手做一做呢?所以我将把这个仓库改造成 lab,每一类问题分为 lab 和 answer 文件夹,lab 即是你要去完善并使用配套的 test 来验证是否通过的代码,answer 即是我原本实现的 demo,可以作为答案参考。欢迎大家踊跃参与亲身实践!😀😀
5 | **注:Go Sdk = 1.18** 6 | 7 | ## 题目列表: 8 | ### 一、 海量数据中的最值问题:
9 | | 题目 | lab 链接 | answer 链接 | 10 | | - | :-: | :-: | 11 | | 1. 海量日志数据,提取出访问次数最多的IP: | [MaxCountIP_lab](https://github.com/ncghost1/MassDataProcessingLab/tree/main/MaxValueProblem/MaxCountIP/lab) | [MaxCountIP_answer](https://github.com/ncghost1/MassDataProcessingLab/tree/main/MaxValueProblem/MaxCountIP/answer) | 12 | 13 | ### 二、海量数据的某个数据是否存在或重复存在的问题:
14 | | 题目 | lab 链接 | answer 链接 | 15 | | - | :-: | :-: | 16 | | 1. 给出海量的不重复整数,之后指定一个数,快速判断指定数字是否存在 | [IsExistsOrNot_lab](https://github.com/ncghost1/MassDataProcessingLab/tree/main/ExistsOrDuplicateProblem/IsExistsOrNot/lab) | [IsExistsOrNot_answer](https://github.com/ncghost1/MassDataProcessingLab/tree/main/ExistsOrDuplicateProblem/IsExistsOrNot/answer) | 17 | | 2. 海量整数中找出不重复的整数 | [SearchUniqueNumbers_lab](https://github.com/ncghost1/MassDataProcessingLab/tree/main/ExistsOrDuplicateProblem/SearchUniqueNumbers/lab) | [SearchUniqueNumbers_answer](https://github.com/ncghost1/MassDataProcessingLab/tree/main/ExistsOrDuplicateProblem/SearchUniqueNumbers/answer) | 18 | 19 | ### 三、海量数据的 top K 问题:
20 | | 题目 | lab 链接 | answer 链接 | 21 | | - | :-: | :-: | 22 | | 1. 如何在海量数据中找出最大的 10 个数: | [FindTop10Numbers_lab](https://github.com/ncghost1/MassDataProcessingLab/tree/main/TopKProblem/FindTop10Numbers/lab) | [FindTop10Numbers_answer](https://github.com/ncghost1/MassDataProcessingLab/tree/main/TopKProblem/FindTop10Numbers/answer) | 23 | 24 | ### 四、海量数据的 No.K 问题:
25 | | 题目 | lab 链接 | answer 链接 | 26 | | - | :-: | :-: | 27 | | 1. 在海量数据中找出从小到大第 k 个数: | [FindTheKthNumber_lab](https://github.com/ncghost1/MassDataProcessingLab/tree/main/NoKProblem/FindTheKthNumber/lab) | [FindTheKthNumber_answer](https://github.com/ncghost1/MassDataProcessingLab/tree/main/NoKProblem/FindTheKthNumber/answer) | 28 | -------------------------------------------------------------------------------- /TopKProblem/FindTop10Numbers/answer/FindTop10Numbers.go: -------------------------------------------------------------------------------- 1 | package FindTop10NumbersAnswer 2 | 3 | import ( 4 | "bufio" 5 | "container/heap" 6 | "github.com/dchest/siphash" 7 | "math/rand" 8 | "os" 9 | "strconv" 10 | "time" 11 | ) 12 | 13 | const ( 14 | maxLimit = 100000000 15 | baseKey = "MassDataProcess4" // For sipHash (用于sipHash) 16 | ) 17 | 18 | var ( 19 | bm bitmap 20 | NumPartFile = uint64(100) 21 | partFile = make([]*os.File, NumPartFile) 22 | srcPath = "SourceFile.txt" 23 | partPathPrefix = "partFile" 24 | ) 25 | 26 | func GenerateBigFile(Row uint64) { 27 | // First: clear file (if it exists) 28 | // 首先清空文件内容(如果文件存在的话) 29 | err := os.Truncate(srcPath, 0) 30 | if err != nil { 31 | panic(err) 32 | } 33 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 34 | defer f.Close() 35 | if err != nil { 36 | panic(err) 37 | } 38 | // Generate the big file 39 | // 生成大文件 40 | for i := uint64(0); i < Row; i++ { 41 | rand.Seed(time.Now().UnixNano()) 42 | 43 | // We control range to ensure that bitmap memory does not exceed the limit on most computers 44 | // 我们控制范围是为了保证 bitmap 占用的内存在大多数电脑下不会超出内存限制 45 | val := rand.Uint64() % maxLimit 46 | str := strconv.FormatUint(val, 10) 47 | _, err := f.WriteString(str + "\n") 48 | if err != nil { 49 | panic(err) 50 | } 51 | } 52 | } 53 | 54 | type bitmap struct { 55 | bitmap []byte 56 | } 57 | 58 | // grow is used for grow bitmaps' size 59 | // grow 用于扩展 bitmap 的空间 60 | func (b *bitmap) grow(size uint64) { 61 | if size < uint64(cap(b.bitmap)) { 62 | // No need to grow capacity 63 | // 无需扩大容量 64 | return 65 | } 66 | old := b.bitmap 67 | New := make([]byte, size+1) 68 | copy(New, old) 69 | b.bitmap = New 70 | } 71 | 72 | // SetBit set the bitPos bit to 1 73 | // SetBit 将第 bitPos 位设置为 1 74 | func (b *bitmap) SetBit(bitPos uint64) { 75 | bytePos := bitPos / 8 76 | if bytePos < uint64(cap(b.bitmap)) { 77 | b.bitmap[bytePos] |= 1 << ((bitPos) % 8) 78 | } else { 79 | b.grow(bytePos) 80 | b.bitmap[bytePos] |= 1 << ((bitPos) % 8) 81 | } 82 | } 83 | 84 | // GetBit get the value at bitPos (0 or 1) 85 | // GetBit 获取第 bitPos 位的值(0或1) 86 | func (b *bitmap) GetBit(bitPos uint64) int { 87 | bytePos := bitPos / 8 88 | if bytePos >= uint64(cap(b.bitmap)) { 89 | return 0 90 | } else { 91 | bit := b.bitmap[bytePos] & (1 << ((bitPos) % 8)) 92 | if bit != 0 { 93 | return 1 94 | } 95 | return 0 96 | } 97 | } 98 | 99 | // SplitBigFile split the source file into partition files 100 | // SplitBigFile 将源文件分割到多个小文件中 101 | func SplitBigFile(NumPartFile uint64) { 102 | srcFile, err := os.Open(srcPath) 103 | defer srcFile.Close() 104 | if err != nil { 105 | panic(err) 106 | } 107 | scanner := bufio.NewScanner(srcFile) 108 | // We use sipHash as our hash algorithm 109 | // 我们使用 sipHash 作为我们要用的 hash 算法 110 | h := siphash.New([]byte(baseKey)) 111 | 112 | // Create partFile 113 | // 创建小文件 114 | for i := 0; i < len(partFile); i++ { 115 | file, err := os.OpenFile(partPathPrefix+strconv.Itoa(i), os.O_CREATE|os.O_RDWR, 0777) 116 | partFile[i] = file 117 | if err != nil { 118 | panic(err) 119 | } 120 | } 121 | 122 | // Read SourceFile 123 | // 读取源文件(大文件) 124 | for scanner.Scan() { 125 | number := scanner.Text() 126 | num, err := strconv.ParseUint(number, 10, 64) 127 | if err != nil { 128 | panic(err) 129 | } 130 | 131 | // If the bit has been set,we skip it. 132 | // 如果 num 对应的 bit 位已经被设置则跳过(去重操作) 133 | if bm.GetBit(num) == 1 { 134 | continue 135 | } 136 | 137 | // set the bit to 1 138 | // 将 num 对应的 bit 置 1 139 | bm.SetBit(num) 140 | // Use number as hash key 141 | // 将 number 作为哈希用的 key 142 | _, err = h.Write([]byte(number)) 143 | if err != nil { 144 | panic(err) 145 | } 146 | // get hash 147 | // 获取读到的 number 对应的哈希值 148 | hash := h.Sum64() % NumPartFile 149 | h.Reset() // Reset hash key(重置 key) 150 | 151 | // Append number to the partFile corresponding to the hash 152 | // 将 number 追加写入到哈希值所对应的小文件上 153 | _, err = partFile[hash].WriteString(number + "\n") 154 | if err != nil { 155 | panic(err) 156 | } 157 | } 158 | } 159 | 160 | type Item struct { 161 | number uint64 162 | } 163 | 164 | type ItemHeap []Item 165 | 166 | // The following is the implementation of heap.Interface 167 | // 以下是对堆的接口的实现 168 | 169 | func (h ItemHeap) Len() int { return len(h) } 170 | 171 | func (h ItemHeap) Less(i, j int) bool { 172 | return h[i].number > h[j].number 173 | } 174 | func (h ItemHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 175 | 176 | func (h *ItemHeap) Push(val interface{}) { 177 | *h = append(*h, val.(Item)) 178 | } 179 | 180 | func (h *ItemHeap) Pop() interface{} { 181 | old := *h 182 | n := len(old) 183 | x := old[n-1] 184 | *h = old[0 : n-1] 185 | return x 186 | } 187 | 188 | var TopHeap = &ItemHeap{} 189 | 190 | // GetPartTop10 get the top 10 numbers in each partition files and push them into TopHeap 191 | // GetPartTop10 获取每个小文件中的 top 10,并把它们加入 TopHeap 中 192 | func GetPartTop10() { 193 | for i := 0; i < len(partFile); i++ { 194 | f := partFile[i] 195 | h := &ItemHeap{} 196 | // Reset offset to 0 (重置文件指针偏移量为0,即回到起始位置) 197 | _, err := f.Seek(0, 0) 198 | if err != nil { 199 | panic(err) 200 | } 201 | scanner := bufio.NewScanner(f) 202 | 203 | // Scan the current partition file to get the top 10 number 204 | // 扫描当前小文件,获取该文件中最大的10个数字 205 | for scanner.Scan() { 206 | number := scanner.Text() 207 | val, err := strconv.ParseUint(number, 10, 64) 208 | if err != nil { 209 | panic(err) 210 | } 211 | heap.Push(h, Item{number: val}) 212 | } 213 | 214 | for i := 0; i < 10; i++ { 215 | if h.Len() == 0 { 216 | break 217 | } 218 | val := heap.Pop(h) 219 | heap.Push(TopHeap, val) 220 | } 221 | } 222 | } 223 | 224 | // GetTop10 get the real top 10 and return the slice containing them 225 | // GetTop10 获取真正的 top 10,并返回包含它们的切片 226 | func GetTop10() []uint64 { 227 | var result []uint64 228 | 229 | // Use TopHeap to pop up the top 10 numbers 230 | // 使用 TopHeap 弹出 top 10 231 | for i := 0; i < 10; i++ { 232 | if TopHeap.Len() == 0 { 233 | break 234 | } 235 | raw := heap.Pop(TopHeap) 236 | item, ok := raw.(Item) 237 | if !ok { 238 | panic("type error") 239 | } 240 | val := item.number 241 | result = append(result, val) 242 | } 243 | return result 244 | } 245 | 246 | func RemoveAndClosePartFile() { 247 | for i := 0; i < len(partFile); i++ { 248 | partFile[i].Close() 249 | err := os.Remove(partPathPrefix + strconv.Itoa(i)) 250 | if err != nil { 251 | panic(err) 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /TopKProblem/FindTop10Numbers/answer/FindTop10Numbers_test.go: -------------------------------------------------------------------------------- 1 | package FindTop10NumbersAnswer 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // This is not actually a test, but a reference to the implementation steps 9 | // 这并不是测试,而是实现步骤的参考 10 | func TestFindTop10Numbers(t *testing.T) { 11 | fmt.Println("---Find Top 10 Numbers---") 12 | 13 | Row := uint64(1000000) 14 | GenerateBigFile(Row) 15 | fmt.Println("Process: GenerateBigFile is completed.") 16 | 17 | // Step 1: split source file to each partition file 18 | // 第一步:分而治之 19 | defer RemoveAndClosePartFile() 20 | SplitBigFile(NumPartFile) 21 | fmt.Println("Process: SplitBigFile is completed.") 22 | 23 | // Step 2: get the top 10 numbers in each partition file, 24 | // the numbers will be saved to the 'TopHeap'. 25 | // 第二步:获取每个小文件中最大的十个数字,这些数字保存到 TopHeap 堆(优先队列)中 26 | GetPartTop10() 27 | fmt.Println("Process: GetPartTop10 is completed.") 28 | 29 | // Step 3: use TopHeap to get the Top 10 numbers 30 | // 第三步:最后使用 TopHeap 获取最大的十个数字 31 | res := GetTop10() 32 | fmt.Println("Process: GetTop10 is completed.") 33 | fmt.Println("Top10 Numbers:") 34 | for i := 0; i < len(res); i++ { 35 | fmt.Print("NO.", i+1, res[i], "\n") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /TopKProblem/FindTop10Numbers/lab/FindTop10Numbers_lab.go: -------------------------------------------------------------------------------- 1 | package FindTop10NumbersLab 2 | 3 | import ( 4 | "bufio" 5 | "math/rand" 6 | "os" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | const ( 12 | maxLimit = 100000000 13 | ) 14 | 15 | var ( 16 | bm bitmap 17 | NumPartFile = uint64(100) 18 | partFile = make([]*os.File, NumPartFile) 19 | srcPath = "SourceFile.txt" 20 | partPathPrefix = "partFile" 21 | ) 22 | 23 | func GenerateBigFile(Row uint64) { 24 | // First: clear file (if it exists) 25 | // 首先清空文件内容(如果文件存在的话) 26 | err := os.Truncate(srcPath, 0) 27 | if err != nil { 28 | panic(err) 29 | } 30 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 31 | defer f.Close() 32 | if err != nil { 33 | panic(err) 34 | } 35 | // Generate the big file 36 | // 生成大文件 37 | for i := uint64(0); i < Row; i++ { 38 | rand.Seed(time.Now().UnixNano()) 39 | 40 | // We control range to ensure that bitmap memory does not exceed the limit on most computers 41 | // 我们控制范围是为了保证 bitmap 占用的内存在大多数电脑下不会超出内存限制 42 | val := rand.Uint64() % maxLimit 43 | str := strconv.FormatUint(val, 10) 44 | _, err := f.WriteString(str + "\n") 45 | if err != nil { 46 | panic(err) 47 | } 48 | } 49 | } 50 | 51 | // GenerateBigFileForTest used in lab testing 52 | func GenerateBigFileForTest(Row, MinInTop10 uint64, Top10Numbers []uint64) { 53 | err := os.Truncate(srcPath, 0) 54 | if err != nil { 55 | panic(err) 56 | } 57 | f, err := os.OpenFile(srcPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) 58 | defer f.Close() 59 | if err != nil { 60 | panic(err) 61 | } 62 | for i := 0; i < len(Top10Numbers); i++ { 63 | str := strconv.FormatUint(Top10Numbers[i], 10) 64 | _, err := f.WriteString(str + "\n") 65 | if err != nil { 66 | panic(err) 67 | } 68 | } 69 | 70 | for i := uint64(0); i < Row; i++ { 71 | rand.Seed(time.Now().UnixNano()) 72 | val := rand.Uint64() % MinInTop10 73 | str := strconv.FormatUint(val, 10) 74 | _, err := f.WriteString(str + "\n") 75 | if err != nil { 76 | panic(err) 77 | } 78 | } 79 | } 80 | 81 | type bitmap struct { 82 | bitmap []byte 83 | } 84 | 85 | // grow is used for grow bitmaps' size 86 | // grow 用于扩展 bitmap 的空间 87 | func (b *bitmap) grow(size uint64) { 88 | if size < uint64(cap(b.bitmap)) { 89 | // No need to grow capacity 90 | // 无需扩大容量 91 | return 92 | } 93 | old := b.bitmap 94 | New := make([]byte, size+1) 95 | copy(New, old) 96 | b.bitmap = New 97 | } 98 | 99 | // SetBit set the bitPos bit to 1 100 | // SetBit 将第 bitPos 位设置为 1 101 | func (b *bitmap) SetBit(bitPos uint64) { 102 | bytePos := bitPos / 8 103 | if bytePos < uint64(cap(b.bitmap)) { 104 | b.bitmap[bytePos] |= 1 << ((bitPos) % 8) 105 | } else { 106 | b.grow(bytePos) 107 | b.bitmap[bytePos] |= 1 << ((bitPos) % 8) 108 | } 109 | } 110 | 111 | // GetBit get the value at bitPos (0 or 1) 112 | // GetBit 获取第 bitPos 位的值(0或1) 113 | func (b *bitmap) GetBit(bitPos uint64) int { 114 | bytePos := bitPos / 8 115 | if bytePos >= uint64(cap(b.bitmap)) { 116 | return 0 117 | } else { 118 | bit := b.bitmap[bytePos] & (1 << ((bitPos) % 8)) 119 | if bit != 0 { 120 | return 1 121 | } 122 | return 0 123 | } 124 | } 125 | 126 | // Task 1: complete the SplitBigFile function 127 | // 任务 1: 完善 SplitBigFile 函数 128 | 129 | // SplitBigFile split the source file into partition files 130 | // SplitBigFile 将源文件分割到多个小文件中 131 | func SplitBigFile(NumPartFile uint64) { 132 | srcFile, err := os.Open(srcPath) 133 | defer srcFile.Close() 134 | if err != nil { 135 | panic(err) 136 | } 137 | scanner := bufio.NewScanner(srcFile) 138 | 139 | // Create partFile 140 | // 创建小文件 141 | for i := 0; i < len(partFile); i++ { 142 | file, err := os.OpenFile(partPathPrefix+strconv.Itoa(i), os.O_CREATE|os.O_RDWR, 0777) 143 | partFile[i] = file 144 | if err != nil { 145 | panic(err) 146 | } 147 | } 148 | 149 | // Read SourceFile 150 | // 读取源文件(大文件) 151 | for scanner.Scan() { 152 | // When duplicate numbers are read, you can use bitmap or hash table to remove duplicates 153 | // 当读取到重复数字时,你可以借助 bitmap 或者 哈希表来去重(已提供 bitmap 的基本功能函数) 154 | 155 | } 156 | } 157 | 158 | type Item struct { 159 | number uint64 160 | } 161 | 162 | type ItemHeap []Item 163 | 164 | // The following is the implementation of heap.Interface 165 | // 以下是对堆的接口的实现,已保证是大根堆(Pop 出的是最大值) 166 | 167 | func (h ItemHeap) Len() int { return len(h) } 168 | 169 | func (h ItemHeap) Less(i, j int) bool { 170 | return h[i].number > h[j].number 171 | } 172 | func (h ItemHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 173 | 174 | func (h *ItemHeap) Push(val interface{}) { 175 | *h = append(*h, val.(Item)) 176 | } 177 | 178 | func (h *ItemHeap) Pop() interface{} { 179 | old := *h 180 | n := len(old) 181 | x := old[n-1] 182 | *h = old[0 : n-1] 183 | return x 184 | } 185 | 186 | var TopHeap = &ItemHeap{} 187 | 188 | // Task 2: complete the GetPartTop10 function 189 | // 任务 2: 完善 GetPartTop10 函数 190 | 191 | // GetPartTop10 get the top 10 numbers in each partition files and push them into TopHeap 192 | // GetPartTop10 获取每个小文件中的 top 10,并把它们加入 TopHeap 中(你也可以使用别的方式来排序) 193 | func GetPartTop10() { 194 | for i := 0; i < len(partFile); i++ { 195 | f := partFile[i] 196 | // Reset offset to 0 (重置文件指针偏移量为0,即回到起始位置) 197 | _, err := f.Seek(0, 0) 198 | if err != nil { 199 | panic(err) 200 | } 201 | 202 | } 203 | } 204 | 205 | // Task 3: complete the GetTop10 function 206 | // 任务 3: 完善 GetTop10 函数 207 | 208 | // GetTop10 get the real top 10 and return the slice containing them 209 | // GetTop10 获取真正的 top 10,并返回包含它们的切片 210 | func GetTop10() []uint64 { 211 | 212 | } 213 | 214 | func RemoveAndClosePartFile() { 215 | for i := 0; i < len(partFile); i++ { 216 | partFile[i].Close() 217 | err := os.Remove(partPathPrefix + strconv.Itoa(i)) 218 | if err != nil { 219 | panic(err) 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /TopKProblem/FindTop10Numbers/lab/FindTop10Numbers_lab_test.go: -------------------------------------------------------------------------------- 1 | package FindTop10NumbersLab 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestFindTop10Numbers(t *testing.T) { 11 | fmt.Println("---Lab Test: Find Top 10 Numbers---") 12 | Row := uint64(100000) 13 | Top10Numbers := make([]uint64, 10) 14 | MinInTop10 := uint64(maxLimit) 15 | 16 | // Generate Top 10 numbers 17 | // 生成 top 10 18 | for i := 0; i < 10; i++ { 19 | rand.Seed(time.Now().UnixNano()) 20 | val := rand.Uint64() % MinInTop10 21 | if val < 10 { 22 | val += 10 23 | } 24 | flag := true 25 | for j := 0; j < i; j++ { 26 | if Top10Numbers[j] == val { 27 | flag = false 28 | break 29 | } 30 | } 31 | if flag == false { 32 | i-- // Regenerate Top10Numbers[i] 33 | continue 34 | } 35 | if val < MinInTop10 { 36 | MinInTop10 = val 37 | Top10Numbers[i] = val 38 | } 39 | } 40 | 41 | GenerateBigFileForTest(Row, MinInTop10, Top10Numbers) 42 | fmt.Println("Process: GenerateBigFile is completed.") 43 | 44 | // Step 1: split source file to each partition file 45 | // 第一步:分而治之 46 | defer RemoveAndClosePartFile() 47 | SplitBigFile(NumPartFile) 48 | fmt.Println("Process: SplitBigFile is completed.") 49 | 50 | // Step 2: get the top 10 numbers in each partition file, 51 | // the numbers will be saved to the 'TopHeap'. 52 | // 第二步:获取每个小文件中最大的十个数字,这些数字保存到 TopHeap 堆(优先队列)中 53 | GetPartTop10() 54 | fmt.Println("Process: GetPartTop10 is completed.") 55 | 56 | // Step 3: use TopHeap to get the Top 10 numbers 57 | // 第三步:最后使用 TopHeap 获取最大的十个数字 58 | result := GetTop10() 59 | fmt.Println("Process: GetTop10 is completed.") 60 | fmt.Println("Top10 Numbers:") 61 | for i := 0; i < len(result); i++ { 62 | if result[i] != Top10Numbers[i] { 63 | t.Errorf("expected result: %v ,actual: %v", Top10Numbers[i], result[i]) 64 | } 65 | fmt.Print("NO.", i+1, result[i], "\n") 66 | } 67 | 68 | fmt.Println("---Congratulations: your answer is correct!---") 69 | } 70 | -------------------------------------------------------------------------------- /TopKProblem/FindTop10Numbers/lab/README.md: -------------------------------------------------------------------------------- 1 | ## lab 题目:如何在海量数据中找出最大的 10 个数 2 | ### 思路解析: 3 | Step 1:如果在海量数据中有很多重复的数,借助哈希表或者 bitmap 把数字去重,如果重复率很高的话能减少很大的内存用量(对于哈希表)和分割成的小文件的存储用量。
4 | ps: 在基本的映射方式下 bitmap 占用内存与海量数据中最大的数字有关,在本次 lab 中我们控制了数据范围在0到1亿之间,令大多数的现代电脑都可以使用 bitmap 完成本次 lab。
5 | Step 2:将海量数据通过哈希映射分到 100 份小文件中。
6 | Step 3:统计每个小文件中最大的10个数。
7 | Step 4:最后使用堆或者其他方式,将所有小文件的 top 10 进行排序以获取最大的10个数。 8 | 9 | ### 任务说明: 10 | **Task 1: 完善 SplitBigFile 函数**
11 | SplitBigFile 函数实现将源文件(大文件)分割成多个小文件,也就是我们要做的第一步:分而治之。
12 | 在这里我们一边读取数据,一边借助 哈希表或者 bitmap 检测数据是否重复。推荐大家将两种写法都进行尝试。🍭🍭
13 | **Task 2: 完善 GetPartTop10 函数**
14 | GetPartTop10 函数获取每个小文件中的 top 10,并把它们加入 TopHeap 中(你也可以使用别的方式来排序)。
15 | **Task 3: 完善 GetTop10 函数**
16 | GetTop10 函数获取真正的 top 10,并返回包含它们的切片。 17 | 18 | **测试说明**
19 | 当你完成了以上的函数之后,在该 lab 文件夹下执行`go test`即可进行 lab 测试!祝贺您一次通过哦!另外,鼓励大家使用不一样的方法实现 lab。 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ncghost1/MassDataProcessingDemo 2 | 3 | go 1.18 4 | 5 | require github.com/dchest/siphash v1.2.3 // indirect 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= 2 | github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= 3 | --------------------------------------------------------------------------------