Haskell:从程序中的多个位置追加到列表
我想有一个列表,可以从代码中的多个位置更新(附加到)。在haskell,如果不允许出现副作用,我管理这个列表的最佳方式是什么?我曾想过每次需要附加列表时都要将列表传递给它,但我不知道这是否足以完成我想要完成的任务。使用Memorization[编辑非动态规划]并在助手列表中存储可能的最佳赢款列表。使用单子和状态模拟全局变量是一个好主意还是有更好的方法 编辑:这是我在python中实现它的方式:Haskell:从程序中的多个位置追加到列表,haskell,functional-programming,global-variables,memoization,Haskell,Functional Programming,Global Variables,Memoization,我想有一个列表,可以从代码中的多个位置更新(附加到)。在haskell,如果不允许出现副作用,我管理这个列表的最佳方式是什么?我曾想过每次需要附加列表时都要将列表传递给它,但我不知道这是否足以完成我想要完成的任务。使用Memorization[编辑非动态规划]并在助手列表中存储可能的最佳赢款列表。使用单子和状态模拟全局变量是一个好主意还是有更好的方法 编辑:这是我在python中实现它的方式: def BJ(i):
def BJ(i):
if i in BJList:
return BJList[i]
else:
options = [0] # if you walk away
if n-i < 4: # not enough cards to play
return 0
for p in range(2,(n - i)): # number of cards taken
player = c[i] + c[i+2] + sum(c[i+4:i+p+2])
# when p is 2
if player > 21:
options.append(-1 + BJ(i + p + 2))
break # breaks out of for(p)
dealer = 0
d1 = 0
for d in range(2,n-i-p+1): # python ranges are not inclusive
d1 = d
dealer = c[i+1] + c[i+3] + sum(c[i+p+2:i+p+d])
if dealer >= 17:
break # breaks out of for(d)
if dealer < 17 and i + p + d >= n: # doesn't change results, maybe
dealer = 21 # make it be more like haskell version
if dealer > 21:
dealer = 0
dealer += .5 # dealer always wins in a tie
options.append(cmp(player, dealer) + BJ(i + p + d1))
BJList[i] = (max(options))
return max(options)
c = [10,3,5,7,5,2,8,9,3,4,7,5,2,1,5,8,7,6,2,4,3,8,6,2,3,3,3,4,9,10,2,3,4,5]
BJList = {}
n = len(c)
print "array:" + str(c)
print BJ(0)
如果我没弄错的话,你是在处理相对较短的名单,因为只有52张卡片。所以我会保持简单,编写函数,将列表作为输入,并将更新后的列表作为输出返回 我只是浏览了一下您的python代码,所以下面提供的示例可能并不完全符合您的需要,但至少它会给您一个想法。假设您想用新卡替换手牌中的第三张卡。你可以用这样的东西
replaceElement xs n x
返回xs
的副本,其中n
th元素已替换为x
replaceElement :: [a] -> Int -> a -> [a]
replaceElement xs i x =
if 0 <= i && i < length xs then fore ++ (x : aft) else xs
where fore = take i xs
aft = drop (i+1) xs
如果你处理的是大列表,我建议你看看可变列表,也许还有状态单子等等。但是当我还是Haskell初学者的时候,我最常犯的错误就是把事情复杂化了。我创建了太多的类和类型,使用了过多的单子,等等。如果我正确理解了这个问题,您处理的是相对较短的列表,因为只有52张卡。所以我会保持简单,编写函数,将列表作为输入,并将更新后的列表作为输出返回 我只是浏览了一下您的python代码,所以下面提供的示例可能并不完全符合您的需要,但至少它会给您一个想法。假设您想用新卡替换手牌中的第三张卡。你可以用这样的东西
replaceElement xs n x
返回xs
的副本,其中n
th元素已替换为x
replaceElement :: [a] -> Int -> a -> [a]
replaceElement xs i x =
if 0 <= i && i < length xs then fore ++ (x : aft) else xs
where fore = take i xs
aft = drop (i+1) xs
如果你处理的是大列表,我建议你看看可变列表,也许还有状态单子等等。但是当我还是Haskell初学者的时候,我最常犯的错误就是把事情复杂化了。我创建了太多的类和类型,使用了过多的单子,等等。一般来说,我看到3种解决方案,对Haskeller的“吸引力”越来越小:
- 优点:没有副作用,很漂亮
- 缺点:如果是命令式的,你可能需要重写算法(这里就是这种情况)
State
monad模拟State,因为它实际上只是“隐藏”函数的附加参数
- 赞成:命令式感觉
- 缺点:一元代码,通常更难推理,也不容易重用
- 例如:
workerState :: State [Int] () workerState = do modify (++ [21]) {- lots of stuff happening -} modify (++ [21]) -- E.g. in repl > runState workerState []
workerST :: STRef s [Int] -> ST s () workerST ref = do modifySTRef ref (++ [21]) {- lots of stuff happening -} modifySTRef ref (++ [21]) -- E.g. in repl: > runST $ do r <- newSTRef [] workerST r
ST
monad或某种可变引用,如IORef
- 优点:真正的易变性,由于一元风格,对原始算法的更改最小
- 缺点:都来自
+终极邪恶:副作用状态
- 例如:
workerState :: State [Int] () workerState = do modify (++ [21]) {- lots of stuff happening -} modify (++ [21]) -- E.g. in repl > runState workerState []
workerST :: STRef s [Int] -> ST s () workerST ref = do modifySTRef ref (++ [21]) {- lots of stuff happening -} modifySTRef ref (++ [21]) -- E.g. in repl: > runST $ do r <- newSTRef [] workerST r
总的来说,我看到3种解决方案,对Haskellers的“吸引力”有所下降: - 以纯函数的方式编写算法
- 优点:没有副作用,很漂亮
- 缺点:如果是命令式的,你可能需要重写算法(这里就是这种情况)
- 使用
monad模拟State,因为它实际上只是“隐藏”函数的附加参数State
- 赞成:命令式感觉
- 缺点:一元代码,通常更难推理,也不容易重用
- 例如:
workerState :: State [Int] () workerState = do modify (++ [21]) {- lots of stuff happening -} modify (++ [21]) -- E.g. in repl > runState workerState []
workerST :: STRef s [Int] -> ST s () workerST ref = do modifySTRef ref (++ [21]) {- lots of stuff happening -} modifySTRef ref (++ [21]) -- E.g. in repl: > runST $ do r <- newSTRef [] workerST r
- 使用副作用。这意味着使用
monad或某种可变引用,如ST
IORef
- 优点:真正的易变性,由于一元风格,对原始算法的更改最小
- 缺点:都来自
+终极邪恶:副作用状态
- 例如:
workerState :: State [Int] () workerState = do modify (++ [21]) {- lots of stuff happening -} modify (++ [21]) -- E.g. in repl > runState workerState []
workerST :: STRef s [Int] -> ST s () workerST ref = do modifySTRef ref (++ [21]) {- lots of stuff happening -} modifySTRef ref (++ [21]) -- E.g. in repl: > runST $ do r <- newSTRef [] workerST r
以下是如何以更实用的方式思考
过程:BJ
的每个值都向p
列表添加一个元素,因此您可以将options
视为具有以下定义:options
其中,options = map f [2..n-i-1]
是一个有待确定的函数 对于f
的每个值,我们需要确定以下内容:p
- 运动员的计数
- 经销商们算了算
- 所取卡片的数量
现在我们可以这样表达result :: Int -> (Int,Int,Int) -- player's count, dealers count, cards taken result p = ...
:选项
options = map score (map result [2..n-i-1])
其中,score :: (Int,Int,Int) -> Int score (pcount,dcount,ncards) = (if pcount > 21 then -1 else if pcount > dcount then 1 else -1) + BJ (i+ncards)
将取三重(pcount、dcount、ncards)并计算结果给玩家的值 为了实现循环终止条件(对于循环,从主得分
),只要玩家的计数大于21,我们就停止获取结果。这可以通过在映射结果之后插入中断
来实现…:takeWhile
现在,您必须在这里实现动态编程/记忆 综上所述,options = map score $ takeWhile (\(p,_,_) -> p <= 21) $ map result [2..n-i-1]
函数有以下伪代码:BJ
因此,代码流是:BJ[i] = 0 if n-i < 4 BJ[i] = maximum options where options = map score $ takeWhile ... $ map result [2..n-i-1] score (p,d,n) = (if ...p > d...) + BJ[i+n] result p = ...
- 记忆版本只是索引到数组中
- 数组是根据原始函数定义的
- 对于任何递归调用,原始函数都会调用已记忆的函数