Как бы я вернуть значение из функции, которая перебирает цикл for в F # - PullRequest
4 голосов
/ 09 апреля 2011

Я пытаюсь перебрать массив и вернуть значение, как показано ниже.Но это дает мне ошибку в строке после оператора if.Он говорит: «Это выражение, как ожидалось, имеет тип блока, но имеет тип int»

let findMostSignificantBitPosition (inputBits:System.Collections.BitArray) =
for i = inputBits.Length - 1 to 0  do
    if inputBits.[i] then
        i
done

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

Ответы [ 6 ]

8 голосов
/ 09 апреля 2011
Циклы

for не должны возвращать значения, они выполняют операцию только фиксированное число раз, а затем возвращают () (единицу).Если вы хотите выполнить итерацию и, наконец, вернуть что-то, вы можете:

  • иметь вне цикла ссылку, в которую вы помещаете конечный результат при его получении, а затем после цикла возвращать ссылочный контент

  • напрямую использовать рекурсивную функцию

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

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

В F # также должна быть возможность написать вашу функцию в императивном стиле, используя yield, чтобы превратить ее в генератор, а затем, наконец,заставляя генератор получить результат.Это можно рассматривать как аналог техники OCaml, заключающейся в использовании исключения для выпрыгивания из цикла.

Редактировать: Хорошее решение, позволяющее избежать вопросов «преждевременной остановки», состоит в использованииленивая промежуточная структура данных, которая будет построена только до первого удовлетворительного результата.Это элегантный и хороший стиль сценариев, но все же менее эффективный, чем поддержка прямого выхода или простая рекурсия.Я думаю, это зависит от ваших потребностей;Эта функция должна использоваться в критическом пути?

Редактировать: Ниже приведен пример кода.Они OCaml и структуры данных разные (некоторые из них используют библиотеки Батареи ), но идеи те же.

(* using a reference as accumulator *)
let most_significant_bit input_bits =
  let result = ref None in
  for i = Array.length input_bits - 1 downto 0 do
    if input_bits.(i) then
      if !result = None then
        result := Some i
  done;
  !result

let most_significant_bit input_bits =
  let result = ref None in
  for i = 0 to Array.length input_bits - 1 do
    if input_bits.(i) then
      (* only the last one will be kept *)
      result := Some i
  done;
  !result

(* simple recursive version *)
let most_significant_bit input_bits =
  let rec loop = function
    | -1 -> None
    | i ->
      if input_bits.(i) then Some i
      else loop (i - 1)
  in
  loop (Array.length input_bits - 1)

(* higher-order traversal *)
open Batteries_uni
let most_significant_bit input_bits =
  Array.fold_lefti
    (fun result i ->
      if input_bits.(i) && result = None then Some i else result)
    None input_bits

(* traversal using an intermediate lazy data structure 
   (a --- b) is the decreasing enumeration of integers in [b; a] *)
open Batteries_uni
let most_significant_bit input_bits =
  (Array.length input_bits - 1) --- 0
  |> Enum.Exceptionless.find (fun i -> input_bits.(i))

(* using an exception to break out of the loop; if I understand
   correctly, exceptions are rather discouraged in F# for efficiency
   reasons. I proposed to use `yield` instead and then force the
   generator, but this has no direct OCaml equivalent. *)
exception Result of int
let most_significant_bit input_bits =
  try
    for i = Array.length input_bits - 1 downto 0 do
      if input_bits.(i) then raise (Result i)
    done;
    None
  with Result i -> Some i
4 голосов
/ 09 апреля 2011

Зачем использовать цикл, когда вы можете использовать функции высокого порядка?

Я бы написал:

let findMostSignificantBitPosition (inputBits:System.Collections.BitArray) =
    Seq.cast<bool> inputBits |> Seq.tryFindIndex id
Модуль

Seq содержит множество функций для управления коллекциями. Часто это хорошая альтернатива использованию императивных петель.

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

Тело цикла for является выражением типа unit. Единственное, что вы можете сделать, - это побочные эффекты (изменение изменяемого значения, печать ...).

В F # if then else аналогично ? : из языков Си. Части then и else должны иметь одинаковый тип, иначе это не имеет смысла в языке со статической типизацией. Когда else отсутствует, компилятор предполагает, что это else (). Таким образом, then должен иметь тип unit. Помещение значения в цикл for не означает return, потому что все является значением в F # (включая if then).

