сделать функцию с "если" точка-бесплатно
У меня есть задание в Хаскелле (нет, это не мое домашнее задание, я учусь для экзамена).
Задача такова:
Напишите беспунктовую функцию
numocc
, которая подсчитывает вхождения элемента в заданные списки. Например::numocc 1 [[1, 2], [2, 3, 2, 1, 1], [3]]
=[1, 2, 0]
Вот мой код:
addif :: Eq a => a -> Int -> a -> Int
addif x acc y = if x == y then acc+1 else acc
count :: Eq a => a -> [a] -> Int
count = flip foldl 0 . addif
numocc :: Eq a => a -> [[a]] -> [Int]
numocc = map . count
numocc
и count
являются "точечными", но они используют функцию addif
, которая не является.
Я понятия не имею, как я могу выполнить функцию addif
без точки. Есть ли способ сделать if
заявление точка-бесплатно? Может быть, есть трюк, который не используют if
?
6 ответов:
Я бы использовал тот факт, что вы можете легко преобразовать
Bool
вInt
, используяfromEnum
:addif x acc y = acc + fromEnum (x == y)
Теперь вы можете начать применять обычные приемы, чтобы сделать его точечным
-- Go prefix and use $ addif x acc y = (+) acc $ fromEnum $ (==) x y -- Swap $ for . when dropping the last argument addif x acc = (+) acc . fromEnum . (==) x
И так далее. Я не буду отнимать у вас все удовольствие от того, чтобы сделать его бесплатным, особенно когда есть инструменты, чтобы сделать это для вас.
В качестве альтернативы можно написать функцию типа
count x = sum . map (fromEnum . (==) x)
Который почти без очков, и есть трюки, которые приближают вас, хотя они становятся довольно неприятными быстро:
count = fmap fmap fmap sum map . fmap fmap fmap fromEnum (==)
Здесь я думаю, что на самом деле лучше использовать
fmap
вместо(.)
, хотя вы можете заменить каждыйfmap
на(.)
, и это будет тот же самый код. По существу,(fmap fmap fmap)
составляет один аргумент и функцию с двумя аргументами вместе, если вы вместо этого дадите ей имя.:
, вы можете записать это какcount = (sum .: map) . (fromEnum .: (==))
Сломано:
> :t fmap fmap fmap sum map Num a => (a -> b) -> [a] -> b
Таким образом, он принимает функцию из
b
в числовойa
, списокb
s и возвращаетa
, а не очень плохо.> :t fmap fmap fmap fromEnum (==) Eq a => a -> a -> Int
И этот тип может быть записан как
Eq a => a -> (a -> Int)
, что важно отметить. Это делает возвращаемый тип этой функции соответствующим входным даннымfmap fmap fmap sum map
сb ~ Int
, поэтому мы можем составить их, чтобы получить функцию типаEq a => a -> [a] -> Int
.
Почему бы и нет
numocc x = map (length . filter (== x)) = map ((length .) (filter (== x)) ) = map (((length .) . filter) (== x)) = map (((length .) . filter) ((==) x)) = map (((length .) . filter . (==)) x) = (map . ((length .) . filter . (==))) x = (map . (length .) . filter . (==)) x
А затем тривиальное eta-сокращение.
Одним из трюков было бы импортировать один из многих
if
функции , напр.Data.Bool.bool 1 0
( также найден вData.Bool.Extras
).Более загадочным трюком было бы использовать
Но я думаю, что самым простым трюком было бы просто не считать себя, а просто применять стандарт.Foreign.Marshal.Utils.fromBool
, что делает именно то, что вам нужно здесь. Или то же самое, менее загадочное:fromEnum
(Спасибо @bheklilr).length
функция послеfilter
Инг для номера.
Используя экземпляр
Enum
дляBool
, можно построить безынерционную замену для if, которую можно использовать в более общих случаях:chk :: Bool -> (a,a) -> a chk = ([snd,fst]!!) . fromEnum
Используя
chk
, мы можем определить другую версиюaddIf
:addIf' :: Eq a => a -> a -> Int -> Int addIf' = curry (flip chk ((+1),id) . uncurry (==))
Теперь мы можем просто заменить
chk
вaddIf'
:addIf :: Eq a => a -> a -> Int -> Int addIf = curry (flip (([snd,fst]!!) . fromEnum) ((+1),id) . uncurry (==))
Я думаю, что вы ищете
Data.Bool
bool
, который существует с 4.7.0.0 (2014-04-08).incif :: (Eq a, Enum counter) => a -> a -> counter -> counter incif = ((bool id succ) .) . (==)
Дополнительный
.
позволяет==
принять два параметра, прежде чем передать выражениеbool
.Поскольку порядок параметров различен, вам нужно использовать
incif
следующим образом:(flip . incif)
(интегрируя это в
incif
оставлено как упражнение для читателя. [Перевод: это не тривиально, и я пока не знаю, как. ;])
Помните, что в Haskell list понимания, если условные обозначения могут быть использованы в предложении result или в конце. Но, самое главное, охранники без Иф могут быть использованы для фильтрации результатов. Я использую пары из zip. Второй из пары-это номер списка. Он остается постоянным, пока элементы списка сравниваются с константой (k). Ваш результат [1,2,0] не включает номера списка 1, 2 или 3, потому что это очевидно из позиций сумм в списке результатов. Результат здесь не добавляет вхождения в каждый список, а перечисляет их для каждого списка.
nocc k ls = [ z | (y,z) <- zip ls [1..length ls], x <- y, k == x] nocc 1 [[1, 2], [2, 3, 2, 1, 1], [3]]
[1,2,2] -- читать как [1,2,0] или 1 в списке 1, 2 в списке 2 и 0 в списке 3