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

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

Новый 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, сделать обработку ошибок и проверку на кривые данные. Хотя кривых данных в тестах не бывает, тут я уже делаю это просто для практики.

Рефакторинг битвы против Skynet

2016-08-31 19:47
skynet

На codingame решил все задачи из блока easy и перешёл к medium. Первой задачей "средней сложности", стала разработка вируса для битвы со Skynet. Ну а по сути, нужно было не дать боту достигнуть одной из целевых вершин (шлюзы) в ненаправленном графе.

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

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

Первое решение

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

let read_network_config () =
    let init_data = read_line () in
    Scanf.sscanf init_data "%d %d %d" (fun n l e -> (n, l, e))


let create_disconnected_network nodes_number =
    Array.make_matrix nodes_number nodes_number 0


let connect_nodes network edges_number =
    for i = 0 to edges_number - 1 do
        let edge = read_line () in
        let n1, n2 = Scanf.sscanf edge "%d %d" (fun n1 n2 -> (n1, n2)) in
        network.(n1).(n2) <- 1;
        network.(n2).(n1) <- 1;
    done


let read_gateways gateway_number =
     Array.init gateway_number (fun i -> read_int ())


let search_nodes_to_disconnect network gateways skynet_agent_position =
    let to_destroy = Array.map (fun i -> if network.(i).(skynet_agent_position) = 1 then (Some i, Some skynet_agent_position) else (None, None)) gateways in
    let e = Array.fold_left (fun a b ->
        match a, b with
        | (None, None), (None, None) -> (None, None)
        | (Some x, Some y), (None, None) | (None, None), (Some x, Some y) -> (Some x, Some y)
    ) (None, None) to_destroy in

    let rec find_edge e =
        if e < Array.length network then
            if network.(skynet_agent_position).(e) = 1 then
                (
                    network.(skynet_agent_position).(e) <- 0;
                    network.(e).(skynet_agent_position) <- 0;
                    e
            )
            else find_edge (e + 1)
        else
            0
    in

    match e with
    | (None, None) -> (skynet_agent_position, find_edge 0)
    | (Some x, Some y) -> (
        network.(x).(y) <- 0;
        network.(y).(x) <- 0;
        (x, y)
    )


let disconnect_nodes (a, b) =
    Printf.printf "%d %d\n" a b


let () =
    let nodes_number, edges_number, gateway_number = read_network_config () in

    let network = create_disconnected_network nodes_number in
    connect_nodes network edges_number;

    let gateways = read_gateways gateway_number in

    while true do
        read_int () |> search_nodes_to_disconnect network gateways |> disconnect_nodes
    done

Рефакторинг

При рефакторинге для меня важно было добиться логичной структуры программы, читабельности и спрятать реализацию графа либо в модуль, либо просто в список функций. Делать класс не видел необходимости, потому что у меня не было потребности в динамическом связывании.

После рефакторинга

module Net = struct
    let connect network a b =
        network.(a).(b) <- 1;
        network.(b).(a) <- 1;
        ()

    let disconnect network a b =
        network.(a).(b) <- 0;
        network.(b).(a) <- 0;
        ()

    let has_direct_connection network a b =
        network.(a).(b) = 1

    let find_any_connected_node network node_number =
        let rec find num =
            if num < Array.length network then
                if network.(node_number).(num) = 1 then
                    num
                else
                    find (num + 1)
            else
                node_number
        in

        find 0

    let build nodes_number edges =
        let network = Array.make_matrix nodes_number nodes_number 0 in
        List.iter (fun (a, b) -> connect network a b) edges;
        network
end


let read_network_config () =
    let open Scanf in

    let nodes_number, edges_number, gateway_number =
        let init_data = read_line () in
        sscanf init_data "%d %d %d" (fun n l e -> (n, l, e))
    in

    let rec read_edges = function
        | 0 -> []
        | num -> sscanf (read_line ()) "%d %d" (fun a b -> (a, b)) :: read_edges (num - 1)
    in

    let edges = read_edges edges_number in

    let gateways =
        Array.init gateway_number (fun i -> read_int ()) |> Array.to_list
    in

    nodes_number, edges, gateways


let fight_with_skynet network gateways =
    let destroy_edge a b =
        Net.disconnect network a b;
        Printf.printf "%d %d\n" a b
    in

    let rec stop_skynet_agent () =
        let skynet_agent_position = read_int () in
        let agent_to_gateway_edges =
            List.filter (fun gateway ->
                Net.has_direct_connection network skynet_agent_position gateway)
            gateways
        in
        let connected_node = match agent_to_gateway_edges with
        | [] -> Net.find_any_connected_node network skynet_agent_position
        | connected_gateway :: tl -> connected_gateway
        in
        destroy_edge skynet_agent_position connected_node;

        stop_skynet_agent ()
    in

    stop_skynet_agent ()


let () =
    let nodes_number, edges, gateways = read_network_config () in
    let network = Net.build nodes_number edges in
    fight_with_skynet network gateways

Заключение

В итоге у меня получилось решение, которое и задачу решало, и меня устраивало (во всяком случае, с текущем уровнем знаний в OCaml).

Недостаток решения задач на соревнованиях - нужен только результат, а не поддерживаемый код. Довольно сложно убедить себя в необходимости дорабатывать код, если все тесты пройдены, а возвращаться к коду вряд ли понадобится.