Как заархивировать каждый отдельный элемент из двух списков в один с помощью OCaml

Если у меня есть ввод кортежа, содержащего два списка целых чисел одинаковой длины, и я хочу, чтобы мой вывод был списком этих двух заархивированных списков, после извлечения этих двух списков из кортежа, как мне заархивировать каждый отдельный элемент в один список? Например, если я ввел twolists = ([1; 2; 3], [4; 5; 6]), то я хочу, чтобы мой вывод был [(1,4); (2,5); (3,6)]. Как заархивировать каждый элемент и добавить его в свои выходные данные? Имя и тип функции следующие:

let rec pairlists twolists = ...

val pairlists : 'a list * 'b list -> ('a * 'b) list = fun

Пока у меня есть:

let rec pairlists twolists = 
  let (l1, l2) = twolists in
  let rec zip (l1,l2) =
    match l1 with 
    |[] -> l2
    |x :: xs -> x :: zip(l2, xs) in
  twolists ;;

но это явно не то, что я хочу.


person Livia Seiler    schedule 23.01.2020    source источник
comment
чтобы разметить фрагмент текста как код, просто выделите его мышью и нажмите кнопку, которая выглядит как {} в окне редактора.   -  person ivg    schedule 23.01.2020


Ответы (4)


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

Если списки гарантированно имеют одинаковую длину, решение может быть таким простым, как:

let rec zip paired_lists =
  match paired_lists with
  | [], [] -> []
  | h1::t1, h2::t2 -> (h1, h2)::(zip (t1, t2))
  | _, _ -> failwith "oops, the lists seems to have different lengths"
;;

zip ([1;2;3], [4;5;6]);;
- : (int * int) list = [(1, 4); (2, 5); (3, 6)]

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

let zip_tr paired_lists =
  let list1, list2 = paired_lists in
  let rec aux l1 l2 acc =
    match l1, l2 with
    | [], [] -> List.rev acc
    | h1::t1, h2::t2 -> aux t1 t2 (h1, h2)::acc
    | _, _ -> failwith "oops, the lists seems to have different lengths"
  in aux list1 list2 []
;;

zip_tr ([1;2;3], [4;5;6]);;
- : (int * int) list = [(1, 4); (2, 5); (3, 6)]
person Konstantin Strukov    schedule 24.01.2020

Подпись вашего кода не соответствует ожидаемой подписи:

line 2, characters 11-13:
Warning 26: unused variable l2.
Line 2, characters 7-9:
Warning 26: unused variable l1.
val pairlists : 'a list * 'a list -> 'a list = <fun>

Действительно, оба возможных совпадения возвращают либо 'a list (это l2), либо x::zip..., который также является списком типа 'a.

В вашем коде должно быть что-то вроде (x,y)::list.

Кроме того, pairlists не является рекурсивным и его не нужно объявлять как таковое, только zip является рекурсивным. Конец вашей функции должен быть таким (иначе zip не действует):

....
let rec zip (l1,l2) =
match l1 with 
|[] -> l2
|x :: xs -> x :: zip(l2, xs) in
zip twolists ;;
person Pierre G.    schedule 23.01.2020
comment
Если бы я хотел, чтобы паирлисты были рекурсивными, как бы я структурировал свой код? (Мне не обязательно использовать функцию zip). - person Livia Seiler; 23.01.2020
comment
zip достаточно, просто переименуйте его в pairlists и все (в вашем случае вспомогательная функция не нужна). - person Pierre G.; 23.01.2020

В дополнение к другим упомянутым решениям, ocaml-4.08 и более поздних версий позволяет вам предоставлять операторы let + и +, которые будут архивировать список по сумме, где в противном случае вы могли бы подумать об использовании аппликативов. Является ли это их улучшением - в глазах смотрящего:

let (let+) list f = List.map f list

let (and+) a b =
  let rec loop first second =
    match first,second with
      first_hd::first_tl,second_hd::second_tl ->
       (first_hd,second_hd)::(loop first_tl second_tl)
    | _ -> []
  in
  loop a b

let pairlists = function
    first,second ->
     let+ elt1 = first
     and+ elt2 = second in
     [elt1 ; elt2]

(* example *)
let () =
  let res = pairlists ([1;2;3], [4;5;6]) in
  List.iter
    (fun list -> List.iter (fun i -> Printf.printf "%d " i) list ;
                 print_endline "")
    res

Здесь для сравнения представлен более традиционный подход, если вы используете аппликативы.

let pure x = [x]

let (<*>) aps args =
  List.concat (List.map (fun f -> List.map (fun x -> f x) args) aps)

let (<|>) aps args =
  let rec loop args_rest aps_rest =
    match args_rest,aps_rest with
      args_hd::args_tl,aps_hd::aps_tl ->
       (aps_hd args_hd)::(loop args_tl aps_tl)
    | _ -> []
  in
  loop args aps

let pairlists = function
    first,second ->
     let two_list a b = a :: [b] in
     pure two_list <*> first <|> second

(* example *)
let () =
  let res = pairlists ([1;2;3], [4;5;6]) in
  List.iter
    (fun list -> List.iter (fun i -> Printf.printf "%d " i) list ;
                 print_endline "")
    res
person Chris Vine    schedule 02.02.2020

Вы ищете List.combine?

val comb: 'a list - ›' b list -› ('a *' b) list

Преобразуйте пару списков в список пар: объедините [a1; ...; ан] [b1; ...; bn] равно [(a1, b1); ...; (ан, бн)].

Вызывает Invalid_argument, если два списка имеют разную длину. Не хвосторекурсивный.

person Hadrien Renaud    schedule 19.01.2021