├── 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 |
--------------------------------------------------------------------------------