Совместное использование элементов списка и индексов
Мне всегда было неудобно иметь функцию или выражение, которое требует использования значений, а также индексов списка (или массива, применяемого точно так же) в Haskell.
я написал validQueens
ниже, экспериментируя с проблемой N-ферзей здесь...
validQueens x =
and [abs (x!!i - x!!j) /= j-i | i<-[0..length x - 2], j<-[i+1..length x - 1]]
Меня не волновало использование индексации, всех плюсов, минусов и т. Д. Это кажется неаккуратным. Я придумал следующее:
enumerate x = zip [0..length x - 1] x
validQueens' :: [Int] -> Bool
validQueens' x = and [abs (snd j - snd i) /= fst j - fst i | i<-l, j<-l, fst j > fst i]
where l = enumerate x
быть вдохновленным питоном enumerate
(не то, что заимствование императивных понятий обязательно хорошая идея). Кажется лучше в концепции, но snd
а также fst
повсюду вроде отстой. Это также, по крайней мере, на первый взгляд, дороже как во времени, так и в пространстве. Я не уверен, нравится ли мне это лучше.
Короче говоря, я не очень доволен ни
- Итерация по индексу, ограниченному по длине или, что еще хуже, по одному и по два
- Индекс-элемент кортежей
Кто-нибудь нашел шаблон, который они находят более элегантным, чем любой из вышеперечисленных? Если нет, то есть ли убедительная причина, по которой один из вышеперечисленных методов является лучшим?
2 ответа
Заимствование enumerate
это хорошо и поощряется. Однако это можно сделать немного ленивее, отказавшись вычислять длину аргумента:
enumerate = zip [0..]
(На самом деле, просто использовать zip [0..]
без названия enumerate
Мне непонятно, почему вы думаете, что ваш второй пример должен быть более дорогостоящим во времени или пространстве. Помните: индексирование - это O(n), где n- это индекс. Ваша жалоба на громоздкость fst
а также snd
оправдано и может быть исправлено с помощью сопоставления с шаблоном:
validQueens' xs = and [abs (y - x) /= j - i | (i, x) <- l, (j, y) <- l, i < j]
where l = zip [0..] xs
Теперь вы можете быть немного обеспокоены эффективностью этого двойного цикла, так как (j, y) <- l
будет бегать по всему позвоночнику l
, когда на самом деле мы просто хотим, чтобы это началось, где мы остановились с (i, x) <- l
, Итак, давайте напишем функцию, которая реализует эту идею:
pairs :: [a] -> [(a, a)]
pairs xs = [(x, y) | x:ys <- tails xs, y <- ys]
Сделав эту функцию, ваша функция не слишком сложна для адаптации. Вытягивая предикат в его собственную функцию, мы можем использовать all
вместо and
:
validSingleQueen ((i, x), (j, y)) = abs (y - x) /= j - i
validQueens' xs = all validSingleQueen (pairs (zip [0..] xs))
Или, если вы предпочитаете бессмысленную запись:
validQueens' = all validSingleQueen . pairs . zip [0..]
Кортежи с индексными элементами - довольно распространенная вещь в Haskell. Так как zip
останавливается, когда останавливается первый список, вы можете записать их как
enumerate x = zip [0..] x
который одновременно более элегантен и эффективен (так как не вычисляет length x
впереди). На самом деле я бы даже не стал называть это, так как zip [0..]
это так коротко.
Это определенно более эффективно, чем итерация по списку для списков, потому что !!
является линейным во втором аргументе из-за списков, являющихся связанными списками.
Другой способ сделать вашу программу более элегантной - использовать сопоставление с образцом вместо fst
а также snd
:
validQueens' :: [Int] -> Bool
validQueens' x = and [abs (j2 - i2) /= j1 - i1 | (i1, i2) <-l, (j1, j2) <-l, j1 > i1]
where l = zip [0..] x