QuickCheck中任意性的唯一性和其他限制

时间:2015-06-12 14:14:35

标签: haskell unique predicate quickcheck

我正在尝试为我的数据类型编写一个修改过的Arbitrary实例,其中(在我的例子中)一个子组件的类型为[String]。理想情况下,我希望在实例本身中带来唯一性,这样我每次编写的测试都不需要==>标题/先决条件。

这是我的数据类型:

data Foo = Vars [String]

和简单的Arbitrary实例:

instance Arbitrary Foo where
  arbitrary = Vars <$> (:[]) <$> choose ('A','z')

这个例子很奇怪,我知道。在过去,我在快速检查组合爆炸时遇到了困难,所以我想保持这些值很小。另一个请求 - 如何创建一个实例,其中生成的字符串小于4个字符,例如?

所有这些,从根本上要求(布尔)谓词来扩充Arbitrary个实例。这可能吗?

2 个答案:

答案 0 :(得分:2)

suchThat :: Gen a -> (a -> Bool) -> Gen a是一种在Gen中嵌入布尔谓词的方法。有关详细信息,请参阅haddocks

以下是如何使实例具有唯一性:

instance Arbitrary Foo where
  arbitrary = Vars <$> (:[]) <$> (:[]) <$> choose ('A','z')
              `suchThat` isUnique
    where
      isUnique x = nub x == x

答案 1 :(得分:2)

当然,您希望实例仅生成与数据类型意图相匹配的实例。如果您希望所有变量都是不同的,Arbitrary实例必须反映这一点。 (另一个问题是,如果在这种情况下将Vars定义为集合(例如newtype Vars = Set [String])会更有意义。)

我建议使用SetHashtable检查重复项,因为nub具有 O(n ^ 2)复杂度,这可能会减慢速度对于较大的输入,您的测试相当例如:

import Control.Applicative
import Data.List (nub)
import qualified Data.Set as Set
import Test.QuickCheck

newtype Foo = Vars [String]

-- | Checks if a given list has no duplicates in _O(n log n)_.
hasNoDups :: (Ord a) => [a] -> Bool
hasNoDups = loop Set.empty
  where
    loop _ []       = True
    loop s (x:xs) | s' <- Set.insert x s, Set.size s' > Set.size s
                    = loop s' xs
                  | otherwise
                    = False

-- | Always worth to test if we wrote `hasNoDups` properly.
prop_hasNoDups :: [Int] -> Property
prop_hasNoDups xs = hasNoDups xs === (nub xs == xs)

