Haskell中动态规划的有效表

Haskell中动态规划的有效表,haskell,lazy-evaluation,dynamic-programming,memoization,knapsack-problem,Haskell,Lazy Evaluation,Dynamic Programming,Memoization,Knapsack Problem,我已经在哈斯克尔把密码编好了。我对迄今为止所取得的懒惰和普遍性水平感到相当自豪 我首先提供创建和处理惰性2d矩阵的函数 mkList f = map f [0..] mkTable f = mkList (\i -> mkList (\j -> f i j)) tableIndex table i j = table !! i !! j 然后,我为一个给定的背包问题创建一个特定的表 knapsackTable = mkTable f where f 0 _ = 0

我已经在哈斯克尔把密码编好了。我对迄今为止所取得的懒惰和普遍性水平感到相当自豪

我首先提供创建和处理惰性2d矩阵的函数

mkList f = map f [0..]
mkTable f = mkList (\i -> mkList (\j -> f i j))

tableIndex table i j = table !! i !! j
然后,我为一个给定的背包问题创建一个特定的表

knapsackTable = mkTable f
    where f 0 _ = 0
          f _ 0 = 0
          f i j | ws!!i > j = leaveI
                | otherwise = max takeI leaveI
              where takeI  = tableIndex knapsackTable (i-1) (j-(ws!!i)) + vs!!i
                    leaveI = tableIndex knapsackTable (i-1) j

-- weight value pairs; item i has weight ws!!i and value vs!!i
ws  = [0,1,2, 5, 6, 7] -- weights
vs  = [0,1,7,11,21,31] -- values
最后,使用两个助手函数查看表格

viewTable table maxI maxJ = take (maxI+1) . map (take (maxJ+1)) $ table
printTable table maxI maxJ = mapM_ print $ viewTable table maxI maxJ
这很容易。但我想更进一步

我希望为表提供更好的数据结构。理想情况下,它应该是

  • 未绑定(不可变)[编辑]别介意
  • 懒惰的
  • 无界
  • O(1)
    施工时间
  • O(1)
    查找给定条目的时间复杂性,
    (更现实地说,最坏的情况是
    O(logn)
    ,其中n是
    i*j
    ,用于查找第i行第j列的条目)
如果您能解释您的解决方案满足这些理想的原因/方式,则可获得额外积分

如果您能进一步推广
背包表
,并证明它是有效的,还可以获得额外的积分

在改进数据结构时,您应尝试满足以下目标:

  • 如果我要求的解决方案中最大重量为10(在我当前的代码中,这将是
    可转位背包表5 10
    ,5种方法包括项目1-5),则只需执行最少的必要工作量。理想情况下,这意味着没有任何
    O(i*j)
    工作强制表中每一行的脊椎达到必要的列长度。如果您认为DP意味着评估整个表,那么您可以说这不是“真实的”DP
  • 如果我要求打印整个表(类似于
    printTable knapsackTable 5 10
    ),则每个条目的值应该计算一次,并且只计算一次。给定单元格的值应取决于其他单元格的值(DP样式:思想是,永远不要重新计算同一子问题两次)

