从自定义类型创建随机数据

时间:2019-01-26 23:47:39

标签: haskell functional-programming

我定义了以下自定义类型

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的参数,因此该解决方案对于我的目的而言并不完全有效。

3 个答案:

答案 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。 (从现在开始,我将NodeEmpty分别缩写为NE,以减少打字量。)对于 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)]