Разделяне на списък с елементи на два списъка с нечетни и четни индексирани елементи

Бих искал да направя функция, която приема списък и връща два списъка: първият съдържа всеки нечетен елемент, а вторият съдържа всеки четен елемент.

Например, ако има [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
По дяволите, това е много хубаво решение. Отне ми няколко секунди, за да разбера как работи, но трябва да призная, че това решение е много елегантно (y). - 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]); [a;b;c;d] --> ([a;c] , [b;d])... - 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
Проблемът с тази реализация обаче е, че не е рекурсивно на опашката, тя причинява препълване на стека в списъци от ~80 000 елемента. - 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 (забавно 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