Нужны идеи для преобразования императивного кода F# в функционал
У меня есть функция, написанная в императивном стиле, и я не могу понять, как преобразовать ее в более надежный функциональный подход.
Функция принимает последовательность строк и возвращает последовательность кортежей, где каждый кортеж состоит из элемента 2,7,12,.. и 5,10,15,.. из входных данных.
Пример:
Input = {"Lorem", "ipsum", "dolor", "set", "amet", "consectetuer", "adipiscing", "elit", "Aenean", "commodo", "ligula", "eget", "dolor", "Aenean", "massa"}
Ouput = {("ipsum", "amet"), ("adipiscing", "commodo"), ("eget", "massa")}
let convert (input : seq<string>) : seq<(string * string)> =
let enum = input.GetEnumerator()
let index = ref 0
let first = ref ""
let second = ref ""
seq {
while enum.MoveNext() do
let modIndex = !index % 5
index := !index + 1
if (modIndex % 2 = 0 && !first = "") then first := enum.Current
if (modIndex % 5 = 0 && !second = "") then second := enum.Current
if modIndex = 0 then
let result = (!first, !second)
first := ""
second := ""
yield result
}
Любая помощь или совет для отправной точки приветствуется.
3 ответа
Я не совсем понимаю, какое поведение вы хотите - каков алгоритм генерации индексов, которые вы хотите спарить? В любом случае, одно хорошее функциональное решение состоит в том, чтобы взять элементы, которые вы хотите соединить отдельно, а затем объединить их, используя Seq.zip
,
Ты можешь использовать Seq.mapi
добавить индексы к значениям, а затем использовать Seq.choose
чтобы получить значения с правильным индексом (и пропустить все остальные значения). Для жестко закодированных индексов вы можете написать что-то вроде:
let indexed = input |> Seq.mapi (fun i s -> i, s)
Seq.zip
(indexed |> Seq.choose (fun (i, v) -> if i=1 || i=6 || i=11 then Some v else None))
(indexed |> Seq.choose (fun (i, v) -> if i=4 || i=9 || i=14 then Some v else None))
Я использовал ваши цифры -1, потому что индексы от 0 - так что выше дает вам результаты, которые вы хотели. Вторая серия выглядит как кратные 5, так что, возможно, вы хотели i%5 = 4
генерировать вторые элементы:
let indexed = input |> Seq.mapi (fun i s -> i, s)
Seq.zip
(indexed |> Seq.choose (fun (i, v) -> if i=1 || i=6 || i=11 then Some v else None))
(indexed |> Seq.choose (fun (i, v) -> if i%5 = 4 then Some v else None))
Хотя я до сих пор не вижу общего механизма генерации первых элементов!
РЕДАКТИРОВАТЬ Еще одна идея - это первая последовательность, сгенерированная i*5 + 2
а второй i*5
? В этом случае ваш пример неверен, но вы можете написать это так:
let indexed = input |> Seq.mapi (fun i s -> i, s)
Seq.zip
(indexed |> Seq.choose (fun (i, v) -> if i%5 = 2 then Some v else None))
(indexed |> Seq.choose (fun (i, v) -> if i%5 = 0 then Some v else None))
... или, если вы хотите улучшить код, вы можете рефакторинг:
let filterNthElements div rem =
input |> Seq.mapi (fun i s -> i, s)
|> Seq.choose (fun (i, v) -> if i%div = rem then Some v else None)
Seq.zip (filterNthElements 5 2) (filterNthElements 5 0)
Вот более идиоматический способ сделать это. На самом деле, это одна строка; Я просто выровнял его для лучшей читаемости.
let Input = [ "Lorem"; "ipsum"; "dolor"; "set"; "amet"; "consectetuer";
"adipiscing"; "elit"; "Aenean"; "commodo"; "ligula"; "eget";
"dolor"; "Aenean"; "massa" ]
// Short solution that does not support more than two values
let Output1 =
Input
|> List.fold
(fun (i, l1, l2) x ->
if i=4 then 0, None, (l1.Value, x)::l2
elif i=1 then i+1, Some x, l2
else i+1, l1, l2
)
(0, None, [])
|> fun (_, _, elem) -> elem
|> List.rev
идея
Общая идея основана на трех этапах:
- Разделение списка на
List
кортежей, берут 2-ую и 5-ую струны. ВНИМАНИЕ! Если исходная длина данных не является множителем 5, конечный элемент будет потерян. - Отфильтровываем временные данные из тройки, беря третий элемент, что является нашей главной целью;
- Перевернуть список.
объяснение
Первая строка самая сложная.
Давайте определим наше состояние. Это будет тройка последовательного числа, string option
который содержит строки ##2, 7 и т. д. и "внешний" (string*string) list
который добавляется, когда мы встречаемся с элементами ##5, 10 и т. д.
Функция поместит 2-й, 7-й и т. Д. Элементы во "внутренний" string option
, или если i
равно 5, 10 и т. д., сформировать кортеж и добавить его к "внешнему" List
(отбрасывая внутреннее значение ради ясности).
Мы используем List.fold
и, следовательно, окончательный список должен быть полностью изменен.
Начальное состояние - это тройка (0, None, []). More info on
List.fold` в MSDN.
Вторая строка просто берет третий элемент из тройки. Я сделал это функцией, позволяющей связывать цепочки.
Третья строка меняет List
из-за характера ::
оператор.
По длине начального списка. Если он нашел "2-й" элемент, но не достиг "5-го", второй элемент тройки имеет значение. Вы можете обнаружить ошибочную ситуацию, проверив ее:
...
|> fun (_, temp, elem) ->
if temp.IsSome
then failwith "Data length must be a multiplier of 5"
else elem
...
Вот немного более длинный код, который поддерживает более двух элементов:
let Output2 =
Input
|> List.foldBack
(fun x (i, l1, l2) ->
if i = 4
then 0, [], (x::l1)::l2
else i+1, x::l1, l2
)
<| (0, [], [])
|> fun (_, _, elem) -> elem
|> List.choose
(function
| [_; first; _; _; second] -> Some (first, second)
| _-> None
)
Обратите внимание, что этот вариант не удаляет элементы во время первого вызова, поэтому вы можете получить более двух элементов.
ВАЖНО: список обрабатывается в обратном порядке, поэтому индекс элемента рассчитывается с конца ввода. Вы можете изменить его на List.fold
в стоимости или дальнейшего изменения списка, как в Output1
,
Следите за оператором обратной привязки <|
из-за подписи List.foldBack
,
Вы можете проверять ошибки аналогичным образом: проверяя, не является ли "внутренний" список пустым.
Я пришел из haskell, а не из F#, поэтому я приведу, вероятно, неверную идею кода F#:
Сначала я бы сгенерировал два списка из моего ввода:
let zeromod5 = filter (index == 0 % 5) input
let twomod5 = filter (index == 2 % 5) input
что должно привести к спискам
{ "ipsum", "adipiscing","eget"}
{ "amet", "commodo","massa" }
а затем заархивировать их, т.е. составить список пар чем-то вроде
zip zeromod5 twomod5
Редактировать:
Версия на Haskell:
zipWeird :: [String] -> [(String, String)]
zipWeird ss = zip twoMod5s zeroMod5s
where zeroMod5s = map fst $ filter (\(_,y) -> y `mod` 5 == 0) eSS
twoMod5s = map fst $ filter (\(_,y) -> y `mod` 5 == 2) eSS
eSS = zip ss [1..]
zipWeird2 :: [String] -> [(String, String)]
zipWeird2 ss = map fst $ filter (\(_,y) -> y `mod`5 ==1) ezSS
where zSS = zip (tail ss) (drop 4 ss)
ezSS = zip zSS [1..]
input :: [String]
input = words ("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, "++
"sed diam nonumy eirmod tempor invidunt ut labore et dolore "++
"magna aliquyam erat, sed diam voluptua. At vero eos et "++
"accusam et justo duo dolores et ea rebum. Stet clita kasd "++
"gubergren, no sea takimata sanctus est Lorem ipsum dolor sit "++
"amet.")
main :: IO ()
main = do
print $ zipWeird input
print $ zipWeird2 input