сделать функцию с "если" точка-бесплатно


У меня есть задание в Хаскелле (нет, это не мое домашнее задание, я учусь для экзамена).

Задача такова:

Напишите беспунктовую функцию 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 13

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