Хаскелл Линейный Поиск (возвращающий индекс)
Итак, я хочу, чтобы моя функция возвращала индекс (первый, если он появляется несколько раз в списке) заданного значения. Кажется, он работает только для пустого списка или если данный элемент имеет индекс 0. Не могли бы вы дать мне знать, что я делаю неправильно?
linsearch _ [] = -1
linsearch y (x:xs) = head [i | let j = length xs, i <- [0..j-1], y == x]
Спасибо.
3 ответа
Возврате -1
представлять неудачный поиск - это то, что вы делаете на языке с плохой системой типов. Кроме того, нет причин искать все элементы, если вы заботитесь только о первом. В дальнейшем, head
потерпит неудачу, если результирующий список будет пустым.
linsearch :: Eq a => a -> [a] -> Maybe Int
linsearch _ [] = Nothing
linsearch y (x:xs) | y == x = Just 0
| otherwise = fmap (+ 1) (linsearch y xs)
Пустой список явно терпит неудачу: return Nothing
, С непустым списком проверьте, является ли первый элемент тем, который вы ищете; если так, вернись Just 0
, Иначе, y
может или не может быть в xs
; рекурсивно выяснить. Вы добавите 1 к этому результату для учета xs
будучи "сдвинутой" версией исходного списка. Вам нужно использовать fmap
потому что вы не будете добавлять 1 к некоторому целому числу y
; вы будете добавлять его к "завернутому" значению, как Just y
или, возможно, Nothing
если y
действительно нет в списке. Заметка
fmap (+1) Nothing == Nothing
fmap (+1) (Just 3) == Just 4
TL;DR: решение находится в конце поста.
Вам не нужен шаблон декомпозиции списка в вашем определении, просто разберитесь со списком в целом через понимание списка:
linsearch :: Eq a => a -> [a] -> Int
linsearch _ [] = -1
linsearch y xs = head [i | let j = length xs, i <- [0..j-1],
-- y == x -- x? what is that?
y == (xs!!i)]
Сейчас linsearch 3 [0..9]
возвращается 3
, как это должно. Но linsearch 3 [0..]
не возвращается вообще - он теряется при попытке вычислить длину списка, которая на самом деле здесь вообще не нужна! Более того, отказ от вычисления длины заставляет нас перестроить алгоритм из его текущей квадратичной формы в гораздо лучшую линейную форму:
linsearch :: Eq a => a -> [a] -> Int
linsearch _ [] = -1
linsearch y xs = head [i | (x,i) <- zip xs [0..], y == x]
linsearch 3 [0..]
теперь успешно возвращается 3
, как и должно быть.
linsearch 3 [0,2..]
хотя все еще расходится (то есть никогда не возвращается), потому что Haskell не знает - и не хочет этого - что нет смысла искать в упорядоченном увеличивающемся списке, пока не проходит самый первый элемент, который больше, чем тот, который мы ищем за. Это так, потому что [a]
тип списков, а не упорядоченных списков.
Мы могли бы также определить такой вариант, например,
linsearchOrdered :: Ord a => a -> [a] -> Int
linsearchOrdered y xs = linsearch y $ takeUntil (> y) xs
takeUntil :: (a -> Bool) -> [a] -> [a]
takeUntil p xs = foldr (\x r -> if not (p x) then x:r else [x]) [] xs
И, конечно же, он работает сейчас:
> linsearchOrdered 3 [0,2..]
*** Exception: Prelude.head: empty list
> takeUntil (> 3) [0,2..]
[0,2,4]
it :: (Ord a, Num a, Enum a) => [a]
> linsearch 3 [0,2,4]
*** Exception: Prelude.head: empty list
чего ждать? Откуда исходит ошибка? Это происходит от вашего использования head
: так как не было 3
нашел в [0,2,4]
Код звонка head []
что является ошибкой
Вместо этого мы можем использовать take 1
и преобразовать его результат в Maybe a
со стандартным использованием listToMaybe
:
import Data.Maybe
linsearch :: (Eq a) => a -> [a] -> Maybe Int
linsearch y xs = listToMaybe $ take 1 [i | (x,i) <- zip xs [0..], y == x]
Хорошо, теперь это работает:
> linsearchOrdered 3 [0,2..]
Nothing
> linsearchOrdered 4 [0,2..]
Just 2
Обратите внимание, что тип возвращаемого значения теперь другой. Вместо использования специального значения мы используем тип обтекания, чтобы указать на успех (с Just
) или неудача (с Nothing
):
data Maybe a = Nothing | Just a
Если вы действительно хотите свой оригинальный дизайн, мы можем закодировать его как
linsearch y xs = head $ [i | (x,i) <- zip xs [0..], y == x] ++ [-1]
Хаскель так ленив head
будет (безопасно, сейчас, с ++ [-1]
) остановиться на первом соответствующем элементе (если это вообще возможно). Там почти нет дублирующих усилий, которые будут потрачены впустую, как с использованием head
а также takeUntil
,
Оба складки, хотя. zip
может быть закодирован как сгиб, так что linsearch
складка тоже. И композицию сгибов можно объединить в один сгиб, объединив функции редуктора, превратив все это в явно однопроходный алгоритм, если это необходимо.
Есть функция elemIndex
в Data.List
который делает именно то, что вы хотите. Ваша функция может быть написана следующим образом:
import Data.List
linsearch y xs = elemIndex y xs
elemIndex
возвращает Maybe Int
, Так зовет linsearch 5 []
доходность Nothing
в то время как linsearch 5 [3,4,5]
доходность Just 2
, Примечание, приведенная выше реализация позволяет отбросить xs
: linsearch y = elemIndex y
является действительным. Теперь, если вы хотите вернуть только Int
мы можем "извлечь" значение из Maybe
используя case
выражение:
linsearch y xs = caseExtract $ elemIndex y xs
where
caseExtract m = case m of
Just a -> a
Nothing -> -1
Если вы хотите по-настоящему модно, вы можете пойти "без очков", бросив xs
и используя композицию(.)
с fromMaybe
от Data.Maybe
:
import Data.List
import Data.Maybe
linsearch y = fromMaybe (-1) . elemIndex y
Преимущество использования библиотечных функций, таких как elemIndex
в том, что они безопасны, в отличие от head
который будет бум, если пропущен пустой список. Ссылка: Haskell Report LYAH Data.List reddit небезопасная голова от Maybe