我定义了以下自定义类型
class MainHandler(...):
def prepare(self):
self.set_header('Access-Control-Allow-Origin', '*')
# the wildcard - '*' - allows CORS from any domain
# you should probably change it to your frontend
# domain name if you wan to restrict CORS to a single domain.
# see Mozilla docs for more info
我想用给定数量的节点data Tree = Empty | Node Tree Tree
创建随机的Tree
,然后将其传递给另一个计算树的深度的函数
n
哪种方法最容易实现?
编辑:我在下面的答案中尝试了与Alec类似的方法,该方法返回随机的depth :: Tree -> Int
depth Empty = 0
depth Node t1 t2 = (maximum [depth t1, depth t2]) + 1
。但是,我还需要将随机IO Tree
传递给其他几个函数,而我对此无法控制。这些要求使用类型Tree
而不是Tree
的参数,因此该解决方案对于我的目的而言并不完全有效。
答案 0 :(得分:1)
将其视为简单的递归问题。唯一的麻烦是,获得随机数需要显式地通过生成器进行线程化,或者需要在IO
中进行操作。为了简单起见,我会坚持使用后者。
import System.Random
data Tree = Empty | Node Tree Tree
-- | Generate a tree of the given size
arbitraryTree :: Int -> IO Tree
arbitraryTree treeSize
| treeSize <= 1 = pure Empty -- base case, tree of size 1
| otherwise = do
leftSize <- randomRIO (0,treeSize - 1)
let rightSize = treeSize - 1 - leftSize
leftSubtree <- arbitraryTree leftSize
rightSubtree <- arbitraryTree rightSize
pure (Node leftSubtree rightSubtree)
答案 1 :(得分:0)
有趣的问题!最好分两个部分解决此问题:生成具有所需节点数量的Tree
列表,然后从该列表中随机选择一个值。
我们将从第一个问题开始:给定数字 n ,生成所有Tree
与 n Node
s的列表。但是,我们将如何做呢?让我们尝试一些小的 n 的简单案例。对于 n = 0,我们只有一个选择:Empty
。 (从现在开始,我将Node
和Empty
分别缩写为N
和E
,以减少打字量。)对于 n = 1,我们也只有一个选择:N E E
。对于 n = 2,我们有两种情况,可以通过用E
替换一个N E E
来从 n = 1情况产生: / p>
N (N E E) E
N E (N E E)
对于 n = 3,我们可以重复相同的过程,在每种情况下依次用E
替换每个N E E
,以查找所有可能的位置,我们可以添加一个额外的节点:
N (N (N E E) E) E
N (N E (N E E)) E
N (N E E) (N E E)
N E (N (N E E) E)
N E (N E (N E E))
我们可以通过递归函数来实现:
allWithNum :: Int -> [Tree]
allWithNum 0 = [Empty]
allWithNum n = nub $ concatMap subWithNode (allWithNum $ n-1)
where
subWithNode = fmap snd . filter fst . go False
where
go False Empty = [(True,Node Empty Empty),(False,Empty)]
go True Empty = [(True,Empty)]
go hasSubdNode (Node x y) = do
(subdX, newX) <- go hasSubdNode x
(subdY, newY) <- go subdX y
return (subdY, Node newX newY)
(请注意,它使用nub
中的Data.List
,并且还要求Tree
具有Eq
实例。)
此处的大部分工作是在go
中完成的,该工作沿Tree
依次替换Node Empty Empty
到每个Empty
中,并跟踪其是否替代了。
现在要解决第二个问题:我们如何从该列表中选择一个随机元素?可以使用random
包中System.Random
模块中的函数来完成此操作,方法是使用choose (0, length_of_list)
选择一个索引,然后使用(!!)
获取该索引处的值。
答案 2 :(得分:0)
QuickCheck是您的朋友。现在是开始学习它的好时机。
您询问有关生成随机树的问题。用QuickCheck术语,我们使用类型类Arbitrary
生成任意树。
class Arbitrary a where
arbitrary :: Gen a
coarbitrary :: a -> Gen b -> Gen b
Gen
是用于测试数据生成器的类型类。
测试数据生成器:类型Gen
测试数据由测试数据生成器生成。快速检查定义 大多数类型的默认生成器,但您可以将自己的与
forAll
,并且需要为任何新的定义自己的生成器 您介绍的类型。生成器的类型为
Gen a
;这是一个发电机 类型a
的值。类型Gen
是单子,因此Haskell的do
语法和标准单子函数可用于定义 发电机。生成器是建立在该功能之上的
choose :: Random a => (a, a) -> Gen a
从一个间隔中随机选择一个值, 分布均匀。例如,要在 列表中的元素,使用
do i <- choose (0, length xs-1) return (xs !! i)
在您的问题中,您说要生成具有一定数量节点的树。一个Arbitrary
实例是
instance Arbitrary Tree where
arbitrary = sized tree'
where tree' 0 = return Empty
tree' n | n > 0 = do
lsize <- choose (0, n - 1)
l <- tree' lsize
r <- tree' (n - lsize - 1)
return $ Node l r
由于sized
,此实例尊重外部尺寸参数。
测试数据的大小
测试数据生成器具有一个隐式
size
参数;快速检查 首先生成小的测试用例,然后逐渐增加 尺寸随测试进度而定。不同的测试数据生成器解释 size参数以不同的方式:有人忽略它,而列表 例如,生成器将其解释为长度的上限 生成的列表。您可以随意使用它来控制自己的 自己的测试数据生成器。您可以获得大小的值 参数使用sized :: (Int -> Gen a) -> Gen a
sized g
调用g
,并将当前大小作为参数传递给它。对于 例如,要生成0到n范围内的自然数,请使用sized $ \n -> choose (0, n)
内部tree'
生成器获取剩余的节点数,并通过在参数为零时创建Empty
或创建Node
,其左侧子树的大小在0到0之间来创建树。 n
-1(为节点本身占1,少1),其右子树获取其余元素。
说你有一个功能
nodes Empty = 0
nodes (Node l r) = 1 + nodes l + nodes r
,我们希望看到它正确地计数了10节点树中的节点。定义属性后
prop_nodes_count = forAll (resize 10 arbitrary) (\t -> nodes t == 10)
我们成功地在GHCi中对其进行了测试
λ> quickCheck prop_nodes_count
+++ OK, passed 100 tests.
上面的代码使用resize
来强制生成的树的大小。
为演示您可以生成纯树列表,即即,而不是[Tree]
,IO [Tree]
,我们将使用一个简单的备用站。
print_trees :: [Tree] -> IO ()
print_trees = print
最终,QuickCheck生成器是随机的,因此是有状态的,因此从您的main
动作生成它们是一种简单的方法。
main :: IO ()
main = do
trees <- sample' (resize 0 arbitrary)
print_trees $ take 1 trees
trees' <- sample' (resize 1 arbitrary)
print_trees $ take 1 trees'
trees'' <- sample' (resize 2 arbitrary)
print_trees $ take 2 trees''
此处的工作是sample'
,类型为Gen a -> IO [a]
。也就是说,在IO
动作中运行时,sample'
会生成一个列表,列出您想要生成的任何内容,在这种情况下为任意树。相关功能sample
的类型为Show a => Gen a -> IO ()
,这意味着它将继续打印生成的测试数据。
将deriving Show
添加到Tree
的定义后,上述程序的输出为
[Empty]
[Node Empty Empty]
[Node (Node Empty Empty) Empty,Node Empty (Node Empty Empty)]