Wenn Sie die Domäne der Funktion aufzählen und Elemente des Bereichs auf Gleichheit vergleichen können, können Sie dies auf recht einfache Weise. Mit Aufzählen meine ich, eine Liste aller verfügbaren Elemente zu haben. Ich bleibe bei Haskell, da ich Ocaml nicht kenne (oder sogar weiß, wie man es richtig kapitalisiert ;-)
Sie möchten die Elemente der Domäne durchlaufen und prüfen, ob sie dem Element des Bereichs entsprechen, den Sie invertieren möchten. Nehmen Sie dann das erste Element, das funktioniert:
inv :: Eq b => [a] -> (a -> b) -> (b -> a)
inv domain f b = head [ a | a <- domain, f a == b ]
Da Sie angegeben haben, dass f
es sich um eine Bijektion handelt, muss es nur ein einziges solches Element geben. Der Trick besteht natürlich darin, sicherzustellen, dass Ihre Aufzählung der Domäne tatsächlich alle Elemente in einer endlichen Zeit erreicht . Wenn Sie eine Bijektion von zu invertieren sind versucht , Integer
zu Integer
verwenden [0,1 ..] ++ [-1,-2 ..]
wird nicht funktionieren , wie Sie nie auf die negativen Zahlen zu bekommen. Konkret inv ([0,1 ..] ++ [-1,-2 ..]) (+1) (-3)
wird nie ein Wert ergeben.
Funktioniert jedoch, 0 : concatMap (\x -> [x,-x]) [1..]
da dies die Ganzzahlen in der folgenden Reihenfolge durchläuft [0,1,-1,2,-2,3,-3, and so on]
. In der Tat inv (0 : concatMap (\x -> [x,-x]) [1..]) (+1) (-3)
sofort zurück -4
!
Das Control.Monad.Omega- Paket kann Ihnen dabei helfen, Listen mit Tupeln usw. auf gute Weise durchzugehen . Ich bin mir sicher, dass es noch mehr solche Pakete gibt - aber ich kenne sie nicht.
Natürlich ist dieser Ansatz eher unaufdringlich und brutal, ganz zu schweigen von hässlich und ineffizient! Ich werde also mit ein paar Bemerkungen zum letzten Teil Ihrer Frage enden, wie man Bijektionen "schreibt". Das Typensystem von Haskell kann nicht beweisen, dass eine Funktion eine Bijektion ist - dafür wollen Sie wirklich so etwas wie Agda -, aber es ist bereit, Ihnen zu vertrauen.
(Warnung: Es folgt ungetesteter Code)
Können Sie also einen Datentyp von Bijection
s zwischen den Typen definieren a
und b
:
data Bi a b = Bi {
apply :: a -> b,
invert :: b -> a
}
zusammen mit so vielen Konstanten (wo Sie sagen können: "Ich weiß , dass es sich um Bijektionen handelt!"), wie Sie möchten, wie zum Beispiel:
notBi :: Bi Bool Bool
notBi = Bi not not
add1Bi :: Bi Integer Integer
add1Bi = Bi (+1) (subtract 1)
und ein paar intelligente Kombinatoren wie:
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)
Ich denke du könntest es dann tun invert (mapBi add1Bi) [1,5,6]
und bekommen [0,4,5]
. Wenn Sie Ihre Kombinatoren auf intelligente Weise auswählen, kann die Häufigkeit, mit der Sie eine Bi
Konstante von Hand schreiben müssen, sehr begrenzt sein.
Wenn Sie wissen, dass eine Funktion eine Bijektion ist, haben Sie hoffentlich eine Beweisskizze dieser Tatsache in Ihrem Kopf, die der Curry-Howard-Isomorphismus in ein Programm verwandeln sollte :-)
f x = 1
die Umkehrung von 1 eine Menge von ganzen Zahlen ist und die Umkehrung von allem anderen eine leere Menge ist. Unabhängig davon, was einige Antworten sagen, ist die nicht bijektive Funktion nicht das größte Problem.