Разработка на коленке

"тут должна быть красивая цитата о программировании"

Новый Level up на codingame, теперь 13

2016-11-21 21:23
13

На днях получил 13 уровень, практически вскарабкался на очередную высоту (как енот на рисунке). Из решённых задач - 100% в Easy, и одна нерешённая в Medium (про марсоход). Марсоход пока делать не буду, переключусь на задачи связанные с поиском в Hard. Только сперва сделаю паузу. Во-первых, хочу разобраться с некоторыми алгоритмами поиска. А во-вторых, нужно снова садиться за книги по OCaml.

Чтобы решать задачи на codingame мне вполне хватает простых функций и базовых типов, иногда мне понадобилось определять свои типы (варианты и записи), но на этом всё. Т.е. будь у меня в руках Python, то я бы мог ограничиться def, а структуры данных создавать на основе tuple и dict. Но я начал решать задачи на codingame не только для тренировки в решении алгоритмических задач, а ещё и для изучения OCaml. Поэтому сейчас мне нужно разобраться с модулями, классами, объектами и функторами, прежде чем я начну писать код. Вполне возможно, что использование этих вещей для решения задач будет большим переусложнением, но меня это устроит.

К сожалению, на OCaml там пишет совсем немного людей, из-за этого нет возможности учиться на примерах. Всё чаще в списке решений на OCaml встречается вот такая картинка.

alone

Ещё я зарегистрировался на hackerrank.com, где сейчас прохожу их "30 day of code". В отличии от codingame, там есть возможность использовать Core. На самом деле, именно это меня в нём привлекло.

How to split string in OCaml by delimiter

2016-11-15 21:54
How to split string in OCaml by delimiter

Из-за того, что на codingame доступна только стандартная библиотека OCaml, да и то не вся, приходится писать свои мелкие функции. С каждой итерацией функции всё больше похожи на что-то пригодное для продакшена.

В этот раз я снова написал функцию для разбиения строки на подстроки.

let split str ~on:delim =
    let open String in

    let rec _split acc str =
        try
            let pos = index str delim in
            _split ((sub str 0 pos) :: acc) (sub str (pos + 1) ((length str) - pos - 1))
        with Not_found -> str :: acc
    in

   _split [] str |> List.filter (fun s -> s <> "") |> List.rev

let () =
    "This is a long message with two spaces after next word  and still there is shouldn't be an empty string in a list "
    |> split ~on:' '
    |> String.concat "\n"
    |> print_endline

Отсутствие библиотек всё чаще заставляет задуматься о переходе на hackerrank, где есть Core. Если писать решения для обычных задач ещё можно, то участвовать в соревнованиях может быть сложно. А с другой стороны так я получаю больше практики и лучше понимаю, как работает OCaml.

Clash of code

2016-10-05 15:42
clash of code

Попробовал участвовать в коротких (5-15 минут) соревнованиях - Clash of code. Получилось довольно странно: при том, что многое мне не понравилось, я всё равно буду в них участвовать, по крайней мере, в этом месяце.

Задачи достаточно простые. Пока что не встретил задач, которые бы требовали большого времени на решение или просто серьёзного обдумывания. Впрочем, это ожидаемо, вряд ли можно решить что-то сложное за 5 минут.

В Clash of code три номинации: самое короткое решение (code golf), самый быстрый программист (кто быстрее накодит) и что-то связанное с определением условия задачи по примерам (мне не попадалось). В code golf нет ограничений или группировки по языкам, что сразу ставит участников в неравные условия, OCaml заметно проигрывает python (во всяком случае при моём текущем уровне знания OCaml это так, на python я бы смог писать более короткие решения). Скоростные решения мне пока тоже не по зубам - приходится долго вспоминать, как сделать ту или иную вещь.

Код, написанный в рамках Clash of code, очень страшен. Если с code golf, по-другому быть не может, то в соревнованиях на скорость по-другому я пока не умею, либо быстро, либо красиво.

Вот так у меня выглядит поиск суммы всех чисел от 0 до (n-1), которые делятся без остатка на 3 или 5 или 7:

Array.init(read_int())(fun i->if i mod 3=0||i mod 5=0||i mod 7=0 then i else 0)|>Array.fold_left(+)0|>print_int

Это не то решение, которое ушло на codingame, там были дополнительные пробелы и строка была чуть длиннее.

Тем временем, я получил 11 уровень за решение задач (не Clash of code, за них уровень не повышают).

Заработал OCaml Addict

2016-09-24 02:22
OCaml Addict

