说我有以下定义
data Book = Book {id :: Int, title :: String}
type Shelf = [Book]
假设我有一个假设函数(upd用于更新)
updShelf :: Shelf -> Shelf
updShelf all@(book : books) = updBook book : updShelf books
到目前为止一切都很好。现在让我们说更新书功能需要参考 更新 之前预订三本书,即书架上第5位书的updateBook需要参考位置2的书(假设前三本书不需要这样的参考更新)。没问题,我说,并修改我的代码:
updShelf :: Shelf -> Shelf
updShelf all@(book : books) prevBook = updBook book prevBook : updShelf books
where prevBook = ???
我需要帮助的是prevBook功能。虽然我甚至不确定我是否正确地接近这个问题。所以,如果你们有更好的建议以不同的方式解决这个问题,我们将非常感激
编辑:
Thomas M. DuBuisson:你的solution对我不起作用。原因如下: 假设初始货架(全部)状态为
Book {id=1, title="a"}
Book {id=2, title="b"}
Book {id=3, title="c"}
Book {id=4, title="d"}
Book {id=5, title="e"}
Book {id=6, title="f"}
Book {id=7, title="g"}
Book {id=8, title="h"}
然后(drop 3 partialUpdate)是(仅使用id而不是整本书语句):
updBook 4
updBook 5
updBook 6
updBook 7
updBook 8
zipWith'($)(drop 3 partialUpdate)(全部)是:
updBook 4 1
updBook 5 2
updBook 6 3
updBook 7 4 -> YIKES! Older version of book 4!
updBook 8 5 -> YIKES! Older version of book 5!
就我而言,我需要根据第4和第5册的已更新版本更新第7和第8本书,而不是未更新的第7和第5本。我希望你理解我的意思。
答案 0 :(得分:11)
这个技巧与tying the knot有关:我们将在计算答案时使用答案。出于说明的目的,我将改为使用type Book = Int
。
updateShelf :: Shelf -> Shelf
updateShelf shelf = answer where
answer = zipWith updateBook shifted shelf
shifted = replicate 3 Nothing ++ map Just answer
-- some stupid implementation just for illustration
updateBook :: Maybe Book -> Book -> Book
updateBook Nothing current = current + 1
updateBook (Just threeBack) current = current + threeBack + 1
现在,在ghci
中,我们可以验证updateShelf
是否确实使用了更新版本:
*Main> updateShelf [1,10,100,1000,10000]
[2,11,101,1003,10012]
如您所见,前三个是1+1
,10+1
和100+1
,其余两个是1000+(1+1)+1
和10000+(10+1)+1
,因此,正如您所希望的那样,使用更新的先前值。
答案 1 :(得分:6)
这里没有结点,没有信息回流,没有传递对尚未定义的值的前向引用。恰恰相反,我们回顾已知的已经计算的值。它甚至没有懒惰的评估。这样:
updShelf shelf@(a : b : c : t) = xs where
xs = a : b : c : zipWith updBook t xs
就是你所需要的。所有它正在“做”,是将显式后向指针保持在正在生成的序列中,返回三个槽口。 “后退指针非常简单”,引用haskellwiki's page来打结。
updBook
的每次调用都会在此处收到两个参数 - 一个是原始列表中位置i+3
的一个项目,另一个是位置i
的新更新项目。
将它与这段Haskell传说进行比较:
g (a,b) = xs where
xs = a : b : zipWith (+) xs (tail xs)
这里也没有打结。
答案 2 :(得分:5)
首先:您的updShelf
是map updBook
。
第二:我认为书籍列表可能不是您问题的最佳数据结构,因为列表不支持随机访问操作。如果您在计算中的任何时候确实需要随机访问,您可能想尝试使用数组 - 请参阅Data.Array
。
现在我的主要观点:你正在尝试做的事情 - 有一个计算消耗部分自己的结果 - 在Haskell社区经常被称为“打结”或“信用卡转型“(立即购买,稍后付款)。基本上,它归结为以下类型的表达式在Haskell中有效:
let result = f result in result -- apply f to its own result
这怎么可能有效?好吧,首先,正如你当然所知,Haskell是懒惰的,所以这样的计算不是必然循环。其次,它不适用于任何计算;必须有一个非循环的终止顺序,其中可以执行计算的子步骤。
例如,此cycle
函数通过将xs
的结果附加到cycle xs
来生成xs
的循环列表:
cycle xs = let result = xs ++ result in result
“绑结”模式可以使用库函数fix
进行抽象,我在这里展示了它的用法:
fix f = let result = f result in result
cycle xs = fix (xs++)
所以在你的情况下,我建议的是:
Array
类型)来表示Shelf;这使您可以随机访问元素。Array
。答案 3 :(得分:2)
你可以分两部分来做,首先你将updBook部分应用到所有书籍,然后制作一个功能列表,然后你通过一个zip将这些功能应用到正确的前一本书:
updShelf :: Shelf -> Shelf
updShelf [] = []
updShelf all@(book:books) =
let partialUpdate = map updBook all :: Book -> Shelf
in zipWith ($) (drop 3 partialUpdate) all
updBook :: Book -> Book -> Shelf
updBook = undefined
请注意,这在开始时有奇怪的行为 - 由于drop 3
,它只会使新列表中的三个元素更短。这不是必需的,您可以将该行读为zipWith ($) partialUPdate (drop (len-3) (cycle all))
,但您从未指定列表开头没有旧书的情况。
答案 4 :(得分:2)
作为一个递归函数,updShelf
将无法访问尚未传递的元素(因此,如果它获得一个从bookFive
开始的列表,它将无法访问到bookThree
。
如果要以递归方式编写函数,则需要进行更多模式匹配:
updShelf :: Shelf -> Shelf
updShelf (bookOne : bookTwo: bookThree : books)
= (updBook bookOne bookThree) : (updShelf $ bookTwo : bookThree : books)
updBook :: Book -> Book -> Shelf
updBook twoEarlier current = {- undefined -}
答案 5 :(得分:2)
好的,这是实现这一目标的灵活方式:
updateOne book = undefined
updateTwo prevUpdatedBook book = undefined
updateBooks books = answer
where numEasy = 3
(easy,hard) = splitAt numEasy books
answer = mapOnto updateOne easy (zipWith updateTwo answer hard)
-- More efficient that (map f xs ++ end)
mapOnto f xs end = foldr ((:) . f) end xs