Мне нужно упаковать данные вот так:

let data = [1; 2; 2; 3; 2; 2; 2; 4]
let packed = [(1, 1); (2, 2); (3, 1); (2, 3); (4, 1)]

Где каждый элемент говорит, сколько раз он существует до следующего. Однако он должен работать с несмежными дубликатами.

Я могу работать с этим классическим императивным кодом, но задаюсь вопросом, как это сделать функционально.

Кроме того, Seq.countBy не работает, потому что он учитывает все значения

f#
5
mamcx 28 Дек 2015 в 03:35

3 ответа

Лучший ответ

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

Рекурсия

Хотя я не знаю, как выглядит ваша императивная версия, вот рекурсивная версия:

let pack xs =
    let rec imp acc = function
    | [] -> acc
    | h::t ->
        match acc with
        | [] -> imp [(h, 1)] t
        | (i, count) :: ta ->
            if h = i
            then imp ((i, count + 1) :: ta) t
            else imp ((h, 1) :: (i, count) :: ta) t
    xs |> imp [] |> List.rev

Эта функция имеет тип 'a list -> ('a * int) list when 'a : equality. Он использует частную «функцию реализации» под названием imp для выполнения работы. Эта функция является рекурсивной и повсюду распределяет аккумулятор (называемый acc). Этот аккумулятор представляет собой список результатов, имеющий тип ('a * int) list.

Если список аккумуляторов пуст, заголовок исходного списка (h), а также счетчик 1 создается как кортеж как единственный элемент обновленного аккумулятора, а imp рекурсивно вызывается с этим обновленным аккумулятором.

Если аккумулятор уже содержит хотя бы один элемент, элемент извлекается с помощью сопоставления с образцом, и элемент в этом кортеже (i) сравнивается с h. Если h = i, аккумулятор обновляется; в противном случае на acc сжимается новый кортеж. Однако в обоих случаях imp рекурсивно вызывается с новым аккумулятором.

Вы можете вызвать его с помощью list , эквивалентного исходному кортежу, следующим образом:

> pack [1; 2; 2; 3; 2; 2; 2; 4];;
val it : (int * int) list = [(1, 1); (2, 2); (3, 1); (2, 3); (4, 1)]

Сложить

Если у вас есть рекурсивная версия, у вас часто есть рецепт версии, использующей свертку. В этом случае, поскольку вышеупомянутая функция pack должна в конце перевернуть аккумулятор (используя List.rev), наиболее подходящим является правое сворачивание . В F # это делается с помощью встроенной функции List.foldBack:

let pack' xs =
    let imp x = function
        | (i, count) :: ta when i = x -> (i, count + 1) :: ta
        | ta -> (x, 1) :: ta
    List.foldBack imp xs []

В этом случае функция, переданная в List.foldBack, слишком сложна для передачи в качестве анонимной функции, поэтому я решил определить ее как частную внутреннюю функцию. Это эквивалентно рекурсивной функции imp, используемой вышеупомянутой функцией pack, но вы заметите, что она не должна вызывать себя рекурсивно. Вместо этого ему просто нужно вернуть новое значение для аккумулятора.

Результат тот же:

> pack' [1; 2; 2; 3; 2; 2; 2; 4];;
val it : (int * int) list = [(1, 1); (2, 2); (3, 1); (2, 3); (4, 1)]
6
Mark Seemann 28 Дек 2015 в 15:16

Вы можете заставить Seq.countBy работать, включив в его аргумент некоторую позиционную информацию. Конечно, тогда вам нужно будет вернуться к исходным данным.

[1; 2; 2; 3; 2; 2; 2; 4]
|> Seq.scan (fun (s, i) x ->
    match s with
    | Some p when p = x -> Some x, i
    | _ -> Some x, i + 1 ) (None, 0)
|> Seq.countBy id
|> Seq.choose (function 
| (Some t, _), n -> Some(t, n)
| _ -> None )
|> Seq.toList
// val it : (int * int) list = [(1, 1); (2, 2); (3, 1); (2, 3); (4, 1)]
1
kaefer 28 Дек 2015 в 08:55

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

let groupFunc list = 
    let rec groupFuncRec acc lst init count =
        match lst with
        | [] -> List.rev acc
        | head::[] when head = init
            -> groupFuncRec ((init, count)::acc) [] 0 0
        | head::[] when head <> init
            -> groupFuncRec ((head, 1)::acc) [] 0 0
        | head::tail when head = init 
            -> groupFuncRec acc tail head (count+1)
        | head::tail when head <> init
            -> groupFuncRec ((init, count)::acc) tail head 1
    let t = List.tail list
    let h = List.head list
    groupFuncRec [] t h 1

Когда я запускаю функцию с вашими образцами данных, я получаю ожидаемый результат:

 list = [(1, 1); (2, 2); (3, 1); (4, 1)]
1
Community 23 Май 2017 в 12:03