QuickCheckを使用して任意の関数を生成しています。任意の injective 関数を生成したいと思います(つまり、f a == f b
の場合のみa == b
)。
私thought私はそれを理解してもらいました:
newtype Injective = Injective (Fun Word Char) deriving Show
instance Arbitrary Injective where
arbitrary = fmap Injective fun
where
fun :: Gen (Fun Word Char)
fun = do
a <- arbitrary
b <- arbitrary
arbitrary `suchThat` \(Fn f) ->
(f a /= f b) || (a == b)
しかし、生成された関数が異なる入力を同じ出力にマップする場合があります。
私が欲しいもの:
私が持っていると思うもの:
どうすれば修正できますか?
問題を正しく特定しました。生成しているのは∃ a≠b. f a≠f b
プロパティ(これはほとんどのランダム関数に当てはまります)の関数ですが、必要なのは∀ a≠b. f a≠f b
です。個々の値を生成するには、他のすべての関数値について知っておく必要があるため、これを確認するのははるかに難しいプロパティです。
私はこれが一般的な入力タイプを保証することは可能ではないと思いますが、特にWordに対してできることは、すべての出力値を順番に事前計算することによって関数を「偽造」することです。完了し、その所定のチャートから読み取ります。これを実際に機能させるには、少し怠惰なfuが必要です。
import qualified Data.Set as Set
newtype Injective = Injective ([Char] {- simply a list without duplicates -})
deriving Show
instance Arbitrary Injective where
arbitrary = Injective . lazyNub <$> arbitrary
lazyNub :: Ord a => [a] -> [a]
lazyNub = go Set.empty
where go _ [] = []
go forbidden (x:xs)
| x `Set.member` forbidden = go forbidden xs
| otherwise = x : go (Set.insert x forbidden) xs
これはあまり効率的ではなく、アプリケーションに問題があるかもしれませんが、おそらくあなたができる最善の方法です。
実際には、実際にInjective
を関数として使用するには、値を[〜#〜] o [〜#〜]( log n)ルックアップ時間。残念ながら、Data.Map.Lazy
は十分に怠惰ではないため、指数関数的に増加するマップのリストのようなものを手動でベイクする必要がある場合があります。
また、十分に大きな結果の型では、十分な値がないために単射関数を生成できない可能性があるという懸念もあります。実際、ジョセフが述べたように、これはここに当てはまります。この場合、lazyNub
関数は無限ループに入ります。私はQuickCheckのためにこれはおそらく大丈夫だと思います。