Объединить два списка - PullRequest
       64

Объединить два списка

10 голосов
/ 01 февраля 2012

Я хочу объединить 2 списка в F # чисто функциональным способом. Мне трудно понять синтаксис.

Допустим, у меня есть кортеж ([5;3;8],[2;9;4])

Когда я вызываю функцию, она должна вернуть [5;2;3;9;8;4]

Вот почему у меня так далеко, я уверен, что это неправильно. Если бы кто-то мог объяснить это простым способом, я был бы благодарен.

let rec interleave (xs,ys) = function
|([], ys) -> ys
|(x::xs, y::ys) -> x :: y::  interleave (xs,ys) 

Ответы [ 5 ]

12 голосов
/ 01 февраля 2012

Ваша функция почти справа.let f = function является сокращением для let f x = match x with, поэтому вам не нужны явные аргументы.Кроме того, ваш алгоритм нуждается в доработке.

let rec interleave = function //same as: let rec interleave (xs, ys) = match xs, ys with
  |([], ys) -> ys
  |(xs, []) -> xs
  |(x::xs, y::ys) -> x :: y :: interleave (xs,ys)

interleave ([5;3;8],[2;9;4]) //output: [5; 2; 3; 9; 8; 4]
8 голосов
/ 01 февраля 2012

Важным моментом является то, что функция неверна . Сбой при вводе ([1;2;3], []), так как вы пропустили регистр (xs, []) при сопоставлении с образцом. Более того, аргументы лучше в форме карри, чтобы их было легче использовать с частичным применением. Вот исправленная версия:

let rec interleave xs ys =
    match xs, ys with
    | [], ys -> ys
    | xs, [] -> xs
    | x::xs', y::ys' -> x::y::interleave xs' ys'

Вы можете видеть, что функция не является хвостовой рекурсией , поскольку она применяет конструктор cons (::) дважды после возврата рекурсивного вызова. Один интересный способ сделать его хвост-рекурсивным - использовать выражение последовательности:

let interleave xs ys =
    let rec loop xs ys = 
       seq {
             match xs, ys with
             | [], ys -> yield! ys
             | xs, [] -> yield! xs
             | x::xs', y::ys' -> 
                   yield x
                   yield y
                   yield! loop xs' ys'
            }
    loop xs ys |> List.ofSeq
2 голосов
/ 04 февраля 2012

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

let rec zipWith f xlist ylist = 
  match f, xlist, ylist with
  | f, (x :: xs), (y :: ys) -> f x y :: zipWith f xs ys
  | _, _, _ -> []

let interleave xs ys = zipWith (fun a b -> [a; b]) xs ys |> List.concat

Редактировать:

Как сказал @pad ниже, F # уже имеет zipWith под именем List.map2.Таким образом, вы можете переписать interleave следующим образом:

let interleave xs ys = List.map2 (fun a b -> [a; b]) xs ys |> List.concat
1 голос
/ 03 мая 2019

Начиная с F # 4.5 (я думаю), предполагая, что вы хотите продолжить давать элементы из более длинной последовательности, когда короткая исчерпана, вы можете просто сделать:

let interleave = Seq.transpose >> Seq.concat >> Seq.toList

> interleave [ [5;3;8]; [2;9;4] ];;
val it : int list = [5; 2; 3; 9; 8; 4]

> interleave [ [1;2;3]; [4;5]; [6;7;8;9] ];; // also works for any number of lists
val it : int list = [1; 4; 6; 2; 5; 7; 3; 8; 9]

(Примечание List.transpose выдает, если последовательности имеют разную длину, но Seq.transpose - нет, поэтому вам нужно использовать последнее.)

1 голос
/ 09 декабря 2016

Из OP не ясно, что должно произойти, если списки имеют разную длину, но вот общая, рекурсивная реализация, которая полностью использует оба списка:

// 'a list -> 'a list -> 'a list
let interleave xs ys =
    let rec imp xs ys acc =
        match xs, ys with
        |    [],    [] -> acc
        | x::xs,    [] -> imp xs [] (x::acc)
        |    [], y::ys -> imp [] ys (y::acc)
        | x::xs, y::ys -> imp xs ys (y::x::acc)
    imp xs ys [] |> List.rev

Примеры:

> interleave [5;3;8] [2;9;4];;
val it : int list = [5; 2; 3; 9; 8; 4]
> interleave [] [1..3];;
val it : int list = [1; 2; 3]
> interleave [1..3] [42];;
val it : int list = [1; 42; 2; 3]
> interleave [1..3] [42;1337];;
val it : int list = [1; 42; 2; 1337; 3]
> interleave [42; 1337] [1..3];;
val it : int list = [42; 1; 1337; 2; 3]
...