List 我怎样才能在哈斯克尔与州政府合作?
我有一个简单的函数(实际上用于解决Euler项目的一些问题)。它将数字列表转换为十进制数List 我怎样才能在哈斯克尔与州政府合作?,list,haskell,fold,traversable,foldable,List,Haskell,Fold,Traversable,Foldable,我有一个简单的函数(实际上用于解决Euler项目的一些问题)。它将数字列表转换为十进制数 fromDigits :: [Int] -> Integer fromDigits [x] = toInteger x fromDigits (x:xs) = (toInteger x) * 10 ^ length xs + fromDigits xs 我意识到类型[Int]并不理想fromDigits应该能够接受其他输入,例如序列,甚至可能是可折叠的 我的第一个想法是用一种“状态折叠”来替换上面的
fromDigits :: [Int] -> Integer
fromDigits [x] = toInteger x
fromDigits (x:xs) = (toInteger x) * 10 ^ length xs + fromDigits xs
我意识到类型[Int]
并不理想fromDigits
应该能够接受其他输入,例如序列,甚至可能是可折叠的
我的第一个想法是用一种“状态折叠”来替换上面的代码。以上功能的正确(=最小)Haskell类别是什么?首先,折叠已经是关于携带一些状态<代码>可折叠的正是您要寻找的,不需要
状态
或其他单子
其次,在空列表上定义基本情况,然后在非空列表上定义基本情况,这将更为自然。现在的情况是,函数在空列表上没有定义(虽然它完全有效)。请注意,[x]
只是x:[]
的简写
在当前形式中,该函数几乎可以使用foldr
来表达。但是在foldl
中,列表或其部分不可用,因此无法计算length xs
。(在每一步计算length xs
也会使整个函数不必要地变成O(n^2)。)但如果您将过程设置为以相反的方式使用列表,则可以很容易地避免这种情况。函数的新结构可能如下所示:
fromDigits' :: [Int] -> Integer
fromDigits' = f 0
where
f s [] = s
f s (x:xs) = f (s + ...) xs
之后,尝试使用
foldl
来表示f
,最后将其替换为。应避免使用length
,并使用foldl
编写函数(或foldl'
):
从这一点来看,对任何可折叠函数的泛化应该是清楚的。这样一个简单的函数可以在其裸参数中携带其所有状态。携带一个累加器参数,操作就变得微不足道了
fromDigits :: [Int] -> Integer
fromDigits xs = fromDigitsA xs 0 # 0 is the current accumulator value
fromDigitsA [] acc = acc
fromDigitsA (x:xs) acc = fromDigitsA xs (acc * 10 + toInteger x)
解决这个问题的一个更好的方法是列出你的10种力量。使用
迭代
非常简单:
powersOf :: Num a => a -> [a]
powersOf n = iterate (*n) 1
然后你只需要将这些10的幂乘以数字列表中它们各自的值。使用zipWith(*)
很容易做到这一点,但首先必须确保顺序正确。这基本上只是意味着您应该对数字进行重新排序,使其按降序排列,而不是按升序排列:
zipWith (*) (powersOf 10) $ reverse xs
但是我们希望它返回一个整数
,而不是Int
,所以让我们从integral中通过一个映射
zipWith (*) (powersOf 10) $ map fromIntegral $ reverse xs
剩下的就是对它们进行总结
fromDigits :: [Int] -> Integer
fromDigits xs = sum $ zipWith (*) (powersOf 10) $ map fromIntegral $ reverse xs
或者是免费的球迷
fromDigits = sum . zipWith (*) (powersOf 10) . map fromIntegral . reverse
现在,您还可以使用fold,它基本上只是一个纯for循环,其中函数是您的循环体,初始值是初始状态,您提供的列表是您正在循环的值。在这种情况下,你的状态是一个总和,你有多大的权力。我们可以创建自己的数据类型来表示这一点,或者我们可以使用一个元组,第一个元素是当前总数,第二个元素是当前幂:
fromDigits xs = fst $ foldr go (0, 1) xs
where
go digit (s, power) = (s + digit * power, power * 10)
这大致相当于Python代码
def fromDigits(数字):
def go(数字,acc):
s、 功率=acc
返回(s+数字*功率,功率*10)
状态=(0,1)
对于数字中的数字:
状态=开始(数字,状态)
返回状态[0]
如果您真的决定使用正确的折叠方式,您可以将计算长度xs
与这样的计算结合起来(自由定义fromDigits[]=0
):
现在很明显,这相当于
fromDigits xn = fst $ foldr (\ x (y, l) -> (toInteger x * 10^l + y, l + 1)) (0, 0) xn
在使用fold编写递归函数时,向累加器中添加额外的组件或结果,并在fold返回时丢弃它的模式是非常普遍的
话虽如此,一个函数在第二个参数中总是严格的foldr
是一个非常非常糟糕的主意(过度的堆栈使用,可能是长列表上的堆栈溢出),你真的应该像其他一些答案所建议的那样,以foldl
的形式编写fromDigits
“fold with state”,可能您正在寻找的抽象是可遍历的
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
基本上,遍历采用类型为a->fb
的“有状态函数”,并将其应用于容器ta
中的每个函数,从而生成一个容器f(tb)
。在这里,f
可以是State
,您可以使用类型为Int->State Integer()的遍历函数
。它将构建一个无用的数据结构(在您的情况下是单元列表),但您可以放弃它。以下是使用Traversable解决问题的方法:
import Control.Monad.State
import Data.Traversable
sumDigits :: Traversable t => t Int -> Integer
sumDigits cont = snd $ runState (traverse action cont) 0
where action x = modify ((+ (fromIntegral x)) . (* 10))
test1 = sumDigits [1, 4, 5, 6]
然而,如果您真的不喜欢构建丢弃的数据结构,您可以将Foldable
与一些棘手的Monoid
实现结合使用:不仅存储计算结果,还存储10^n
,其中n
是转换为该值的位数。此附加信息使您能够进行梳理有两个值:
import Data.Foldable
import Data.Monoid
data Digits = Digits
{ value :: Integer
, power :: Integer
}
instance Monoid Digits where
mempty = Digits 0 1
(Digits d1 p1) `mappend` (Digits d2 p2) =
Digits (d1 * p2 + d2) (p1 * p2)
sumDigitsF :: Foldable f => f Int -> Integer
sumDigitsF cont = value $ foldMap (\x -> Digits (fromIntegral x) 10) cont
test2 = sumDigitsF [0, 4, 5, 0, 3]
我坚持第一个实现。虽然它构建了不必要的数据结构,但它更简短、更容易理解(就读者理解的可遍历的)。您的函数不使用状态,但如果使用状态,您可以使用数据。可折叠的,它提供可折叠的::(可折叠的t,Monad m)=>(b->a->mb->b->t a->m b
。顺便说一句,你函数的运行时非常糟糕。非常感谢!我更喜欢user5402的答案,因为它更简洁。@ruben.moor我不想马上给出答案,更多的是如何得到它的提示。
import Control.Monad.State
import Data.Traversable
sumDigits :: Traversable t => t Int -> Integer
sumDigits cont = snd $ runState (traverse action cont) 0
where action x = modify ((+ (fromIntegral x)) . (* 10))
test1 = sumDigits [1, 4, 5, 6]
import Data.Foldable
import Data.Monoid
data Digits = Digits
{ value :: Integer
, power :: Integer
}
instance Monoid Digits where
mempty = Digits 0 1
(Digits d1 p1) `mappend` (Digits d2 p2) =
Digits (d1 * p2 + d2) (p1 * p2)
sumDigitsF :: Foldable f => f Int -> Integer
sumDigitsF cont = value $ foldMap (\x -> Digits (fromIntegral x) 10) cont
test2 = sumDigitsF [0, 4, 5, 0, 3]