
В очередной лекции упоминалась сортировка выбором, и я решил сделать её реалзизацию в функциональном стиле на OCaml, насколько это вообще возможно.
В этот раз у меня были такие цели:
- написать проверочный код в чисто императивном стиле;
- написать вариант со списками и с рекурсией, пусть и не хвостовой.
В этот раз решение без разделения на разные программы и передачу данных через stdin
и stdout
.
Итак, первый и самый простой вариант - императивный:
let imp_sel_sort lst = let data = List.to_array lst in let last_index = Array.length data - 1 in for i = 0 to last_index do let ind = ref i in for j = (i + 1) to last_index do if data.(j) < data.(!ind) then ind := j; done; if !ind <> i then Array.swap data i !ind; done; Array.to_list data
Тут всё, как в описании алгоритма. Ищем минимальный элемент и ставим его в очередную позицию. Для поиска индекса использовал изменяемое значение let ind = ref i in
.
Следующая реализация - сортировка списка с рекурсией и разбором списка до элементарных значений.
let fun_sel_sort lst = let rec find_min = function | [] -> assert false | [hd] -> (hd, []) | hd :: tl -> ( let hd', tl' = find_min tl in if hd < hd' then hd, hd' :: tl' else hd', hd :: tl' ) in let rec sort _lst = let m, rest = find_min _lst in match rest with | [] -> [m] | _ -> m :: sort rest in match lst with | [] | [_] -> lst | lst -> sort lst
В этом варианте я искал минимальный элемент в остатке списка и потом конструировал из них отсортированный список, вызывая рекурсивно сортировку с остатком списка без этого элемента. Для такого финта ушами мне понадобилось, чтобы функция поиска минимального элемента возвращала и сам элемент и список, в котором его нет. По сути, мне приходилось разбивать список на ряд целых чисел, а потом собирать его обратно по стеку вызовов.
Пока писал функциональный вариант, решил попробовать написать новый вариант императивной сортировки, но при этом ограничиться только одной изменяемой переменной - массивом, а индекс минимального значения сделать обычной переменной, без ref
.
let imp_rec_sel_sort lst = let data = List.to_array lst in let last_index = Array.length data - 1 in for i = 0 to last_index do let min_index = let rec find imin current = if current > last_index then imin else begin let imin = match compare data.(imin) data.(current) with | 1 -> current | _ -> imin in if current = last_index then imin else find imin (current + 1) end in find i (i + 1) in if min_index <> i then Array.swap data i min_index; done; Array.to_list data
Для запуска всех этих функций мне потребовался небольшой тестовый код, который позволил мне проверить, что всё работает так, как я ожидал:
let list2str x = x |> List.to_string ~f:string_of_int let () = (* Код для первоначальной проверки работоспособности *) imp_sel_sort [3; 4; 8; 1; 2] |> list2str |> printf "%s\n"; fun_sel_sort [3; 4; 8; 1; 2] |> list2str |> printf "%s\n"; imp_rec_sel_sort [3; 4; 8; 1; 2] |> list2str |> printf "%s\n";
Полный листинг программы с тестом и выводом ошибок:
open Core.Std let fun_sel_sort lst = let rec find_min = function | [] -> assert false | [hd] -> (hd, []) | hd :: tl -> ( let hd', tl' = find_min tl in if hd < hd' then hd, hd' :: tl' else hd', hd :: tl' ) in let rec sort _lst = let m, rest = find_min _lst in match rest with | [] -> [m] | _ -> m :: sort rest in match lst with | [] | [_] -> lst | lst -> sort lst let imp_sel_sort lst = let data = List.to_array lst in let last_index = Array.length data - 1 in for i = 0 to last_index do let ind = ref i in for j = (i + 1) to last_index do if data.(j) < data.(!ind) then ind := j; done; if !ind <> i then Array.swap data i !ind; done; Array.to_list data let imp_rec_sel_sort lst = let data = List.to_array lst in let last_index = Array.length data - 1 in for i = 0 to last_index do let min_index = let rec find imin current = if current > last_index then imin else begin let imin = match compare data.(imin) data.(current) with | 1 -> current | _ -> imin in if current = last_index then imin else find imin (current + 1) end in find i (i + 1) in if min_index <> i then Array.swap data i min_index; done; Array.to_list data let list2str x = x |> List.to_string ~f:string_of_int let rec test deep = let render_error lst = [ "Error!"; "Origin: " ^ (lst |> list2str); "Imperative: " ^ (lst |> imp_sel_sort |> list2str); "Functional: " ^ (lst |> fun_sel_sort |> list2str); "Imperative with recursion: " ^ (lst |> imp_rec_sel_sort |> list2str); ] in let rec create_random_list num = match num with | 0 -> [] | _ -> Random.int 10:: create_random_list (num - 1) in let random_list = create_random_list deep in let imp_sorted = imp_sel_sort random_list in if imp_sorted <> (fun_sel_sort random_list) || imp_sorted <> (imp_rec_sel_sort random_list) then random_list |> render_error |> List.fold ~init:"" ~f:(fun accum s -> accum ^ s ^ "\n") else if deep > 0 then test (deep - 1) else "Ok" let () = (* (* Код для первоначальной проверки работоспособности *) imp_sel_sort [3; 4; 8; 1; 2] |> list2str |> printf "%s\n"; fun_sel_sort [3; 4; 8; 1; 2] |> list2str |> printf "%s\n"; imp_rec_sel_sort [3; 4; 8; 1; 2] |> list2str |> printf "%s\n"; *) test 100 |> printf "%s"
Заключение
Практической ценности у fun_sel_sort
и imp_rec_sel_sort
нет. Я их написал просто в качестве упражнения. Это похоже на code golf, когда пытаются решить задачу, использовав при написании кода наименьшее количество символов. Так и здесь, это просто разминка для мозгов.