Разделение списка элементов на два списка нечетных и четных проиндексированных элементов

Я хотел бы создать функцию, которая принимает список и возвращает два списка: первый содержит все нечетные элементы, а второй - все четные элементы.

Например, учитывая [1;2;4;6;7;9], я хотел бы вернуть [ [1;4;7] ; [2;6;9] ].

Я написал это до сих пор и не знаю, как развиваться.

let splitList list =
    let rec splitOdd oList list1 list2 =
        match oList with
        | [] -> []
        | head :: tail -> splitEven tail (list1::head) list2
    and splitEven oList list1 list2 =
        match oList with
        | [] -> []
        | head :: tail -> splitOdd tail list1 (list2::head)
    splitOdd list [] []

person Nathron    schedule 30.10.2011    source источник
comment
Не могли бы вы использовать для этого List.partition?   -  person Mathias    schedule 30.10.2011
comment
@Mathias: Нет, List.partition использует предикат, и в этом случае подходящего предиката нет.   -  person J D    schedule 21.02.2012


Ответы (8)


Реализация без переполнения стека:

let splitList list = List.foldBack (fun x (l,r) -> x::r, l) list ([],[])
person Ed'ka    schedule 30.10.2011
comment
Стоит отметить, что foldBack преобразует список в массив, чтобы обеспечить обход "от хвоста к голове". Как правило, это хорошо, поскольку обход массива выполняется быстро, но для больших списков следует учитывать стоимость. - person Daniel; 23.02.2012
comment
Блин, это очень хорошее решение. Мне потребовалось несколько секунд, чтобы понять, как это работает, но я должен признать, что это решение очень элегантно (у). - person user1498611; 08.11.2015
comment
Итак, не могли бы вы более подробно объяснить, как это работает? Если я правильно понял, List.foldBack применяет функцию к каждому элементу в списке, идет от хвоста к голове? ([], []) - кортеж, в котором хранится состояние. Как работает веселая часть? Что означает x :: r? Спасибо - person Dávid Molnár; 04.05.2017
comment
@ DávidMolnár из определения складки в псевдокоде split [a;b;c;d] --> (a::r , l) { where (l, r) <-- split [b;c;d] }; split [] --> ([] , []). таким образом, [] --> ([],[]); [d] --> ([d],[]); [c;d] --> ([c],[d]); [b;c;d] --> ([b;d],[c]); _7 _... - person Will Ness; 11.02.2018
comment
ср.. - person Will Ness; 15.03.2020

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

let rec splitList = function
    | [] -> [], []
    | [x]-> [x], []
    | x1::x2::xs -> let xs1, xs2 = splitList xs
                    x1::xs1, x2::xs2
person pad    schedule 30.10.2011
comment
Полностью то, что я искал ... большое спасибо! Определенно есть чему поучиться этому языку, особенно его лаконичности. - person Nathron; 30.10.2011
comment
Проблема с этой реализацией заключается в том, что не будучи хвостовой рекурсией, она вызывает переполнение стека в списках из ~ 80000 элементов. - person Ed'ka; 30.10.2011
comment
Разработчики F # могли принять во внимание en.wikipedia.org/wiki/Tail_call#Tail_recursion_modulo_cons - person Will Ness; 03.10.2012

Вот простое нерекурсивное решение:

let splitList ll =
    ll
    |> List.mapi (fun i x -> (i % 2 = 0, x))
    |> List.partition fst
    |> fun (odd,even) -> [List.map snd odd, List.map snd even];;

val splitList : 'a list -> 'a list list

Применив его к вашему образцу, он дает именно то, что вы хотели:

splitList [1;2;4;6;7;9];;

val it : int list list = [[1; 4; 7]; [2; 6; 9]]
person Gene Belitski    schedule 30.10.2011

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

let splitList items =
  let rec splitOdd odds evens = function
    | [] -> odds, evens
    | h::t -> splitEven (h::odds) evens t
  and splitEven odds evens = function
    | [] -> odds, evens
    | h::t -> splitOdd odds (h::evens) t
  let odds, evens = splitOdd [] [] items
  List.rev odds, List.rev evens
