Возвращаемое значение в F # - неполная конструкция - PullRequest
7 голосов
/ 10 марта 2010

Я пытаюсь выучить F #. Я начинающий, так что это может быть прогулка для вас, ребята:)

У меня есть следующая функция:

let removeEven l = 
 let n = List.length l;
 let list_ = [];
 let seq_ = seq { for x in 1..n do if x % 2 <> 0 then yield List.nth l (x-1)}
 for x in seq_ do
  let list_ = list_ @ [x];
 list_;

Он принимает список и возвращает новый список, содержащий все числа, который помещается по нечетному индексу в исходном списке, поэтому removeEven [x1;x2;x3] = [x1;x3]

Однако я получаю свое любимое сообщение об ошибке: Incomplete construct at or before this point in expression...

Если я добавлю печать в конец строки вместо list_:

...
print_any list_;

проблема исправлена. Но я не хочу печатать список, я хочу вернуть это!

Что вызывает это? Почему я не могу вернуть свой список?

Ответы [ 5 ]

6 голосов
/ 10 марта 2010

Чтобы ответить на ваш вопрос первым, компилятор жалуется, потому что внутри цикла for есть проблема. В F # let служит для объявления значений (которые неизменны и не могут быть изменены позже в программе). Это не оператор, поскольку в C # - let может использоваться только как часть другого выражения. Например:

let n = 10
n + n

Фактически означает, что вы хотите, чтобы символ n ссылался на значение 10 в выражении n + n. Проблема с вашим кодом в том, что вы используете let без каких-либо выражений (возможно, потому что вы хотите использовать изменяемые переменные):

for x in seq_ do 
  let list_ = list_ @ [x]  // This isn't assignment! 
list_

Проблемная строка является неполным выражением - использование let таким способом недопустимо, поскольку оно не содержит никакого выражения (значение list_ не будет доступно из любого кода). Вы можете использовать mutable переменную для исправления вашего кода:

let mutable list_ = [] // declared as 'mutable'
let seq_ = seq { for x in 1..n do if x % 2 <> 0 then yield List.nth l (x-1)}    
for x in seq_ do    
  list_ <- list_ @ [x] // assignment using '<-'

Теперь, это должно работать, но это не совсем функционально, потому что вы используете императивную мутацию. Более того, добавление элементов с использованием @ на функциональных языках действительно неэффективно. Итак, если вы хотите сделать свой код функциональным, вам, вероятно, придется использовать другой подход. Оба других ответа демонстрируют отличный подход, хотя я предпочитаю пример Джоэла, потому что индексирование в список (в решении от Chaos) также не очень функционально (нет арифметики с указателями, поэтому она будет также медленнее) .

Вероятно, наиболее классическим функциональным решением было бы использование функции List.fold, которая объединяет все элементы списка в один результат, проходя слева направо:

[1;2;3;4;5] 
  |> List.fold (fun (flag, res) el -> 
     if flag then (not flag, el::res) else (not flag, res)) (true, [])
  |> snd |> List.rev

Здесь состояние, используемое во время агрегации, является логическим флагом, указывающим, следует ли включать следующий элемент (на каждом шаге мы переворачиваем флаг, возвращая not flag). Второй элемент - это агрегированный до сих пор список (мы добавляем элемент на el::res, только когда установлен flag. После возврата fold мы используем snd, чтобы получить второй элемент кортежа (агрегированный список) и переверните его, используя List.rev, потому что он был собран в обратном порядке (это более эффективно, чем добавление в конец с помощью res@[el]).

6 голосов
/ 10 марта 2010

Редактировать: Если я правильно понимаю ваши требования, вот версия вашей функции, выполненная в функциональном, а не императивном стиле, которая удаляет элементы с нечетными индексами.

let removeEven list =
    list
    |> Seq.mapi (fun i x -> (i, x))
    |> Seq.filter (fun (i, x) -> i % 2 = 0)
    |> Seq.map snd
    |> List.ofSeq

> removeEven ['a'; 'b'; 'c'; 'd'];;
val it : char list = ['a'; 'c']
2 голосов
/ 10 марта 2010

Я думаю, это то, что вы ищете.

let removeEven list = 
    let maxIndex = (List.length list) - 1;
    seq { for i in 0..2..maxIndex -> list.[i] }
    |> Seq.toList

Тесты

val removeEven : 'a list -> 'a list

> removeEven [1;2;3;4;5;6];;
val it : int list = [1; 3; 5]
> removeEven [1;2;3;4;5];;
val it : int list = [1; 3; 5]
> removeEven [1;2;3;4];;
val it : int list = [1; 3]
> removeEven [1;2;3];;
val it : int list = [1; 3]
> removeEven [1;2];;
val it : int list = [1]
> removeEven [1];;
val it : int list = [1]
0 голосов
/ 10 марта 2010

Еще одна альтернатива, которая (по моим расчетам) немного медленнее, чем у Джоэла, но она короче:)

let removeEven list =
    list
    |> Seq.mapi (fun i x -> (i, x))
    |> Seq.choose (fun (i,x) -> if i % 2 = 0 then Some(x) else None)
    |> List.ofSeq
0 голосов
/ 10 марта 2010

Вы можете попробовать подход сопоставления с образцом. Я не использовал F # некоторое время и не могу сейчас что-то тестировать, но это будет примерно так:

let rec curse sofar ls =
    match ls with
    | even :: odd :: tl -> curse (even :: sofar) tl
    | even :: [] -> curse (even :: sofar) []
    | [] -> List.rev sofar

curse [] [ 1; 2; 3; 4; 5 ]

Это рекурсивно отбирает четные элементы. Я думаю. Я бы, вероятно, использовал подход Джоэля Мюллера. Я не помню, есть ли основанная на индексах функция filter, но это, вероятно, было бы идеальным для использования или для создания, если ее нет в библиотеках.

Но в общем случае списки на самом деле не предназначены для индексации. Для этого и нужны массивы. Если вы подумаете, для какого алгоритма требуется список, в котором удалены четные элементы, возможно, возможно, что на этапах, предшествующих этому требованию, элементы могут быть объединены в пары, например:

[ (1,2); (3,4) ]

Было бы тривиально вывести четные «индексированные» элементы:

thelist |> List.map fst // take first element from each tuple

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

...