F # изящно складывается без первого или последнего элемента? - PullRequest
1 голос
/ 19 апреля 2020

Скажем, у вас есть список строк: ["a"; "B"; "c"] и вы хотите преобразовать ее в одну строку, например так: "a, b, c" обратите внимание, что последняя запятая отсутствует. Я обнаружил, что этот случай возникает снова и снова для меня, подумайте обо всех языках программирования, которые не позволяют использовать запятую, и вы создаете какой-то генератор кода. Я обычно получаю что-то вроде:

let listOfThings = ["a";"b";"c"]
let folded =
    listOfThings
    |> List.map (fun i -> i + ",")
    |> List.fold (+) ""
    |> (fun s -> s.Substring(0, s.Length - 1))

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

Ответы [ 3 ]

4 голосов
/ 20 апреля 2020

A fold рекурсивно применяет вашу функцию сворачивания ко всем значениям списка, начиная с начального состояния, которое в данном случае вам особенно не нужно.

Проще использовать сокращение, использующее заголовок списка в качестве его начального состояния:

listOfThings |> List.reduce (fun sum cur -> sum + "," + cur) // "a,b,c"

Незначительный недостаток заключается в том, что, поскольку он использует заголовок списка, вызов метода visible с пустым списком завершится неудачно. Вы можете уменьшить это с помощью проверки пустого списка.

Без встроенных модулей, как вы описали, мы пропускаем добавление запятой в конце для последнего элемента:

let rec join = function
| []    -> ""
| [x]   -> x
| x::xs -> x + ","  + join xs

["a"; "b"; "c"] |> join // a,b,c

Однако наиболее эффективным методом будет использование String.Join, который внутренне использует StringBuilder, тогда как reduce выделяет новую строку для каждого вызова:

String.Join(",", listOfThings) // "a,b,c"
2 голосов
/ 20 апреля 2020

Сокращение, применяемое к элементам списка, обязательно того же типа, что и они. Напротив, аккумулятор (также называемый состоянием) fold может быть другого типа, который является более универсальным. Подписи делают это очевидным:

val reduce: ('a -> 'a -> 'a) -> 'a list -> 'a
val fold: ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a

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

let fold1 folderN folder0 state = function
| [] -> state
| x::xs -> List.fold folderN (folder0 state x) xs
// val fold1 :
//   folderN:('a -> 'b -> 'a) ->
//     folder0:('a -> 'b -> 'a) -> state:'a -> _arg1:'b list -> 'a

Теперь мы можем свернуть список или даже использовать StringBuilder:

([], ["a";"b";"c"])
||> fold1 
    (fun s t -> t::", "::s)
    (fun s t -> t::s)
|> List.rev
// val it : string list = ["a"; ", "; "b"; ", "; "c"]

(System.Text.StringBuilder(), ["a";"b";"c"])
||> fold1 
    (fun s t -> s.Append(", ").Append t)
    (fun s t -> s.Append t) 
|> string
// val it : string = "a, b, c"
0 голосов
/ 20 апреля 2020

Действительно, есть встроенная функция, которая делает это:

let s = [ "a"; "b"; "c" ]
String.concat ", " s // "a, b, c"
...