person Daniel    schedule 30.10.2011

Другой (менее эффективный) вариант

let splitList xs = 
    let odd, even =
        xs
        |> List.zip [ 1 .. (List.length xs) ]
        |> List.partition (fun (i, _) -> i % 2 <> 0)
    [ odd |> List.map snd; even |> List.map snd ]

Если вы не хотите создавать временные списки, рассмотрите возможность использования последовательностей:

let splitListSeq xs =
    xs
    |> Seq.mapi (fun i x -> (i % 2 = 0, x))
    |> Seq.groupBy (fun (b, _) -> b)
    |> Seq.map snd
    |> Seq.map ((Seq.map snd) >> Seq.toList)
    |> Seq.toList

Еще одна, похожая на версию Даниила:

let splitListRec xs =
    let rec loop l r = function
        | []      -> [l; r]
        | x::[]   -> [x::l; r]
        | x::y::t -> loop (x::l) (y::r) t
    loop [] [] xs |> List.map List.rev
person Frank    schedule 30.10.2011

Похоже, вы хотите List.partition ‹'T> (http://msdn.microsoft.com/en-us/library/ee353782.aspx). Эта функция принимает предикат и список и возвращает пару (2-кортеж), где первый элемент - это все элементы, прошедшие ваш тест, а второй - все элементы, которые не прошли тест. Итак, вы можете классифицировать шансы и эвенты с помощью:

List.partition odd [1;2;4;6;7;9]

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

Удачи!

person gnuvince    schedule 30.10.2011
comment
Опять же, речь идет о четном и нечетном индексе. partition не передает индекс функции, используемой для разделения. - person manojlds; 30.10.2011
comment
Я пробовал этот метод так: List.partition (fun i - ›(i% 2 = 0)) [1; 2; 4; 6; 7; 9] и получил такой результат ([2; 4; 6], [ 1; 7; 9]). Итак, это решение очень хорошо отделяет нечетные числа от четных. - person Daniel Hollinrake; 21.03.2017

Мои 2 ¢ в OCaml, так как баунти еще открыт.

Может быть, вы могли бы намекнуть, что вы хотите. Элегантность? FP? Рекурсия хвоста? Представление?

Изменить:

Я удалил более длинный раствор. Для работы List.partition отсутствовал предикат. Вот:

let so_split lst = 
  let flag = ref false in
  List.partition (fun e -> flag := not !flag; !flag) lst

Доработок какие-нибудь? Тестирование решения:

# so_split [1;2;4;6;7;9];;
- : int list * int list = ([1; 4; 7], [2; 6; 9])
person Str.    schedule 09.03.2014
comment
привет, в награде сказано, что один или несколько ответов являются образцовыми и заслуживают дополнительной награды. :) Это одновременно и награждение за ответ, и привлечение внимания к вопросу, чтобы он мог заинтересовать больше людей. - person Will Ness; 10.03.2014
comment
Хорошо, я заинтересовался этим. Когда кто-то спрашивает дополнительную информацию, вы обычно не повторяете известный текст, а перефразируете его, или лучше ... - person Str.; 10.03.2014

Для полноты картины приведу скучное и более настоятельное решение:

let splitList (list:int list) =
    let odds = [for i in 0..list.Length-1 do
                  if i%2=1 then
                    yield list.[i]]
    let evens = [for i in 0..list.Length-1 do
                    if i%2=0 then
                        yield list.[i]]
    odds,evens 
person Fsharp Pete    schedule 12.03.2014
comment
Определяют ли понимания списков лениво генерируемые последовательности в F #? - person Will Ness; 12.03.2014
comment
Вам нужно использовать seq {} для ленивых вещей, но это выглядит так же - person Fsharp Pete; 12.03.2014
comment
Мне просто было интересно, потребуются ли для этого два прохода по списку, или он может быть скомпилирован в один (ленивый) проход, который по ходу добавляет к тому или иному хвосту результирующей последовательности. (Я большой поклонник tailrecursion-modulo-cons). - person Will Ness; 12.03.2014
comment
Понимание списка создаст только один список (или последовательность). - person Fsharp Pete; 12.03.2014