想法:

  • 有界:(
  • 严格的:(
  • (关于Haskell中DP的问题)这可能有效

对我所说的理想做出一些妥协的答案,只要是信息性的,就会被我(无论如何)投票支持。妥协最少的答案很可能是“被接受”的答案。

为什么不使用数据。映射将其他数据放入其中。映射?据我所知,它相当快。 但这也不是懒惰

除此之外,您还可以为您的数据实现Ord类型类

data Index = Index Int Int
并将二维索引直接作为键。通过将此映射生成为列表,然后使用

fromList [(Index 0 0, value11), (Index 0 1, value12), ...] 

Unboxed意味着严格且有界。任何100%未绑定的内容都不能是惰性或无界的。通常的折衷方法体现在将[Word8]转换为Data.ByteString.Lazy,其中存在以无界方式惰性链接在一起的未绑定块(strict ByteString)

可以使用“scanl”、“zipWith”和我的“takeOnto”创建更高效的表格生成器(增强以跟踪单个项目)。这有效地避免了在创建表格时使用(!!):

import Data.List(sort,genericTake)

type Table = [ [ Entry ] ]

data Entry = Entry { bestValue :: !Integer, pieces :: [[WV]] }
  deriving (Read,Show)

data WV = WV { weight, value :: !Integer }
  deriving (Read,Show,Eq,Ord)

instance Eq Entry where
  (==) a b = (==) (bestValue a) (bestValue b)

instance Ord Entry where
  compare a b = compare (bestValue a) (bestValue b)

solutions :: Entry -> Int
solutions = length . filter (not . null) . pieces

addItem :: Entry -> WV -> Entry
addItem e wv = Entry { bestValue = bestValue e + value wv, pieces = map (wv:) (pieces e) }

-- Utility function for improve
takeOnto :: ([a] -> [a]) -> Integer -> [a] -> [a]
takeOnto endF = go where
  go n rest | n <=0 = endF rest
            | otherwise = case rest of
                            (x:xs) -> x : go (pred n) xs
                            [] -> error "takeOnto: unexpected []"

improve oldList wv@(WV {weight=wi,value = vi}) = newList where
  newList | vi <=0 = oldList
          | otherwise = takeOnto (zipWith maxAB oldList) wi oldList
  -- Dual traversal of index (w-wi) and index w makes this a zipWith
  maxAB e2 e1 = let e2v = addItem e2 wv
                in case compare e1 e2v of
                     LT -> e2v
                     EQ -> Entry { bestValue = bestValue e1
                                 , pieces = pieces e1 ++ pieces e2v }
                     GT -> e1

-- Note that the returned table is finite
-- The dependence on only the previous row makes this a "scanl" operation
makeTable :: [Int] -> [Int] -> Table
makeTable ws vs =
  let wvs = zipWith WV (map toInteger ws) (map toInteger vs)
      nil = repeat (Entry { bestValue = 0, pieces = [[]] })
      totW = sum (map weight wvs)
  in map (genericTake (succ totW)) $ scanl improve nil wvs

-- Create specific table, note that weights (1+7) equal weight 8
ws, vs :: [Int]
ws  = [2,3, 5, 5, 6, 7] -- weights
vs  = [1,7,8,11,21,31] -- values

t = makeTable ws vs

-- Investigate table

seeTable = mapM_ seeBestValue t
  where seeBestValue row = mapM_ (\v -> putStr (' ':(show (bestValue v)))) row >> putChar '\n'

ways = mapM_ seeWays t
  where seeWays row = mapM_ (\v -> putStr (' ':(show (solutions v)))) row >> putChar '\n'

-- This has two ways of satisfying a bestValue of 8 for 3 items up to total weight 5
interesting = print (t !! 3 !! 5) 
导入数据列表(排序、genericTake)
类型表=[[Entry]]
数据条目=条目{bestValue::!Integer,片段:[[WV]]}
派生(读取、显示)
数据WV=WV{weight,value::!Integer}
派生(读取、显示、等式、Ord)
实例Eq条目在哪里
(==)AB=(==)(最佳值a)(最佳值b)
实例Ord条目在哪里
比较a b=比较(最佳值a)(最佳值b)
解决方案::Entry->Int
解决方案=长度.filter(not.null).parties
addItem::Entry->WV->Entry
addItem e wv=条目{bestValue=bestValue+value wv,片段=map(wv:)(片段e)}
--改进的效用函数
takeOnto::([a]->[a])->整数->[a]->[a]
takeOnto endF=去哪里
去休息| nx:go(pred n)xs
[]->错误“TakeOno:意外[]”
改进旧列表wv@(wv{weight=wi,value=vi})=新列表,其中
新列表| vi e2v
等式->条目{bestValue=bestValue e1
,个数=个数e1++个数e2v}
GT->e1
--请注意,返回的表是有限的
--由于只依赖于前一行,因此这是一个“scanl”操作
makeTable::[Int]->[Int]->Table
makeTable-ws-vs=
设wvs=zipWith WV(映射到Integer ws)(映射到Integer vs)
nil=重复(条目{bestValue=0,片段=[[]]})
totW=总和(地图权重wvs)
在地图中(常规拍摄(成功))$scanl改善零wvs
--创建特定表格,注意权重(1+7)等于权重8
ws,vs::[Int]
ws=[2,3,5,5,6,7]——权重
vs=[1,7,8,11,21,31]——数值
t=makeTable ws vs
--调查表
seeTable=mapM\seeBestValue t
其中seeBestValue row=mapM_40;v->putStr(“”:(show(bestValue v)))row>>putChar'\n'
ways=mapM_uuuWayst
其中seeWays row=mapM\uv->putStr(“”:(show(solutions v)))row>>putChar'\n'
--这有两种方法可以满足总重量为5的3个项目的最佳值8
有趣=打印(t!!3!!5)
惰性可存储向量:


无界,惰性,O(chunksize)构造时间,O(n/chunksize)索引,其中chunksize对于任何给定的目的都可以足够大。基本上是一个懒惰的列表,具有一些显著的常量因子好处。

首先,您对未绑定数据结构的标准可能有点误导。未绑定值必须严格,并且它们与不变性无关。我将提出的解决方案是immutable、lazy和boxed。此外,我不确定您希望以何种方式构造和查询O(1)。我建议的结构是延迟构造的,但由于它可能是无界的,所以其完整构造将花费无限时间。查询结构将花费O(k)任何大小为k的特定键都需要时间,但是您查找的值可能需要更多的时间来计算

数据结构是一个懒惰的trie。我正在使用Conal
knapsack :: (Enum a, Num w, Num v, Num a, Ord w, Ord v, HasTrie a, HasTrie w) =>
            (a -> w) -> (a -> v) -> a -> w -> v
knapsack weight value = knapsackMem
  where knapsackMem = memo2 knapsack'
        knapsack' 0 w = 0
        knapsack' i 0 = 0
        knapsack' i w
          | weight i > w = knapsackMem (pred i) w
          | otherwise = max (knapsackMem (pred i) w)
                        (knapsackMem (pred i) (w - weight i)) + value i
mapM_ (print . uncurry (knapsack ws vs)) $ range ((0,0), (i,w))
knapsack :: (Int,Int) -> Solution
knapsack = memo f
    where
    memo    = pair integral integral
    f (i,j) = ... knapsack (i-b,j) ...
data IntTrie a = Branch IntTrie a IntTrie

integral f = \n -> lookup n table
     where
     table = Branch (\n -> f (2*n)) (f 0) (\n -> f (2*n+1))
lookup 0 (Branch l a r) = a
lookup n (Branch l a r) = if even n then lookup n2 l else lookup n2 r
     where n2 = n `div` 2