Решил очередную задачу и получил Legend-ачивку "OCaml Addict" (профиль) за то, что помог Марвину выйти из двигателя. Сделал скриншот ачивки, а котика нарисовал просто так.

Решение задач у меня сейчас проходит примерно так:

  • Добиться прохождения всех тестов. На этом этапе идёт в ход всё плохое, что есть в программирование: magic number, magic string, хардкод, копипаст - большая часть из-за того, что я плохо знаю OCaml.
  • Убрать явные косяки, копипаст, magic numbers. В целом, сделать так, чтобы код можно было читать и понять. На этом этапе код часто уменьшается в размерах.
  • Ввести типы данных, избавиться от строк и чисел в math ... with, сделать обработку ошибок и проверку на кривые данные. Хотя кривых данных в тестах не бывает, тут я уже делаю это просто для практики.

Тестирование OCaml-модулей при помощи Alcotest

2016-09-16 20:01
alcotest terminal

Мне нужно было научиться писать тесты на OCaml, чтобы я мог контролировать работу своих модулей. Не настолько я крутой программист, чтобы всегда обходиться без тестов, особенно это касается новых для меня языков.

Из списка библиотек для тестирования взял первый по списку - Alcotest. Когда не знаешь ничего, лучший способ разобраться - попробовать.

Для разработки первого теста взял первую задачу из списка 99 problems на ocaml.org - "Write a function last : 'a list -> 'a option that returns the last element of a list".

# last [ "a" ; "b" ; "c" ; "d" ];;
- : string option = Some "d"
# last [];;
- : 'a option = None

Т.е. нужно написать функцию, которая возвращает последний элемент списка или None.

Решение задачи

Чтобы проверить в работе Alcotest, я сперва написал решение задачи. Хоть это и не по канонам TDD, в соответствии с которыми сперва пишут тест, но меня это устраивает.

Интерфейс last.mli

val last: 'a list -> 'a option

Реализация last.ml

let rec last = function
    | [] -> None
    | [elem] -> Some elem
    | hd :: tl -> last tl

Тест test.ml

open Last


let empty_list () =
  Alcotest.(check (option reject)) "Last from empty list" None (last [])


let one_elem () =
  Alcotest.(check (option int)) "Last from empty list" (Some 1) (last [1])


let last_int () =
  Alcotest.(check (option int)) "Last int" (Some 2) (last [1; 2])


let last_char () =
  Alcotest.(check (option char)) "Last char" (Some 'a') (last ['b'; 'c'; 'a'])


let last_string () =
  Alcotest.(check (option string)) "Last string" (Some "hi") (last ["bye"; "hi"])


let () =
  Alcotest.run "Test Last.last function which should return last element of a list" [
      "test_set", [
        "Get last from empty list", `Quick, empty_list;
        "Get the only element from list", `Quick, one_elem;
        "Get the int from list", `Quick, last_int;
        "Get the char from list", `Quick, last_char;
        "Get the string from list", `Quick, last_string;
      ]
  ]

Сборка

$ ocamlbuild -pkg alcotest test.byte
Finished, 7 targets (7 cached) in 00:00:00.

И результат в терминале

$ ./test.byte
Testing Test Last.last function which should return last element of a list.
[OK]                test_set          0   Get last from empty list.
[OK]                test_set          1   Get the only element from list.
[OK]                test_set          2   Get the int from list.
[OK]                test_set          3   Get the char from list.
[OK]                test_set          4   Get the string from list.
The full test results are available in `_tests`.
Test Successful in 0.002s. 5 tests run.

Все тесты пройдены, все танцуют.

Эмуляция ошибки

На самом деле нужно было с этого начинать разработку, но не в том случае, если для меня предметом изучения является сама библиотека для тестов.

Сперва я поломал функцию last

let rec last = function
    | _ -> None

Потом пересобрал тест и запустил

$ ocamlbuild -pkg alcotest test.byte
Finished, 7 targets (4 cached) in 00:00:00.
$ ./test.byte
Testing Test Last.last function which should return last element of a list.
[OK]                test_set          0   Get last from empty list.
[ERROR]             test_set          1   Get the only element from list.
[ERROR]             test_set          2   Get the int from list.
[ERROR]             test_set          3   Get the char from list.
[ERROR]             test_set          4   Get the string from list.
The full test results are available in `_tests`.
4 errors! in 0.003s. 5 tests run.

Ну и славно, значит ошибки искать тоже умеет. А ещё он умеет раскрашивать вывод.

alcotest terminal 2

Заключение

Alcotest на текущий момент меня устраивает, поэтому сейчас буду тестировать свой код при помощи этой библиотеки. Для моих скромных потребностей её хватает.