Как разделить список с заданным размером группы? - PullRequest
4 голосов
/ 09 ноября 2011

Я ищу лучший способ разделить список (или seq), чтобы группы имели заданный размер. например скажем, я хочу сгруппировать с размером 2 (это может быть любое другое число):

let xs = [(a,b,c); (a,b,d); (y,z,y); (w,y,z); (n,y,z)]
let grouped = partitionBySize 2 input
// => [[(a,b,c);(a,b,d)]; [(y,z,y);(w,y,z)]; [(n,y,z)]]

Очевидным способом реализации partitionBySize было бы добавление позиции к каждому кортежу в списке ввода, чтобы он стал

[(0,a,b,c), (1,a,b,d), (2,y,z,y), (3,w,y,z), (4,n,y,z)]

, а затем используйте GroupBy с

xs |> Seq.ofList |> Seq.GroupBy (function | (i,_,_,_) -> i - (i % n))

Однако это решение выглядит не очень элегантно для меня. Есть ли лучший способ реализовать эту функцию (может быть, с помощью встроенной функции)?

Ответы [ 6 ]

9 голосов
/ 09 ноября 2011

Кажется, что это повторяющийся шаблон, который не фиксируется ни одной функцией в базовой библиотеке F #. При решении подобных проблем ранее я определил функцию Seq.groupWhen (см. F # snippets ), которая превращает последовательность в группы. Новая группа запускается, когда предикат верен.

Вы можете решить проблему, используя Seq.groupWhen аналогично Seq.group (путем создания новой группы с четным индексом). В отличие от Seq.group, это эффективно, потому что Seq.groupWhen выполняет итерацию по входной последовательности только один раз:

[3;3;2;4;1;2;8] 
|> Seq.mapi (fun i v -> i, v) // Add indices to the values (as first tuple element)
|> Seq.groupWhen (fun (i, v) -> i%2 = 0) // Start new group after every 2nd element
|> Seq.map (Seq.map snd) // Remove indices from the values

Реализация функции напрямую с использованием рекурсии, вероятно, проще - решение от Джона делает именно то, что вам нужно, - но если вы хотите увидеть более общий подход, тогда Seq.groupWhen может быть интересным.

5 голосов
/ 09 октября 2015

List.chunkBySize (отзыв о шапке: Скотт Влашин ) теперь доступен и делает именно то, о чем вы говорите. Похоже, что новый с F # 4.0.

let grouped = [1..10] |> List.chunkBySize 3
// val grouped : int list list = 
//   [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]; [10]]

Seq.chunkBySize и Array.chunkBySize также доступны.

4 голосов
/ 10 ноября 2011

Вот хвосто-рекурсивная функция, которая перебирает список один раз.

let chunksOf n items =
  let rec loop i acc items = 
    seq {
      match i, items, acc with
      //exit if chunk size is zero or input list is empty
      | _, [], [] | 0, _, [] -> ()
      //counter=0 so yield group and continue looping
      | 0, _, _::_ -> yield List.rev acc; yield! loop n [] items 
      //decrement counter, add head to group, and loop through tail
      | _, h::t, _ -> yield! loop (i-1) (h::acc) t
      //reached the end of input list, yield accumulated elements
      //handles items.Length % n <> 0
      | _, [], _ -> yield List.rev acc
    }
  loop n [] items

Использование

[1; 2; 3; 4; 5]
|> chunksOf 2 
|> Seq.toList //[[1; 2]; [3; 4]; [5]]

Мне нравится элегантность подхода Томаса, но я сравнил обе наши функции, используя входной список из 10 миллионов элементов. Этот показался в 9 секунд против 22 для его. Конечно, как он признал, наиболее эффективный метод, вероятно, будет включать массивы / циклы.

3 голосов
/ 09 ноября 2011

А как насчет рекурсивного подхода?- требуется только один проход

let rec partitionBySize length inp dummy = 
    match inp with
    |h::t ->
        if dummy |> List.length < length then
            partitionBySize length t (h::dummy)
        else dummy::(partitionBySize length t (h::[]))
    |[] -> dummy::[]

Затем вызовите его с помощью partitionBySize 2 xs []

2 голосов
/ 09 ноября 2011
let partitionBySize size xs =
  let sq = ref (seq xs)
  seq {
    while (Seq.length !sq >= size) do
      yield Seq.take size !sq
      sq := Seq.skip size !sq
    if not (Seq.isEmpty !sq) then yield !sq
  }
  // result to list, if you want
  |> Seq.map (Seq.toList)
  |> Seq.toList

UPDATE

let partitionBySize size (sq:seq<_>) =
  seq {
    let e = sq.GetEnumerator()
    let empty = ref true;
    while !empty do
      yield seq { for i = 1 to size do
                    empty := e.MoveNext()
                    if !empty then yield e.Current
            }
  }

Версия среза массива:

let partitionBySize size xs =
  let xa = Array.ofList xs
  let len = xa.Length
  [
    for i in 0..size..(len-1) do
      yield ( if i + size >= len then xa.[i..] else xa.[i..(i+size-1)] ) |> Array.toList 
  ]
1 голос
/ 10 ноября 2011

Ну, я опоздал на вечеринку.Приведенный ниже код является хвостовой рекурсивной версией, использующей функции высокого порядка на List:

let partitionBySize size xs =
    let i = size - (List.length xs - 1) % size
    let xss, _, _ =
        List.foldBack( fun x (acc, ls, j) -> 
                                          if j = size then ((x::ls)::acc, [], 1) 
                                          else (acc, x::ls, j+1)
                       ) xs ([], [], i)
    xss

. Я выполнил тот же тест, что и Даниэль.Эта функция эффективна, хотя она в 2 раза быстрее, чем его подход на моей машине.Я также сравнил его с версией массива / цикла, они сравнимы по производительности.

Более того, в отличие от ответа Джона, эта версия сохраняет порядок элементов во внутренних списках.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...