3 голосов
/ 09 апреля 2011

+ 1 для гаше
Вот несколько примеров на F #.Я добавил один (второй), чтобы показать, как yield работает с for в выражении последовательности, как упоминалось Гаше.

(* using a mutable variable as accumulator as per gasche's example *)
let findMostSignificantBitPosition (inputBits: BitArray) =
    let mutable ret = None // 0
    for i = inputBits.Length - 1 downto 0  do
        if inputBits.[i] then ret <- i
    ret

(* transforming to a Seq of integers with a for, then taking the first element *)
let findMostSignificantBitPosition2 (inputBits: BitArray) =
    seq {
        for i = 0 to inputBits.Length - 1 do
        if inputBits.[i] then yield i
    } |> Seq.head

(* casting to a sequence of bools then taking the index of the first "true" *)
let findMostSignificantBitPosition3 (inputBits: BitArray) =
    inputBits|> Seq.cast<bool>  |> Seq.findIndex(fun f -> f) 

Редактировать : версии, возвращающие Option

let findMostSignificantBitPosition (inputBits: BitArray) =
    let mutable ret = None
    for i = inputBits.Length - 1 downto 0  do
        if inputBits.[i] then ret <- Some i
    ret

let findMostSignificantBitPosition2 (inputBits: BitArray) =
    seq {
        for i = 0 to inputBits.Length - 1 do
        if inputBits.[i] then yield Some(i)
        else yield None
    } |> Seq.tryPick id

let findMostSignificantBitPosition3 (inputBits: BitArray) =
    inputBits|> Seq.cast<bool>  |> Seq.tryFindIndex(fun f -> f)
1 голос
/ 09 апреля 2011

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

случай отказа по опции

let findMostSignificantBitPosition (inputBits:System.Collections.BitArray) =
    let rec loop i =
        if i = -1 then
            None
        elif inputBits.[i] then 
            Some i
        else
            loop (i - 1)

    loop (inputBits.Length - 1)

let test = new BitArray(1)
match findMostSignificantBitPosition test with
| Some i -> printf "Most Significant Bit: %i" i
| None -> printf "Most Significant Bit Not Found"

случай отказа по исключению

let findMostSignificantBitPosition (inputBits:System.Collections.BitArray) =
    let rec loop i =
        if i = -1 then
            failwith  "Most Significant Bit Not Found"
        elif inputBits.[i] then 
            i
        else
            loop (i - 1)

    loop (inputBits.Length - 1)

let test = new BitArray(1)
try 
    let i = findMostSignificantBitPosition test
    printf "Most Significant Bit: %i" i
with
    | Failure msg -> printf "%s" msg

случай отказа по -1

let findMostSignificantBitPosition (inputBits:System.Collections.BitArray) =
    let rec loop i =
        if i = -1 then
            i
        elif inputBits.[i] then 
            i
        else
            loop (i - 1)

    loop (inputBits.Length - 1)

let test = new BitArray(1)
let i = findMostSignificantBitPosition test
if i <> -1 then
    printf "Most Significant Bit: %i" i
else
    printf "Most Significant Bit Not Found"
1 голос
/ 09 апреля 2011

Я бы порекомендовал использовать функцию более высокого порядка (как упомянуто Лораном) или написать рекурсивную функцию явно (это общий подход для замены циклов в F #).

Если вы хотите увидеть какую-нибудь причудливуюРешение F # (которое, вероятно, является лучшей версией использования некоторой временной ленивой структуры данных), тогда вы можете взглянуть на мою статью, в которой определяется императивный компоновщик вычислений для F #.Это позволяет вам написать что-то вроде:

let findMostSignificantBitPosition (inputBits:BitArray) = imperative {
    for b in Seq.cast<bool> inputBits do
      if b then return true
    return false }

Существуют некоторые издержки (как при использовании других временных ленивых структур данных), но это выглядит так же, как C # :-). РЕДАКТИРОВАТЬ Я также разместил образцы на F # Snippets: http://fssnip.net/40

0 голосов
/ 11 апреля 2011

Один из вариантов - использовать метод seq и findIndex как:

let findMostSignificantBitPosition (inputBits:System.Collections.BitArray) =
  seq {
      for i = inputBits.Length - 1 to 0  do
          yield inputBits.[i]
  } |> Seq.findIndex(fun e -> e)
...