-
Notifications
You must be signed in to change notification settings - Fork 107
/
Copy pathShrink.hs
128 lines (119 loc) · 2.75 KB
/
Shrink.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
module Hedgehog.Internal.Shrink (
towards
, towardsFloat
, list
, halves
, removes
, consNub
) where
-- | Shrink an integral number by edging towards a destination.
--
-- >>> towards 0 100
-- [0,50,75,88,94,97,99]
--
-- >>> towards 500 1000
-- [500,750,875,938,969,985,993,997,999]
--
-- >>> towards (-50) (-26)
-- [-50,-38,-32,-29,-27]
--
-- /Note we always try the destination first, as that is the optimal shrink./
--
towards :: Integral a => a -> a -> [a]
towards destination x =
if destination == x then
[]
else
let
-- Halve the operands before subtracting them so they don't overflow.
-- Consider 'minBound' and 'maxBound' for a fixed sized type like 'Int64'.
diff =
(x `quot` 2) - (destination `quot` 2)
in
destination `consNub` fmap (x -) (halves diff)
-- | Shrink a floating-point number by edging towards a destination.
--
-- >>> take 7 (towardsFloat 0.0 100)
-- [0.0,50.0,75.0,87.5,93.75,96.875,98.4375]
--
-- >>> take 7 (towardsFloat 1.0 0.5)
-- [1.0,0.75,0.625,0.5625,0.53125,0.515625,0.5078125]
--
-- /Note we always try the destination first, as that is the optimal shrink./
--
towardsFloat :: RealFloat a => a -> a -> [a]
towardsFloat destination x =
if destination == x then
[]
else
let
diff =
x - destination
ok y =
y /= x && not (isNaN y) && not (isInfinite y)
in
takeWhile ok .
fmap (x -) $
iterate (/ 2) diff
-- | Shrink a list by edging towards the empty list.
--
-- >>> list [1,2,3]
-- [[],[2,3],[1,3],[1,2]]
--
-- >>> list "abcd"
-- ["","cd","ab","bcd","acd","abd","abc"]
--
-- /Note we always try the empty list first, as that is the optimal shrink./
--
list :: [a] -> [[a]]
list xs =
concatMap
(\k -> removes k xs)
(halves $ length xs)
-- | Produce all permutations of removing 'k' elements from a list.
--
-- >>> removes 2 "abcdef"
-- ["cdef","abef","abcd"]
--
removes :: Int -> [a] -> [[a]]
removes k0 xs0 =
let
loop k n xs =
let
(hd, tl) =
splitAt k xs
in
if k > n then
[]
else if null tl then
[[]]
else
tl : fmap (hd ++) (loop k (n - k) tl)
in
loop k0 (length xs0) xs0
-- | Produce a list containing the progressive halving of an integral.
--
-- >>> halves 15
-- [15,7,3,1]
--
-- >>> halves 100
-- [100,50,25,12,6,3,1]
--
-- >>> halves (-26)
-- [-26,-13,-6,-3,-1]
--
halves :: Integral a => a -> [a]
halves =
takeWhile (/= 0) . iterate (`quot` 2)
-- | Cons an element on to the front of a list unless it is already there.
--
consNub :: Eq a => a -> [a] -> [a]
consNub x ys0 =
case ys0 of
[] ->
x : []
y : ys ->
if x == y then
y : ys
else
x : y : ys