然后您的实例需要创建一个列表列表,每个列表都应该是随机的。因此,您需要两次调用(: []),而不是只创建单个列表(只有一个级别)的listOf,而不是instance Arbitrary Foo where arbitrary = Vars <$> (listOf . listOf $ choose ('A','z')) `suchThat` hasNoDups 。{/ p>

choose ('A', 'z')

另请注意,oneof [choose ('A','Z'), choose ('a','z')] 允许使用 A z 之间的所有字符,其中包含许多控制字符。我的猜测是你想要像

这样的东西
hasNoDups

如果您真的想要,也可以使用hash tables monad中的ST Gen Foo O(n)

关于限制大小:你总是可以拥有自己的参数化函数来产生不同的Gen,但我会说在大多数情况下它没有必要。 listOf有自己的内部大小参数,在整个测试过程中会增加(参见this answer),因此涵盖了不同的大小(使用Var生成)。

但我建议你实施shrink,因为这会给你更好的反例。例如,如果我们定义(错误的测试),试图验证prop_Foo_hasNoDups :: Foo -> Property prop_Foo_hasNoDups (Vars xs) = all (notElem 'a') xs === True 的任何实例在其任何变量中都不包含'a'

Vars ["RhdaJytDWKm","FHHhrqbI","JVPKGTqNCN","awa","DABsOGNRYz","Wshubp","Iab","pl"]

我们会得到丑陋的反例,例如

shrink (Vars xs) = map Vars $ shrink xs

但添加

Arbitrary Foo

Vars ["a"] 使得反例只是

namespace WindowsFormsApplication2
{
    public partial class Form1 : Form
    {
        Chart chart2 = new Chart();
        Chart chart3 = new Chart();
        int[] rand;
        int[] rand1;
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
    {

        chart2.Width = 1000 ;
        chart2.Height = 200;
        this.Controls.Add(chart2);
        this.Controls.Add(chart3);

        chart2.MouseMove += new MouseEventHandler(chart2_MouseMove);
       //chart2.GetToolTipText += new EventHandler<ToolTipEventArgs>(chart2_GetToolTipText);

        chart2.ChartAreas.Add("Area");




        chart2.ChartAreas[0].CursorX.AutoScroll = true;
        chart2.ChartAreas[0].CursorX.Interval = 1;

        chart2.ChartAreas[0].AxisX.LabelAutoFitStyle = LabelAutoFitStyles.None;
        chart2.ChartAreas[0].AxisX.IsStartedFromZero = true;
        chart2.ChartAreas[0].AxisX.Minimum = 0;

        chart2.ChartAreas[0].AxisX.ScaleView.SizeType = DateTimeIntervalType.Number;
        chart2.ChartAreas[0].AxisX.MajorTickMark.Interval = .5;
        chart2.ChartAreas[0].AxisX.ScrollBar.IsPositionedInside = true;
        chart2.ChartAreas[0].AxisX.ScrollBar.ButtonStyle = ScrollBarButtonStyles.SmallScroll;
        chart2.ChartAreas[0].AxisX.ScaleView.SmallScrollSize = 100;
        chart2.ChartAreas[0].AxisX.ScrollBar.Enabled = true;


        chart2.ChartAreas[0].AxisY.IsStartedFromZero = true;

        int[] X = new int[100];
        for (int i = 0; i < 50; i++)
        {
             X[i] = i+1;
        }
        int[] Y = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        chart2.Series.Add("Alpha");
        chart2.Series.Add("Beta");
        chart2.Series["Alpha"].ChartType = SeriesChartType.Line;
        chart2.Series["Beta"].ChartType = SeriesChartType.Line;

        chart2.Series["Alpha"].Color = Color.Blue;
        chart2.Series["Beta"].Color = Color.DarkGreen;
        chart2.Series["Alpha"].XValueType = ChartValueType.Double;
        chart2.Series["Beta"].XValueType = ChartValueType.Double;
        //chart2.Series[0].


        chart2.Series["Alpha"].YValueType = ChartValueType.Double;
        chart2.Series["Beta"].YValueType = ChartValueType.Double;


        Legend leg = new Legend();
        chart2.Legends.Add(leg);
        chart2.Legends[0].Alignment = StringAlignment.Near;



        Random _r = new Random();
        rand = new int[1000];
        rand1 = new int[1000];
        for (int i = 0; i < X.Length; i++)
        {
            int n = _r.Next(0, 100);
            rand[i] = n;
            chart2.Series["Alpha"].Points.AddY(n);
        }

        for (int i = 0; i < X.Length; i++)
        {
            int n = _r.Next(0, 100);
            rand1[i] = n;
            chart2.Series["Beta"].Points.AddY(n);
        }
        chart2.Series[0].ToolTip = "X = #VALX, Y= #VALY";
        chart2.Series[1].ToolTip = "X = #VALX, Y= #VALY";

    }

    private void chart2_Click(object sender, EventArgs e)
    {

    }

    private void chart2_MouseMove(object sender, MouseEventArgs e)
    {
        HitTestResult htr = this.chart2.HitTest (e.X, e.Y);
        Point p = new Point(e.X, e.Y);
        int K = Convert.ToInt32(p.X);
        chart2.ChartAreas[0].CursorX.Interval = 0;
        chart2.ChartAreas[0].CursorX.SetCursorPixelPosition(p,true);
        //chart2.ChartAreas[0].CursorY.SetCursorPixelPosition(p, true);
        //Label Xlbldisplay = new Label();
        //Xlbldisplay.Text = "X";

        //Label Ylbldisplay = new Label();
        //Ylbldisplay.Text = "Y";
        chart2.Series[0].ToolTip = "X = #VALX, Y= #VALY";
        //string p = chart2.GetToolTipText;
        if (Convert.ToInt32(chart2.ChartAreas[0].CursorX.Position) <= 1000)
        {
            aXdisplay.Text = Convert.ToString(Convert.ToInt32(chart2.ChartAreas[0].CursorX.Position));

            aYdisplay.Text = Convert.ToString(rand[Convert.ToInt32(aXdisplay.Text)]);
            bXdisplay.Text = aXdisplay.Text;
            bYdisplay.Text = Convert.ToString(rand1[Convert.ToInt32(aXdisplay.Text)]);
        }



    }

    //private void chart2_GetToolTipText(object sender, ToolTipEventArgs e)
    //{
    //    HitTestResult rt = chart2.HitTest(e.X, e.Y);
    //    if(rt.ChartElementType == ChartElementType.DataPoint)
    //    {
    //        Xdisplay.Text = chart2.Series[0].Points[rt.PointIndex].XValue.ToString();

    //        Ydisplay.Text = chart2.Series[0].Points[rt.PointIndex].YValues[0].ToString();
    //    }
    //    //Ydisplay.Text = e.Y.ToString();
    //}

    //private void chart2_CursorPositionChanged(object sender, CursorEventArgs e)
    //{
    //    //HitTestResult htr = this.chart2.HitTest(e.X, e.Y);
    //    //Point p = new Point(e.X, e.Y);

    //    //chart2.ChartAreas[0].CursorX.Interval = 0;
    //    //chart2.ChartAreas[0].CursorX.SetCursorPixelPosition(p, true);
    //    //chart2.ChartAreas[0].CursorY.SetCursorPixelPosition(p, true);
    //}







}