From 8173ecdd8cc7d0b87c8d59895226f853d3f0f844 Mon Sep 17 00:00:00 2001 From: Ivan Puzyrevskiy Date: Mon, 24 Feb 2020 22:54:48 +0300 Subject: [PATCH] Add 5 more exercises --- hogwarts/README.md | 25 +++++++++ hogwarts/courselist.go | 7 +++ hogwarts/courselist_test.go | 89 ++++++++++++++++++++++++++++++ hotelbusiness/README.md | 41 ++++++++++++++ hotelbusiness/hotels.go | 17 ++++++ hotelbusiness/hotels_test.go | 75 +++++++++++++++++++++++++ utf8reverse/README.md | 16 ++++++ utf8reverse/reverse.go | 7 +++ utf8reverse/reverse_test.go | 44 +++++++++++++++ utf8reverse/solution.go | 24 ++++++++ utf8spacecollapse/README.md | 13 +++++ utf8spacecollapse/collapse.go | 7 +++ utf8spacecollapse/collapse_test.go | 29 ++++++++++ utf8spacecollapse/solution.go | 30 ++++++++++ varjoin/README.md | 54 ++++++++++++++++++ varjoin/join.go | 7 +++ varjoin/join_test.go | 35 ++++++++++++ 17 files changed, 520 insertions(+) create mode 100644 hogwarts/README.md create mode 100644 hogwarts/courselist.go create mode 100644 hogwarts/courselist_test.go create mode 100644 hotelbusiness/README.md create mode 100644 hotelbusiness/hotels.go create mode 100644 hotelbusiness/hotels_test.go create mode 100644 utf8reverse/README.md create mode 100644 utf8reverse/reverse.go create mode 100644 utf8reverse/reverse_test.go create mode 100644 utf8reverse/solution.go create mode 100644 utf8spacecollapse/README.md create mode 100644 utf8spacecollapse/collapse.go create mode 100644 utf8spacecollapse/collapse_test.go create mode 100644 utf8spacecollapse/solution.go create mode 100644 varjoin/README.md create mode 100644 varjoin/join.go create mode 100644 varjoin/join_test.go diff --git a/hogwarts/README.md b/hogwarts/README.md new file mode 100644 index 0000000..664e224 --- /dev/null +++ b/hogwarts/README.md @@ -0,0 +1,25 @@ +# hogwarts + +Учебному офису школы чародейства и волшебства "Хогвартс". + +В связи с назначением меня -- Долорес Амбридж -- директором вашей (ха-ха!) школы +прошу подготовить учебный план для начинающих чародеев первого года. Высылаю +список курсов и их пререквизитов. + +Надеюсь, вы меня не подведете. Иначе будете объясняться будете с самим Министерством! + +## Технические детали + +Реализуйте функцию `GetCourseList`. + +На вход дан словарь пререквезитов для каждого курса. На выходе нужно вернуть список курсов +в порядке прохождения с учётом пререквизитов. + +Если есть циклическая зависимость между курсами -- паникуйте! + +### Примеры + +Как запустить все тесты: +``` +go test -v ./hogwarts/... +``` diff --git a/hogwarts/courselist.go b/hogwarts/courselist.go new file mode 100644 index 0000000..f96143b --- /dev/null +++ b/hogwarts/courselist.go @@ -0,0 +1,7 @@ +// +build !solution + +package hogwarts + +func GetCourseList(prereqs map[string][]string) []string { + return []string{} +} diff --git a/hogwarts/courselist_test.go b/hogwarts/courselist_test.go new file mode 100644 index 0000000..2cecbf4 --- /dev/null +++ b/hogwarts/courselist_test.go @@ -0,0 +1,89 @@ +package hogwarts + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func impl(t *testing.T, prereqs map[string][]string, courseList []string) { + learned := make(map[string]bool) + for index, course := range courseList { + for _, prereq := range prereqs[course] { + if !learned[prereq] { + t.Errorf("course %v (index %v) depends on course %v which is not taken yet", course, index, prereq) + } + } + learned[course] = true + } + for course := range prereqs { + if !learned[course] { + t.Errorf("course %v is missing on the list", course) + } + } +} + +func TestGetCourseList_computerScience(t *testing.T) { + var computerScience = map[string][]string{ + "algorithms": {"data structures"}, + "calculus": {"linear algebra"}, + "compilers": { + "data structures", + "formal languages", + "computer organization", + }, + "data structures": {"discrete math"}, + "databases": {"data structures"}, + "discrete math": {"intro to programming"}, + "formal languages": {"discrete math"}, + "networks": {"operating systems"}, + "operating systems": {"data structures", "computer organization"}, + "programming languages": {"data structures", "computer organization"}, + } + impl(t, computerScience, GetCourseList(computerScience)) +} + +func TestGetCourseList_linearScience(t *testing.T) { + var linearScience = map[string][]string{ + "1": {"0"}, + "2": {"1"}, + "3": {"2"}, + "4": {"3"}, + "5": {"4"}, + "6": {"5"}, + "7": {"6"}, + "8": {"7"}, + "9": {"8"}, + } + impl(t, linearScience, GetCourseList(linearScience)) +} + +func TestGetCourseList_naiveScience(t *testing.T) { + var naiveScience = map[string][]string{ + "здравый смысл": {}, + "русский язык": {"здравый смысл"}, + "литература": {"здравый смысл"}, + "иностранный язык": {"здравый смысл"}, + "алгебра": {"здравый смысл"}, + "геометрия": {"здравый смысл"}, + "информатика": {"здравый смысл"}, + "история": {"здравый смысл"}, + "обществознание": {"здравый смысл"}, + "география": {"здравый смысл"}, + "биология": {"здравый смысл"}, + "физика": {"здравый смысл"}, + "химия": {"здравый смысл"}, + "музыка": {"здравый смысл"}, + } + impl(t, naiveScience, GetCourseList(naiveScience)) +} + +func TestGetCourseList_weirdScience(t *testing.T) { + var weirdScience = map[string][]string{ + "купи": {"продай"}, + "продай": {"купи"}, + } + require.Panics(t, func() { + impl(t, weirdScience, GetCourseList(weirdScience)) + }) +} diff --git a/hotelbusiness/README.md b/hotelbusiness/README.md new file mode 100644 index 0000000..5c5caeb --- /dev/null +++ b/hotelbusiness/README.md @@ -0,0 +1,41 @@ +# hotelbusiness + +Здравствуйте, Дональд Крямп! + +Ваша империя ширится, и теперь в вашем владении премиальный курорт "Koza Hutor: Resort, Spa, Golang 5*". +Поздравляем с успешным приобретением! + +Правда, есть нюанс. + +Ваш твиттер настолько заряжен энергей на успех и богатство, что предыдущий управляющий курорта сбежал, +едва узнав, что вы собственной персоной планируете посетить курорт! + +Ваш верный comrade Эдвард Санден немного разобрался в IT-системах курорта и выяснил, +что курорт интегрирован в международный сервис "Gluking.com" и что к вам уже вот-вот нагрянут гости! + +Эдварду удалось выгрузить информацио о заездах и выездах будущих гостей -- см. структуру `hotelbusiness.Guest`. +По техническим причинам все даты заменены на цифры. Эдвард говорит, что ноль -- значит сегодня. + +Менеджер по работе с гостями (его зовут Валентин) утверждает, сбежавший управляющий (его имя еще не установлено) +всегда передавал ему информацию о гостях в другом виде. А именно -- см. структуру `hotelbusiness.Load` -- +указание, с какой даты сколько гостей ожидается на курорте. + +В таком виде Валентин может рассчитать необходмые закупки продуктов, график работы персонала и другие мелочи, +чтобы курорт функционировал в штатном режиме. + +Эдвард бы рад рассчитать данные для Валентина в требуемом виде, но, говорит, интернет в Шереметьево так себе. + +Давайте покажем всем завистникам вашу мощь и талант! Напишите функцию `hotelbusiness.ComputeLoad`, +покажите всему твиттеру, что еще есть порох в пороховицах! + +### Технические уточнения от Валентина + +* Если в один день гости и выезжают, и заезжают, то Валентину важно знать количество гостей к ужину: то есть когда все выезжающие выехали, а все заезжающие заехали. +* Для упрощения работы Валентин просит сообщать ему только о датах, когда изменяется загрузка курорта. + +### Примеры + +Как запустить все тесты: +``` +go test -v ./hotelbusiness/... +``` diff --git a/hotelbusiness/hotels.go b/hotelbusiness/hotels.go new file mode 100644 index 0000000..0a13167 --- /dev/null +++ b/hotelbusiness/hotels.go @@ -0,0 +1,17 @@ +// +build !solution + +package hotelbusiness + +type Guest struct { + CheckInDate int + CheckOutDate int +} + +type Load struct { + StartDate int + GuestCount int +} + +func ComputeLoad(guests []Guest) []Load { + return []Load{} +} diff --git a/hotelbusiness/hotels_test.go b/hotelbusiness/hotels_test.go new file mode 100644 index 0000000..23bc57a --- /dev/null +++ b/hotelbusiness/hotels_test.go @@ -0,0 +1,75 @@ +package hotelbusiness + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestComputeLoad_basic(t *testing.T) { + for _, tc := range []struct { + title string + guests []Guest + result []Load + }{ + { + title: "empty input", + guests: []Guest{}, + result: []Load{}, + }, + { + title: "one guest", + guests: []Guest{{1, 2}}, + result: []Load{{1, 1}, {2, 0}}, + }, + { + title: "two guests, one-by-one, without any gaps", + guests: []Guest{{1, 2}, {2, 3}}, + result: []Load{{1, 1}, {3, 0}}, + }, + { + title: "two guests, one-by-one, with a gap", + guests: []Guest{{1, 2}, {3, 4}}, + result: []Load{{1, 1}, {2, 0}, {3, 1}, {4, 0}}, + }, + { + title: "two guests, together", + guests: []Guest{{1, 2}, {1, 2}}, + result: []Load{{1, 2}, {2, 0}}, + }, + { + title: "overlapping", + guests: []Guest{{1, 3}, {3, 5}, {2, 4}}, + result: []Load{{1, 1}, {2, 2}, {4, 1}, {5, 0}}, + }, + { + title: "stairs", + guests: []Guest{{1, 6}, {2, 5}, {3, 4}}, + result: []Load{{1, 1}, {2, 2}, {3, 3}, {4, 2}, {5, 1}, {6, 0}}, + }, + } { + t.Run(tc.title, func(t *testing.T) { + require.Equal(t, tc.result, ComputeLoad(tc.guests)) + }) + } +} + +func TestComputeLoad_stress1(t *testing.T) { + n := 1000000 + g := make([]Guest, 0, 1000000) + for i := 0; i < n; i++ { + g = append(g, Guest{1, 2}) + } + l := ComputeLoad(g) + require.Equal(t, []Load{{1, n}, {2, 0}}, l) +} + +func TestComputeLoad_stress2(t *testing.T) { + n := 1000000 + g := make([]Guest, 0, 1000000) + for i := 0; i < n; i++ { + g = append(g, Guest{i, i + 1}) + } + l := ComputeLoad(g) + require.Equal(t, []Load{{0, 1}, {n, 0}}, l) +} diff --git a/utf8reverse/README.md b/utf8reverse/README.md new file mode 100644 index 0000000..2082261 --- /dev/null +++ b/utf8reverse/README.md @@ -0,0 +1,16 @@ +# utf8reverse + +Реализуйте функцию `utf8reverse.Reverse`. + +Функция принимает на вход юникодную строку и должна возвращать строку, +состояющую из тех же юникодных рун, но записанных в обратном порядке. + +Обратите внимание в тестах, что некоторые графемы распадаются при обращении строки. +Данный эффект связан с тем, что в юникоде некоторые руны умеют комбинироваться в одну графему. + +### Примеры + +Как запустить все тесты: +``` +go test -v ./utf8reverse/... +``` diff --git a/utf8reverse/reverse.go b/utf8reverse/reverse.go new file mode 100644 index 0000000..0c147d3 --- /dev/null +++ b/utf8reverse/reverse.go @@ -0,0 +1,7 @@ +// +build !solution + +package utf8reverse + +func Reverse(input string) string { + return "" +} diff --git a/utf8reverse/reverse_test.go b/utf8reverse/reverse_test.go new file mode 100644 index 0000000..f0715e9 --- /dev/null +++ b/utf8reverse/reverse_test.go @@ -0,0 +1,44 @@ +package utf8reverse + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestReverse(t *testing.T) { + for i, tc := range []struct { + input string + output string + }{ + {input: "", output: ""}, + {input: "x", output: "x"}, + {input: "Hello!", output: "!olleH"}, + {input: "Привет", output: "тевирП"}, + {input: "\r\n", output: "\n\r"}, + {input: "\n\n", output: "\n\n"}, + {input: "\t*", output: "*\t"}, + // NB: Диакритика съехала! + {input: "möp", output: "p̈om"}, + // NB: Иероглиф развалился!, + {input: "뢴", output: "ᆫᅬᄅ"}, + {input: "Hello, 世界", output: "界世 ,olleH"}, + {input: "ำ", output: "ำ"}, + {input: "ำำ", output: "ำำ"}, + // NB: Эмоджи распался. + {input: "👩‍❤️‍💋‍👩", output: "👩‍💋‍️❤‍👩"}, + // NB: Эмоджи распался. + {input: "🏋🏽‍♀️", output: "️♀\u200d🏽🏋"}, + {input: "🙂", output: "🙂"}, + {input: "🙂🙂", output: "🙂🙂"}, + // NB: DE != ED + {input: "🇩🇪", output: "🇪🇩"}, + // NB: Флаг распался. :) + {input: "🏳️‍🌈", output: "🌈‍️🏳"}, + } { + t.Run(fmt.Sprintf("#%v: %v", i, tc.input), func(t *testing.T) { + require.Equal(t, tc.output, Reverse(tc.input)) + }) + } +} diff --git a/utf8reverse/solution.go b/utf8reverse/solution.go new file mode 100644 index 0000000..99760a5 --- /dev/null +++ b/utf8reverse/solution.go @@ -0,0 +1,24 @@ +// +build solution + +package utf8reverse + +import ( + "unicode/utf8" +) + +func Reverse(input string) string { + rs := []rune{} + sz := 0 + for len(input) > 0 { + r, n := utf8.DecodeRuneInString(input) + rs = append(rs, r) + sz += utf8.RuneLen(r) + input = input[n:] + } + bs := make([]byte, sz) + for i, j := 0, 0; i < len(rs); i++ { + n := utf8.EncodeRune(bs[j:], rs[len(rs)-i-1]) + j += n + } + return string(bs) +} diff --git a/utf8spacecollapse/README.md b/utf8spacecollapse/README.md new file mode 100644 index 0000000..a5cd8ee --- /dev/null +++ b/utf8spacecollapse/README.md @@ -0,0 +1,13 @@ +# utf8spacecollapse + +Реализуйте функцию `utf8spacecollapse.CollapseSpaces`. + +Функция принимает на вход юникодную строку и должна возвращать строку, +состояющую из тех же символом, но где все подряд идущие группы пробельных символов заменены на ' ' (один обычный пробел). + +### Примеры + +Как запустить все тесты: +``` +go test -v ./utf8spacecollapse/... +``` diff --git a/utf8spacecollapse/collapse.go b/utf8spacecollapse/collapse.go new file mode 100644 index 0000000..da6b990 --- /dev/null +++ b/utf8spacecollapse/collapse.go @@ -0,0 +1,7 @@ +// +build !solution + +package utf8spacecollapse + +func CollapseSpaces(input string) string { + return "" +} diff --git a/utf8spacecollapse/collapse_test.go b/utf8spacecollapse/collapse_test.go new file mode 100644 index 0000000..1ee174c --- /dev/null +++ b/utf8spacecollapse/collapse_test.go @@ -0,0 +1,29 @@ +package utf8spacecollapse + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCollapseSpaces(t *testing.T) { + for i, tc := range []struct { + input string + output string + }{ + {input: "", output: ""}, + {input: "x", output: "x"}, + {input: "Hello, World!", output: "Hello, World!"}, + {input: "Привет,\tМир!", output: "Привет, Мир!"}, + {input: "\r\n", output: " "}, + {input: "\n\n", output: " "}, + {input: "\t*", output: " *"}, + {input: " \t \t ", output: " "}, + {input: " \tx\t ", output: " x "}, + } { + t.Run(fmt.Sprintf("#%v: %v", i, tc.input), func(t *testing.T) { + require.Equal(t, tc.output, CollapseSpaces(tc.input)) + }) + } +} diff --git a/utf8spacecollapse/solution.go b/utf8spacecollapse/solution.go new file mode 100644 index 0000000..526fcd4 --- /dev/null +++ b/utf8spacecollapse/solution.go @@ -0,0 +1,30 @@ +// +build solution + +package utf8spacecollapse + +import ( + "unicode" + "unicode/utf8" +) + +func CollapseSpaces(input string) string { + res := make([]byte, len(input)) + pos := 0 + lastWasSpace := false + for len(input) > 0 { + r, n := utf8.DecodeRuneInString(input) + input = input[n:] + if unicode.IsSpace(r) { + if lastWasSpace { + continue + } + res[pos] = ' ' + pos++ + lastWasSpace = true + } else { + pos += utf8.EncodeRune(res[pos:], r) + lastWasSpace = false + } + } + return string(res[:pos]) +} diff --git a/varjoin/README.md b/varjoin/README.md new file mode 100644 index 0000000..42e6ca0 --- /dev/null +++ b/varjoin/README.md @@ -0,0 +1,54 @@ +# varfmt + +Реализуйте функцию `varfmt.Sprintf`. Функция принимает формат строку и переменное число аргументов. + +Синтаксис формат-строки похож на формат-строки питона: + - `{}` - задаёт ссылку на аргумент + - `{number}` - ссылается на аргумент с индексом `number` + - `{}` ссылается на аргумент с индексом равным позиции `{}` внутри паттерна + +Например, `varfmt.Sprintf("{1} {0}", "Hello", "World)` должен вернуть строку `World Hello`. + +Аргументы функции могут быть произвольными типами. Вам нужно форматировать их так же, как это +делает функция `fmt.Sprint`. Вызывать `fmt.Sprint` для форматирования отдельного аргумента +не запрещается. + +Ваше решение будет сравниваться с baseline-решением на бенчмарке. Сравнение будет +проходить независимо по трем метрикам. + - `time/op` - время на одну итерацию бенчмарка + - `alloc/op` - число выделенных байт на одну итерацию бенчмарка + - `allocs/op` - число выделенных объектов на одну итерацию бенчмарка + +Ваш код должен быть не более чем в два раза хуже чем baseline. + +``` +goos: linux +goarch: amd64 +pkg: gitlab.com/slon/shad-go/varfmt +BenchmarkFormat/small_int-4 4744729 263 ns/op 64 B/op 4 allocs/op +BenchmarkFormat/small_string-4 2388128 484 ns/op 168 B/op 8 allocs/op +BenchmarkFormat/big-4 8997 127827 ns/op 194656 B/op 41 allocs/op +BenchmarkSprintf/small-4 13330094 85.7 ns/op 2 B/op 1 allocs/op +BenchmarkSprintf/small_string-4 9351295 123 ns/op 16 B/op 1 allocs/op +BenchmarkSprintf/big-4 12006 108144 ns/op 16392 B/op 1 allocs/op +PASS +``` + +### Примеры + +Как запустить все тесты и бенчмарки: +``` +go test -v -bench=. ./varfmt/... +``` + +Как запустить только бенчмарки: +``` +go test -v -run=^a -bench=. ./varfmt/... +``` +Здесь `^a` - регулярное выражение, задающее тесты для запуска, +а `.` - задаёт бенчмарки. + +Как запустить только big бенчмарки: +``` +go test -v -run=^a -bench=/big ./varfmt/... +``` diff --git a/varjoin/join.go b/varjoin/join.go new file mode 100644 index 0000000..bcf0f07 --- /dev/null +++ b/varjoin/join.go @@ -0,0 +1,7 @@ +// +build !solution + +package varjoin + +func Join(args ...string) string { + return "" +} diff --git a/varjoin/join_test.go b/varjoin/join_test.go new file mode 100644 index 0000000..aabd04c --- /dev/null +++ b/varjoin/join_test.go @@ -0,0 +1,35 @@ +package varjoin + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFormat(t *testing.T) { + for _, tc := range []struct { + sep string + args []string + result string + }{ + { + sep: "/", + args: []string{}, + result: "", + }, + { + sep: "/", + args: []string{"1", "2", "3"}, + result: "1/2/3", + }, + { + sep: "", + args: []string{"h", "e", "ll", "o"}, + result: "hello", + }, + } { + t.Run(tc.result, func(t *testing.T) { + require.Equal(t, tc.result, Join(tc.sep, tc.args...)) + }) + } +}