Если вы можете перечислить область определения функции и сравнить элементы диапазона на предмет равенства, вы можете - довольно простым способом. Под перечислением я подразумеваю наличие списка всех доступных элементов. Я буду придерживаться Haskell, так как я не знаю Ocaml (или даже как правильно его использовать ;-)
Что вы хотите сделать, так это пройтись по элементам домена и посмотреть, равны ли они элементу диапазона, который вы пытаетесь инвертировать, и выбрать первый, который работает:
inv :: Eq b => [a] -> (a -> b) -> (b -> a)
inv domain f b = head [ a | a <- domain, f a == b ]
Поскольку вы заявили, что f
это биекция, обязательно должен быть один и только один такой элемент. Уловка, конечно же, состоит в том, чтобы гарантировать, что ваше перечисление домена действительно достигнет всех элементов за конечное время . Если вы пытаетесь инвертировать биекцию с 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 может помочь вам хорошо просмотреть списки кортежей и так далее; Я уверен, что таких пакетов много, но я их не знаю.
Конечно, это довольно простой и грубый подход, не говоря уже о безобразном и неэффективном! Итак, я закончу несколькими замечаниями по последней части вашего вопроса о том, как «писать» биекции. Система типов Haskell не способна доказать, что функция является взаимно однозначной - для этого вам действительно нужно что-то вроде Agda - но она готова вам доверять.
(Предупреждение: следует непроверенный код)
Итак, можете ли вы определить тип данных Bijection
s между типами a
и b
:
data Bi a b = Bi {
apply :: a -> b,
invert :: b -> a
}
вместе с любым количеством констант (где вы можете сказать: «Я знаю, что это биекция!»), например:
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
константу вручную, может быть весьма ограниченным.
В конце концов, если вы знаете, что функция является биекцией, вы, надеюсь, будете иметь в своей голове схему доказательства этого факта, которую изоморфизм Карри-Ховарда сможет превратить в программу :-)
f x = 1
, обратное к 1 является набором целых чисел, а обратное к чему-либо еще является пустым набором. Независимо от того, что говорят некоторые ответы, то, что функция не является биективной, не является самой большой проблемой.