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

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

Сортировка выбором на OCaml

2016-07-31 19:56
Selection sort on OCaml

В очередной лекции упоминалась сортировка выбором, и я решил сделать её реалзизацию в функциональном стиле на 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, когда пытаются решить задачу, использовав при написании кода наименьшее количество символов. Так и здесь, это просто разминка для мозгов.