SML, используя foldr для определения минимума списка - PullRequest
1 голос
/ 05 марта 2019

Мне нужно найти минимальное значение списка, используя foldr.

Вот код, который я написал:

fun minlist nil = nil
  | minlist (x::xs) = List.foldr (fn (y,z) => if y < z then y else z) x xs;

Однако я получаю сообщение об ошибке: «Перегружено> не может бытьприменяется к аргументу (ам) типа «список»

Я застрял на некоторое время.Любая помощь приветствуется

Ответы [ 2 ]

1 голос
/ 06 марта 2019

Ваше первое предложение говорит, что минимальное значение пустого списка - это список.
Таким образом, (fn (y,z) => if y < z then y else z) создает список, а y и z также должны быть списками.

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

0 голосов
/ 06 марта 2019

Нахождение минимум двух значений

Ваше выражение if y < z then y else z имеет встроенное имя Int.min (y, z).

Работа с пустыми списками

Вы обрабатываете пустой список как minlist nil = nil, что означает, что «наименьшее целое из пустого списка int - это пустой список». Но пустой список не является int и поэтому не может быть элементом в списке int или возвращаемым значением для функции, которая в противном случае возвращает наименьшие целые числа.

Как также говорит molbdnilo, вы можете либо жить с предупреждением компиляции (и рискнуть, если во время выполнения возникнет исключение Match, если вы когда-нибудь передадите функции пустой список), либо вызвать конкретное исключение, такое как Empty, когда учитывая пустой список. Ни то, ни другое не хорошо, но последнее, по крайней мере, проясняет проблему.

Запись этого без foldr может выглядеть так:

fun minimum [] = raise Empty
  | minimum [x] = x
  | minimum (x::xs) = Int.min (x, minimum xs)

Преобразование рекурсивной функции в сгиб

Учитывая некоторую рекурсивную функцию foo, которая зависит от некоторой функции bar и некоторого значения по умолчанию acc:

fun foo [] = acc
  | foo (x::xs) = bar (x, foo xs)

вы можете заметить сходство между minimum и foo:

  • acc равно x, минимальное значение
  • bar - это Int.min.

Это попытка обобщить схему рекурсии minimum.

С учетом функции foldr:

fun foldr f e []      = e
  | foldr f e (x::xs) = f (x, foldr f e xs);

вы можете заметить то же сходство:

  • f равно bar, но преобразовано в параметр
  • e равно acc, но преобразовано в параметр

Единственная вещь из minimum, которая не соответствует этой общей схеме рекурсии, - это обработка пустого списка. Так что вы все равно должны сделать это отдельно от foldr:

fun minimum [] = ...
  | minimum (x::xs) = foldr ...

Но остальное похоже.

Типы возврата с учетом ошибок

Третьим вариантом будет изменение сигнатуры типа функции на

val minimum : int list -> int option

, что, возможно, не разрешено вашим текущим упражнением.

Запись этого без foldr может выглядеть так:

fun minimum [] = NONE
  | minimum [x] = SOME x
  | minimum (x::xs) =
    case minimum xs of
         NONE => SOME x
       | SOME y => SOME (Int.min (x, y))

или еще лучше:

fun minimum [] = NONE
  | minimum [x] = SOME x
  | minimum (x::xs) = Option.map (fn y => Int.min (x, y)) (minimum xs)

Преобразование этой функции для использования foldr - это тот же процесс, но с другим f.

Хвостовая рекурсия

Функция minimum без складывания (повторяется сверху):

fun minimum [] = raise Empty
  | minimum [x] = x
  | minimum (x::xs) = Int.min (x, minimum xs)

проблема в том, что он в основном использует стековую память .

Это можно проиллюстрировать, оценив функцию вручную:

   minimum [1,2,3,4,5]
~> Int.min (1, minimum [2,3,4,5])
~> Int.min (1, Int.min (2, minimum [3,4,5]))
~> Int.min (1, Int.min (2, Int.min (3, minimum [4,5])))
~> Int.min (1, Int.min (2, Int.min (3, Int.min (4, minimum [5]))))
~> Int.min (1, Int.min (2, Int.min (3, Int.min (4, 5))))
~> Int.min (1, Int.min (2, Int.min (3, 4)))
~> Int.min (1, Int.min (2, 3))
~> Int.min (1, 2)
~> 1

Поскольку внешний Int.min не может быть вычислен до возвращения рекурсивного вызова, объем стековой памяти, используемой для вычисления функции, увеличивается пропорционально длине списка.

Вы можете избежать этого, используя набирающий аргумент:

fun minimum [] = raise Empty
  | minimum (y::ys) =
    let fun helper [] acc = acc
          | helper (x::xs) acc = helper xs (Int.min (x, acc))
    in helper ys y end

Оценка этой функции вручную:

   minimum [1,2,3,4,5]
~> helper [2,3,4,5] 1
~> helper [3,4,5] (Int.min (2, 1))
~> helper [3,4,5] 1
~> helper [4,5] (Int.min (3, 1))
~> helper [4,5] 1
~> helper [5] (Int.min (4, 1))
~> helper [5] 1
~> helper [] (Int.min (5, 1))
~> helper [] 1
~> 1

Поскольку Int.min является коммутативным, вы также можете решить это упражнение с помощью foldl вместо foldr точно так же, как вы делали это выше, и у вас был бы хвост-рекурсивный вариант, использующий меньше пространство стека.

...