
На 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).
Недостаток решения задач на соревнованиях - нужен только результат, а не поддерживаемый код. Довольно сложно убедить себя в необходимости дорабатывать код, если все тесты пройдены, а возвращаться к коду вряд ли понадобится.