сделать функцию с "если" точка-бесплатно
У меня есть задание в Хаскелле (нет, это не мое домашнее задание, я учусь для экзамена).
Задача такова:
Напишите беспунктовую функцию
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, списокbs и возвращает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.Boolbool, который существует с 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