Haskellのような純粋な関数型言語では、全単射の場合、関数の逆を取得(編集)するアルゴリズムはありますか?そして、あなたの機能をプログラムする特定の方法はありますか?
いいえ、一般的には不可能です。
証明:型の全単射関数を考慮する
_type F = [Bit] -> [Bit]
_
と
_data Bit = B0 | B1
_
_inv :: F -> F
_のようなインバータ_inv f . f ≡ id
_があるとします。関数_f = id
_に対してテストしたことを確認して、
_inv f (repeat B0) -> (B0 : ls)
_
出力のこの最初の_B0
_は一定の時間が経過した後でなければならないので、n
が取得するテスト入力を実際に評価した深さの両方に上限inv
があります。この結果と、f
を呼び出した回数。関数のファミリーを定義する
_g j (B1 : B0 : ... (n+j times) ... B0 : ls)
= B0 : ... (n+j times) ... B0 : B1 : ls
g j (B0 : ... (n+j times) ... B0 : B1 : ls)
= B1 : B0 : ... (n+j times) ... B0 : ls
g j l = l
_
明らかに、すべての_0<j≤n
_に対して、_g j
_は全単射であり、実際には自己逆です。確認できるはずです
_inv (g j) (replicate (n+j) B0 ++ B1 : repeat B0) -> (B1 : ls)
_
しかし、これを実現するには、inv (g j)
は
g j (B1 : repeat B0)
を_n+j > n
_の深さまで評価するn
_replicate (n+j) B0 ++ B1 : ls
に一致する異なるリストの_head $ g j l
_を評価しますその時点まで、少なくとも1つの_g j
_はf
と区別できず、_inv f
_はこれらの評価のいずれも実行していなかったため、inv
はおそらく独自にいくつかのランタイム測定を行うことを除いて、それをバラバラに言いました。これは_IO Monad
_でのみ可能です。
⬜
ウィキペディアで調べることができます。これは リバーシブルコンピューティング と呼ばれます。
一般にそれはできませんが、どの機能言語にもそのオプションはありません。例えば:
f :: a -> Int
f _ = 1
この関数には逆関数はありません。
ほとんどの関数型言語ではなく、ロジックプログラミングまたはリレーショナルプログラミングでは、定義するほとんどの関数は実際には関数ではなく「関係」であり、これらは双方向で使用できます。たとえば、プロローグまたはkanrenを参照してください。
関数のドメインを列挙でき、範囲の要素が等しいかどうかを比較できる場合、次のようにできます。列挙するということは、利用可能なすべての要素のリストを持つことを意味します。私はOcamlを知らないのでHaskellに固執します(またはそれを適切に大文字にする方法さえ;-)
やりたいことは、ドメインの要素を調べて、それらが反転しようとしている範囲の要素と等しいかどうかを確認し、動作する最初の要素を取得することです:
_inv :: Eq b => [a] -> (a -> b) -> (b -> a)
inv domain f b = head [ a | a <- domain, f a == b ]
_
f
は全単射であると述べたので、そのような要素は1つに限定されます。もちろん、コツは、ドメインの列挙がすべての要素に有限時間で実際に到達するようにすることです。 Integer
からInteger
に全単射を反転させようとしている場合、_[0,1 ..] ++ [-1,-2 ..]
_を使用しても、負の数にならないため機能しません。具体的には、inv ([0,1 ..] ++ [-1,-2 ..]) (+1) (-3)
は値を生成しません。
ただし、0 : concatMap (\x -> [x,-x]) [1..]
は次の順序で整数を実行するため、_[0,1,-1,2,-2,3,-3, and so on]
_で機能します。確かにinv (0 : concatMap (\x -> [x,-x]) [1..]) (+1) (-3)
はすぐに_-4
_を返します!
Control.Monad.Omega パッケージを使用すると、タプルなどのリストを適切に実行できます。そのようなパッケージは他にもあると思いますが、知りません。
もちろん、このアプローチはratherくて非能率的なことは言うまでもなく、かなり控えめで総当たり的なものです!それでは、あなたの質問の最後の部分である、全単射を「書く」方法についてのいくつかの発言で終わります。 Haskellの型システムは、関数が全単射であることを証明するものではありません-あなたは本当にそのためにAgdaのようなものが欲しいのですが-それはあなたを信頼してくれます。
(警告:テストされていないコードが続きます)
タイプBijection
とa
の間でb
sのデータ型を定義できますか:
_data Bi a b = Bi {
apply :: a -> b,
invert :: b -> a
}
_
好きなだけ多くの定数(「Iknowそれらは全単射だ!」)
_notBi :: Bi Bool Bool
notBi = Bi not not
add1Bi :: Bi Integer Integer
add1Bi = Bi (+1) (subtract 1)
_
次のようないくつかのスマートコンビネータ
_idBi :: Bi a a
idBi = Bi id id
invertBi :: Bi a b -> Bi b a
invertBi (Bi a i) = (Bi i a)
composeBi :: Bi a b -> Bi b c -> Bi a c
composeBi (Bi a1 i1) (Bi a2 i2) = Bi (a2 . a1) (i1 . i2)
mapBi :: Bi a b -> Bi [a] [b]
mapBi (Bi a i) = Bi (map a) (map i)
bruteForceBi :: Eq b => [a] -> (a -> b) -> Bi a b
bruteForceBi domain f = Bi f (inv domain f)
_
その後、invert (mapBi add1Bi) [1,5,6]
を実行して_[0,4,5]
_を取得できると思います。コンビネータを賢明な方法で選択した場合、Bi
定数を手動で記述する必要がある回数はかなり制限されると思います。
結局、関数が全単射であることを知っているなら、その事実を頭の中に証明するスケッチがあることを願っています。それはカリー-ハワード同型がプログラムに変換できるはずです:-)
このようなタスクはほとんどの場合決定できません。いくつかの特定の機能に対する解決策はありますが、一般的な解決策はありません。
ここでは、どの関数に逆関数があるのかさえ認識できません。引用 Barendregt、H. P. The Lambda Calculus:its Syntax and Semantics。North Holland、Amsterdam(1984) :
空のセットでも完全なセットでもない場合、ラムダ項のセットは重要です。 AとBが(ベータ)等式で閉じられた2つの非自明な、互いに素なラムダ項のセットである場合、AとBは再帰的に不可分です。
Aを可逆関数を表すラムダ項のセット、Bを残りとしましょう。どちらも空ではなく、ベータ平等の下で閉じられています。そのため、関数が反転可能かどうかを判断することはできません。
(これは、型付けされていないラムダ計算に適用されます。TBH反転したい関数の型がわかっているときに、引数を型付きラムダ計算に直接適応できるかどうかはわかりません。確かに似ています。)
私は最近このような問題に対処してきましたが、いや、(a)多くの場合それは難しくありませんが、(b)それはまったく効率的ではありません。
基本的に、f :: a -> b
があり、f
が実際にbjiectionであると仮定します。あなたは本当に愚かな方法で逆f' :: b -> a
を計算できます:
import Data.List
-- | Class for types whose values are recursively enumerable.
class Enumerable a where
-- | Produce the list of all values of type @a@.
enumerate :: [a]
-- | Note, this is only guaranteed to terminate if @f@ is a bijection!
invert :: (Enumerable a, Eq b) => (a -> b) -> b -> Maybe a
invert f b = find (\a -> f a == b) enumerate
f
が全単射で、enumerate
がa
のすべての値を本当に生成する場合、最終的にf a == b
などのa
にヒットします。
Bounded
およびEnum
インスタンスを持つ型は、簡単にRecursivelyEnumerable
にすることができます。 Enumerable
型のペアをEnumerable
にすることもできます:
instance (Enumerable a, Enumerable b) => Enumerable (a, b) where
enumerate = crossWith (,) enumerate enumerate
crossWith :: (a -> b -> c) -> [a] -> [b] -> [c]
crossWith f _ [] = []
crossWith f [] _ = []
crossWith f (x0:xs) (y0:ys) =
f x0 y0 : interleave (map (f x0) ys)
(interleave (map (flip f y0) xs)
(crossWith f xs ys))
interleave :: [a] -> [a] -> [a]
interleave xs [] = xs
interleave [] ys = []
interleave (x:xs) ys = x : interleave ys xs
Enumerable
型の分離についても同じことが言えます。
instance (Enumerable a, Enumerable b) => Enumerable (Either a b) where
enumerate = enumerateEither enumerate enumerate
enumerateEither :: [a] -> [b] -> [Either a b]
enumerateEither [] ys = map Right ys
enumerateEither xs [] = map Left xs
enumerateEither (x:xs) (y:ys) = Left x : Right y : enumerateEither xs ys
(,)
とEither
の両方でこれを行うことができるという事実は、おそらく、任意の代数データ型でできることを意味します。
すべての関数に逆関数があるわけではありません。ディスカッションを1対1の機能に限定すると、任意の機能を反転できるため、暗号システムを解読することができます。理論的にも、これが実現不可能であることを願わなければなりません!
場合によっては、全単射関数の逆関数をシンボリック表現に変換することで見つけることができます。 この例 に基づいて、このHaskellプログラムを作成して、いくつかの単純な多項式関数の逆関数を見つけました。
bijective_function x = x*2+1
main = do
print $ bijective_function 3
print $ inverse_function bijective_function 3
data Expr = X | Const Double |
Plus Expr Expr | Subtract Expr Expr | Mult Expr Expr | Div Expr Expr |
Negate Expr | Inverse Expr |
Exp Expr | Log Expr | Sin Expr | Atanh Expr | Sinh Expr | Acosh Expr | Cosh Expr | Tan Expr | Cos Expr |Asinh Expr|Atan Expr|Acos Expr|Asin Expr|Abs Expr|Signum Expr|Integer
deriving (Show, Eq)
instance Num Expr where
(+) = Plus
(-) = Subtract
(*) = Mult
abs = Abs
signum = Signum
negate = Negate
fromInteger a = Const $ fromIntegral a
instance Fractional Expr where
recip = Inverse
fromRational a = Const $ realToFrac a
(/) = Div
instance Floating Expr where
pi = Const pi
exp = Exp
log = Log
sin = Sin
atanh = Atanh
sinh = Sinh
cosh = Cosh
acosh = Acosh
cos = Cos
tan = Tan
asin = Asin
acos = Acos
atan = Atan
asinh = Asinh
fromFunction f = f X
toFunction :: Expr -> (Double -> Double)
toFunction X = \x -> x
toFunction (Negate a) = \a -> (negate a)
toFunction (Const a) = const a
toFunction (Plus a b) = \x -> (toFunction a x) + (toFunction b x)
toFunction (Subtract a b) = \x -> (toFunction a x) - (toFunction b x)
toFunction (Mult a b) = \x -> (toFunction a x) * (toFunction b x)
toFunction (Div a b) = \x -> (toFunction a x) / (toFunction b x)
with_function func x = toFunction $ func $ fromFunction x
inverse X = X
inverse (Const a) = Const a
inverse (Plus (Const a) b) = (Subtract (inverse b) (Const a))
inverse (Plus b (Const a)) = inverse (Plus (Const a) b)
inverse (Mult (Const a) b) = (Div (inverse b) (Const a))
inverse (Mult b (Const a)) = inverse (Mult (Const a) b)
inverse (Negate a) = Negate $ inverse a
inverse (Asin x) = Sin $ inverse x
inverse (Acos x) = Cos $ inverse x
inverse (Atan x) = Tan $ inverse x
inverse_function x = with_function inverse x
この例は算術式でのみ機能しますが、おそらくリストでも機能するように一般化できます。