diff --git a/cmd/srv/main.go b/cmd/srv/main.go index 8fecfab..11d3531 100644 --- a/cmd/srv/main.go +++ b/cmd/srv/main.go @@ -19,6 +19,8 @@ import ( _ "github.com/hhhapz/codequest/question/q01" _ "github.com/hhhapz/codequest/question/q02" _ "github.com/hhhapz/codequest/question/q03" + _ "github.com/hhhapz/codequest/question/q04" + _ "github.com/hhhapz/codequest/question/q05" ) func run() error { diff --git a/question/q02/q02.go b/question/q02/q02.go index 9fbd449..d380af7 100644 --- a/question/q02/q02.go +++ b/question/q02/q02.go @@ -13,7 +13,7 @@ import ( ) func init() { - t := template.New("saturnalia") + t := template.New("summer") var err error t, err = t.Parse(q02Text) if err != nil { @@ -21,8 +21,8 @@ func init() { } question.Register( &question.Question{ - ID: "saturnalia", - Name: "Saturnalia's Problem", + ID: "summer", + Name: "Summer Scheduling", Text: t, Level: question.Level1, Generate: func(u *models.User) string { diff --git a/question/q02/q02_test.go b/question/q02/q02_test.go index b1a2fc3..82467b4 100644 --- a/question/q02/q02_test.go +++ b/question/q02/q02_test.go @@ -14,7 +14,7 @@ func TestQ02(t *testing.T) { ID: "1203120957198056", } - q := question.QuestionByID("saturnalia") + q := question.QuestionByID("summer") raw := q.Generate(u) var input []int diff --git a/question/q03/q03.md b/question/q03/q03.md index 6ac098a..b6a98a0 100644 --- a/question/q03/q03.md +++ b/question/q03/q03.md @@ -75,12 +75,8 @@ Using this information, the maximums of the rows and columns is `25` ad `20`. The answer, and the dragon number, is `25*20 = 500`. ---- - **What is the dragon number for your lake?** -### Example - {{ if .Part2.Completed -}} **Congratulations! You have completed both parts! The answer was `{{ .Part2.Solution }}`.** diff --git a/question/q04/part1.go b/question/q04/part1.go new file mode 100644 index 0000000..dccc390 --- /dev/null +++ b/question/q04/part1.go @@ -0,0 +1,23 @@ +package q04 + +func waterCorners(grid [][]rune, x, y int) int { + isWater := func(x, y int) int { + if x < 0 || y < 0 || x >= len(grid) || y >= len(grid[x]) || grid[x][y] == ' ' { + return 1 + } + return 0 + } + return isWater(x-1, y) + isWater(x+1, y) + isWater(x, y-1) + isWater(x, y+1) +} + +func solveP1(grid [][]rune) int { + var ps int + for x := range grid { + for y := range grid[x] { + if waterCorners(grid, x, y) == 3 { + ps++ + } + } + } + return ps +} diff --git a/question/q04/part2.go b/question/q04/part2.go new file mode 100644 index 0000000..dcb25b5 --- /dev/null +++ b/question/q04/part2.go @@ -0,0 +1,37 @@ +package q04 + +func check(x, y, n, m int) bool { + return x >= 0 && y >= 0 && x < n && y < m +} + +var dirs = [][]int{{-1, 0}, {0, -1}, {1, 0}, {0, 1}} + +func dfs(grid [][]rune, vis [][]bool, x, y, n, m int) { + if !check(x, y, n, m) || grid[x][y] == ' ' || vis[x][y] { + return + } + vis[x][y] = true + for _, d := range dirs { + x1, y1 := x+d[0], y+d[1] + dfs(grid, vis, x1, y1, n, m) + } +} + +func solveP2(grid [][]rune) int { + var res int + n := len(grid) + m := len(grid[0]) + vis := make([][]bool, n) + for i := range vis { + vis[i] = make([]bool, m) + } + for i := range grid { + for j := range grid[i] { + if !vis[i][j] && grid[i][j] == 'X' { + res++ + dfs(grid, vis, i, j, n, m) + } + } + } + return res +} diff --git a/question/q04/q04.go b/question/q04/q04.go new file mode 100644 index 0000000..aeb001a --- /dev/null +++ b/question/q04/q04.go @@ -0,0 +1,108 @@ +package q04 + +import ( + _ "embed" + "fmt" + "math/rand" + "strconv" + "strings" + "text/template" + + "github.com/hhhapz/codequest/models" + "github.com/hhhapz/codequest/question" +) + +type boolgen struct { + src *rand.Rand + cache int64 + remaining int +} + +func (b *boolgen) Bool() bool { + if b.remaining == 0 { + b.cache, b.remaining = b.src.Int63(), 63 + } + + result := b.cache&0x01 == 1 + b.cache >>= 1 + b.remaining-- + + return result +} + +func init() { + t := template.New("island") + var err error + t, err = t.Parse(q03Text) + if err != nil { + panic(err) + } + question.Register( + &question.Question{ + ID: "island", + Name: "Island Analysis", + Text: t, + Level: question.Level2, + Generate: func(u *models.User) string { + inp := generate(u) + res := make([]string, 0, rows) + + for _, row := range inp { + res = append(res, string(row)) + } + return strings.Join(res, "\n") + }, + Validate: func(u *models.User, part question.Part, solution string) bool { + return Validate(u, part, solution) + }, + }) +} + +const ( + rows = 90 + cols = 50 +) + +func generate(u *models.User) [][]rune { + res := make([][]rune, rows) + + b := boolgen{ + src: question.UserRandom(u), + } + + for i := 0; i < rows; i++ { + res[i] = make([]rune, cols) + for j := 0; j < cols; j++ { + m := 'X' + if b.Bool() { + m = ' ' + } + res[i][j] = m + } + } + + return res +} + +func Validate(u *models.User, p question.Part, sol string) bool { + inp := generate(u) + + var n int + switch p { + case question.Part1: + n = solveP1(inp) + case question.Part2: + n = solveP2(inp) + + default: + return false + } + + fmt.Println("submitted", u.Name, p, sol) + fmt.Println("actual", u.Name, p, n) + + return strconv.Itoa(n) == sol +} + +//go:embed q04.md +var q03Text string diff --git a/question/q04/q04.md b/question/q04/q04.md new file mode 100644 index 0000000..b046d52 --- /dev/null +++ b/question/q04/q04.md @@ -0,0 +1,107 @@ +You decide to visit the archipelagos of Indonesia. You find a map of the area near South Sulawesi, +and can see where there is land (marked with an `X`), and where the ocean is (empty space). + +You and your friends decide you want to stay at the islands for the weekend. + +However, there are so many islands to pick from, and you have no way to decide which one to pick to +stay at. To make a decision, you come up with a plan. + +Firstly, you have decided that the location needs to be a peninsula. That means, water on 3 sides, +and land on one side. + +### Note: + +> When counting peninsulas, do not consider diagonal tiles. Only consider those directly to the left, +> right, above and below the tile. + +> The (invisible) edges of the input all count after the water tiles. + +### Example + +Given the following input: + +``` + ABCDE +1. X XX +2. X X +3. X +4. XX X +``` + +Contains 4 peninsulas: + +* 1D +* 3D +* 4A +* 4B + +--- + +Given the following input: + +``` + ABCDEFGH +1 XXX XX +2 X XX X +3 XXXXX X +4 X X +5 X XXXX +6 XX XXXX + +``` + +Contains 9 peninsulas: + +* 1C +* 1H +* 2B +* 3A +* 4D +* 5A +* 5H +* 6B +* 6D + +To get the answer for part one, **what is the total number of peninsulas you can count on your +map?** + +{{ if .Part1.Completed -}} + +**Congratulations! You got Part 1 correct. Your answer was `{{ .Part1.Solution }}`.** + +## Part 2 + +With the number of peninsulas that are in the lake, you are having trouble determining which one to +pick. In order to do so, you have devised a plan to try to see which island has the most number of +peninsulas. + +In order to do this, though, we first need to count how many islands we have. + +An island is a contiguous set of grids, that are connected by water in at least one of the four +directions, similar to a peninsula. + +### Example + +Given the following input: + +``` + ABCDEFGH +1 XXX XX +2 X XX X +3 XXXXX X +4 X X +5 X XXXX +6 XX XXXX + +``` + +There are 3 distinct islands. + +**What is the total number of islands in your map?** + +{{ if .Part2.Completed -}} + +**Congratulations! You have completed both parts! The answer was `{{ .Part2.Solution }}`.** + +{{- end }} +{{- end }} diff --git a/question/q04/q04_test.go b/question/q04/q04_test.go new file mode 100644 index 0000000..490a2c8 --- /dev/null +++ b/question/q04/q04_test.go @@ -0,0 +1,43 @@ +package q04 + +import ( + "strconv" + "strings" + "testing" + + "github.com/hhhapz/codequest/models" + "github.com/hhhapz/codequest/question" +) + +func TestQ04(t *testing.T) { + u := &models.User{ + ID: "123", + } + + q := question.QuestionByID("island") + + raw := q.Generate(u) + + t.Logf("INPUT:\n\n%s\n\n", raw) + + input := make([][]rune, 0, rows) + + for _, row := range strings.Split(raw, "\n") { + input = append(input, []rune(row)) + } + + // res := solveP1(input) + // t.Logf("part 1 result: %d", res) + // if !q.Validate(u, question.Part1, strconv.Itoa(res)) { + // t.Errorf("Expected question 1 part 1(%v) to be correct!", res) + // } + + res := solveP2(input) + if !q.Validate(u, question.Part2, strconv.Itoa(res)) { + t.Errorf("Expected question 2 part 2(%v) to be correct!", res) + } + + if q.Validate(u, question.Part1, "") { + t.Errorf("Expected bad input to be invalid") + } +} diff --git a/question/q05/part1.go b/question/q05/part1.go new file mode 100644 index 0000000..9eb0c97 --- /dev/null +++ b/question/q05/part1.go @@ -0,0 +1,9 @@ +package q05 + +func solveP1(nums []int) int { + var sum int + for _, v := range nums { + sum += int(float64(v)/2.5 - 2) + } + return sum +} diff --git a/question/q05/part2.go b/question/q05/part2.go new file mode 100644 index 0000000..acdf8a3 --- /dev/null +++ b/question/q05/part2.go @@ -0,0 +1,13 @@ +package q05 + +func solveP2(nums []int) int { + var sum int + for _, v := range nums { + fuel := int(float64(v)/2.5 - 2) + for fuel > 0 { + sum += fuel + fuel = int(float64(fuel)/2.5 - 2) + } + } + return sum +} diff --git a/question/q05/q05.go b/question/q05/q05.go new file mode 100644 index 0000000..de793c0 --- /dev/null +++ b/question/q05/q05.go @@ -0,0 +1,81 @@ +package q05 + +import ( + _ "embed" + "fmt" + "strconv" + "strings" + "text/template" + + "github.com/hhhapz/codequest/models" + "github.com/hhhapz/codequest/question" +) + +func init() { + t := template.New("fuel") + var err error + t, err = t.Parse(q05Text) + if err != nil { + panic(err) + } + question.Register( + &question.Question{ + ID: "fuel", + Name: "Fuel for the Farlands", + Text: t, + Level: question.Level1, + Generate: func(u *models.User) string { + inp := generate(u) + nums := make([]string, nums) + for i, num := range inp { + nums[i] = strconv.Itoa(num) + } + return strings.Join(nums, "\n") + }, + Validate: func(u *models.User, part question.Part, solution string) bool { + return Validate(u, part, solution) + }, + }) +} + +const ( + nums = 350 +) + +func generate(u *models.User) []int { + res := make([]int, nums) + + r := question.UserRandom(u) + + for i := 0; i < nums; i++ { + if nums < 10 { + res[i] = r.Intn(10000-200) + 200 + continue + } + res[i] = r.Intn(1000000-1000) + 1000 + } + + return res +} + +func Validate(u *models.User, p question.Part, sol string) bool { + inp := generate(u) + + var n int + switch p { + case question.Part1: + n = solveP1(inp) + case question.Part2: + n = solveP2(inp) + + default: + return false + } + + fmt.Println("submitted", u.Name, p, sol) + fmt.Println("actual", u.Name, p, n) + return strconv.Itoa(n) == sol +} + +//go:embed q05.md +var q05Text string diff --git a/question/q05/q05.md b/question/q05/q05.md new file mode 100644 index 0000000..28a41b3 --- /dev/null +++ b/question/q05/q05.md @@ -0,0 +1,68 @@ +The time has arrived and you need to pack things up and say goodbye. You are leaving for the +Farlands, and it's.. far! + +You plan to fly solo over the continents to make it to the Farlands. This is the first time you've +ever flown for so long, and so you need to prepare a few things before leaving. + +The first order of business is to determine how much fuel you're going to need to reach. To do this, +you need the empty mass of your aircraft. + +With the mass, you can calculate the total required fuel. + +For each each mass, the amount of fuel can be calculated using this **formula**. Take the mass, +divide it by 2.5, round down the result, and then subtract 2. + +Before taking off, your friends and family have sent you hundreds of gifts, which you plan to take +with you. You'll need to know the total amount of fuel needed for your trip. + +Apply the formula for each item (your input), and sum them to get the total requirement. + +### Example + +For a mass of `18`, divide it by `2.5` and round down to get `7`, then subtract `2` to get `5` as +the fuel requirement. + +For a mass of `58`, divide it by `2.5` and round down to get `23`, then subtract `2` to get `21`. + +For `2022`, the requirement is `806` + +For `84942`, the requirement is `33974` + +**What is the total fuel requirement for your trip?** + +{{ if .Part1.Completed -}} + +**Congratulations! You got Part 1 correct. Your answer was `{{ .Part1.Solution }}`.** + +## Part 2 + +Just before you about to take off, your copilot reminded you that you completely forgot to take into +account the extra fuel needed for the fuel you already have! Just like your parting gifts, apply the +same **formula** on the fuel to obtain the fuel needed for the fuel. + +So, for each gift, calculate the fuel needed, treat that fuel as the mass itself, and calculate the +fuel needed, and repeat this process **until** your fuel requirement reaches **zero or a negative +value**. + +### Example + +For a mass of `18`, divide it by `2.5` and round down to get `7`, then subtract `2` to get `5` as +the fuel requirement. `5` divided by `2.5` gives you `1`, and subtracting `2` gives you `-1` so the +total fuel required is still `5`. + +For a mass of `58`, `21` fuel is used. Then, `21` needs `6` more fuel, and `6` requires no further +fuel. The total fuel needed is `21+6 = 27` + +For `2022`, the requirement is `806+320+126+48+17+4 = 1321` + +Using this new formula, **what is the fuel total fuel requirements** for all the mass, but also +including the mass of the newly added fuel? + +> Calculate the requirements seperately, and them sum them together for each item. + +{{ if .Part2.Completed -}} + +**Congratulations! You have completed both parts! The answer was `{{ .Part2.Solution }}`.** + +{{- end }} +{{- end }} diff --git a/question/q05/q05_test.go b/question/q05/q05_test.go new file mode 100644 index 0000000..b588ece --- /dev/null +++ b/question/q05/q05_test.go @@ -0,0 +1,44 @@ +package q05 + +import ( + "strconv" + "strings" + "testing" + + "github.com/hhhapz/codequest/models" + "github.com/hhhapz/codequest/question" +) + +func TestQ05(t *testing.T) { + u := &models.User{ + ID: "123", + } + + q := question.QuestionByID("fuel") + + raw := q.Generate(u) + + t.Logf("INPUT:\n\n%s\n\n", raw) + + input := make([]int, 0, nums) + + for _, num := range strings.Split(raw, "\n") { + n, _ := strconv.Atoi(num) + input = append(input, n) + } + + res := solveP1(input) + t.Logf("part 1 result: %d", res) + if !q.Validate(u, question.Part1, strconv.Itoa(res)) { + t.Errorf("Expected question 1 part 1(%v) to be correct!", res) + } + + res = solveP2(input) + if !q.Validate(u, question.Part2, strconv.Itoa(res)) { + t.Errorf("Expected question 2 part 2(%v) to be correct!", res) + } + + if q.Validate(u, question.Part1, "") { + t.Errorf("Expected bad input to be invalid") + } +} diff --git a/question/question.go b/question/question.go index 48b346a..68cb811 100644 --- a/question/question.go +++ b/question/question.go @@ -47,7 +47,6 @@ const ( Part2 ) -// TODO: Internal autoincrement id func UserRandom(u *models.User) *rand.Rand { id := u.ID if len(id) > 17 { diff --git a/web_src/lib/components/QuestionListing.svelte b/web_src/lib/components/QuestionListing.svelte index 53099f1..c6cce9c 100644 --- a/web_src/lib/components/QuestionListing.svelte +++ b/web_src/lib/components/QuestionListing.svelte @@ -1,5 +1,5 @@