Биективная функция ℤ → ℤⁿ


23

Тривиально можно создать биективную функцию от (множество всех целых чисел) до Z (например, единичная функция).ZZ

Кроме того , можно создать биективную функцию от до Z 2 (множества всех пар целых чисел 2, в декартово произведение из Z и Z ). Например, мы могли бы взять решетку, представляющую целочисленные точки на 2D-плоскости, нарисовать спираль от 0 наружу, а затем закодировать пары целых чисел как расстояние вдоль спирали, когда она пересекает эту точку.ZZ2ZZ

спиральный

(Функция, которая делает это с натуральными числами, называется функцией сопряжения .)

На самом деле существует семейство этих биективных функций:

fk(x):ZZk

Соревнование

Определите семейство функций (где k - положительное целое число) со свойством, что f k ( x ) биективно отображает целые числа в k- ряд целых чисел.fk(x)kfk(x)k

Ваше представление должно, учитывая входы и x , возвращать f k ( x ) .kxfk(x)

Это , поэтому выигрывает самый короткий действительный ответ (измеряемый в байтах).

Характеристики

  • Любое семейство может использоваться при условии, что оно удовлетворяет вышеуказанным критериям.fk(x)
  • Вам предлагается добавить описание того, как работает ваше семейство функций, а также фрагмент для вычисления обратной функции (это не входит в число ваших байтов).
  • Хорошо, если обратная функция невычислима, если вы можете доказать это, функция является биективной.
  • Вы можете использовать любое подходящее представление для целых чисел со знаком и списков целых чисел со знаком для вашего языка, но вы должны разрешить неограниченные входные данные для вашей функции.
  • Вы только должны поддерживать значение до 127.k

Можно ли брать строковые версии kи xвместо целых чисел?
JungHwan Мин

@JungHwanMin Строки, представляющие входные числа, в порядке.
Esolanging Fruit

Ответы:


19

Алиса , 14 12 байт

/O
\i@/t&Yd&

Попробуйте онлайн!

Обратная функция (не игра в гольф):

/o     Q
\i@/~~\ /dt&Z

Попробуйте онлайн!

объяснение

Алиса имеет встроенную биекцию между и 2 , которая может быть вычислена с помощью Y(распаковка) и ее обратной Z (упаковка). Вот выдержка из документов, объясняющих биекцию:

Детали биекции, вероятно, не имеют значения для большинства случаев использования. Суть в том, что он позволяет пользователю кодировать два целых числа в одно, а затем снова извлечь два целых числа. Повторно применяя команду pack, можно хранить целые списки или деревья целых чисел в одном числе (хотя это не особенно эффективно с точки зрения памяти). Отображение вычисляется с помощью операции упаковки является биективной функцией 2 → ℤ (т.е. отображение один к одному). Во-первых, целые числа {..., -2, -1, 0, 1, 2, ...} сопоставляются с натуральными числами (включая ноль), такими как {..., 3, 1, 0, 2, 4 , ...}(другими словами, отрицательные целые числа отображаются в нечетные натуральные числа, а неотрицательные целые числа отображаются в четные натуральные числа). Затем два натуральных числа отображаются в одно с помощью функции сопряжения Кантора , которая записывает натуральные числа вдоль диагоналей первого квадранта целочисленной сетки. В частности, {(0,0), (1,0), (0,1), (2,0), (1,1), (0,2), (3,0), ...} являются отображается на {0, 1, 2, 3, 4, 5, 6, ...} . Полученное натуральное число затем сопоставляется с целыми числами, используя обратную обратную биекцию. Команда unpack вычисляет в точности обратную зависимость от этого отображения.

Как упоминалось выше, мы можем использовать эту операцию распаковки, чтобы отобразить и k . После применения его к начальному целому числу мы можем снова распаковать второе целое число результата, что дает нам список из трех целых чисел. Таким образом, k-1 приложения Yдают нам k целых чисел в результате.

Мы можем вычислить обратное, упаковав список с Zконца.

Итак, сама программа имеет такую ​​структуру:

/O
\i@/...d&

Это просто базовый шаблон для программы, которая считывает переменное число десятичных целых чисел в качестве входных данных и печатает переменное число в качестве результата. Таким образом, настоящий код действительно просто:

t   Decrement k.
&   Repeat the next command k-1 times.
Y   Unpack.

Одна вещь, на которую я хотел бы обратить внимание: «почему у Алисы есть встроенная система для биографии ℤ → ℤ 2 , разве это не языковая территория игры в гольф»? Как и в большинстве странных встроенных функций Алисы, основной причиной является принцип разработки Алисы, согласно которому каждая команда имеет два значения: одно для кардинального (целочисленного) режима и одно для ординального (строкового) режима, и эти два значения должны как- то быть связаны, чтобы дать Кардинальный и Порядковый режим - ощущение, что они являются зеркальными вселенными, где вещи похожи, но различны. И довольно часто у меня была команда для одного из двух режимов, которые я хотел добавить, а затем мне приходилось выяснять, с какой другой командой он был связан.

В случае Yи Zрежима Порядковый пришел первым: Я хотел бы иметь функцию чередования двух строк (ZIP) и отделить их снова (распаковать). Качество этого, которое я хотел уловить в режиме Cardinal, состояло в том, чтобы сформировать одно целое число из двух и иметь возможность извлекать два целых числа позже, что делает такую ​​биекцию естественным выбором.

Я также подумал, что это было бы очень полезно вне игры в гольф, потому что он позволяет хранить весь список или даже дерево целых чисел в одной единице памяти (элемент стека, ячейка ленты или ячейка сетки).


Отличное объяснение как всегда
Луис Мендо

Нахождение YиZ в документах Алисы на самом деле побудило меня опубликовать этот вызов (я думал об этом некоторое время, но это напомнило мне).
Esolanging Fruit

11

Python, 96 93 байта

def f(k,x):
 c=[0]*k;i=0
 while x:v=(x+1)%3-1;x=x//3+(v<0);c[i%k]+=v*3**(i//k);i+=1
 return c

Это работает в принципе, преобразовывая входное число xв сбалансированную троичную форму , а затем распределяя трижды (троичные цифры) наименее значимые сначала между различными координатами в циклическом порядке. Так, k=2например, каждый трит с четным позиционированием будет вносить вклад в xкоординату, а каждый трит с нечетным позиционированием будет вносить вклад в yкоординату. Потому что у k=3вас есть первый, четвертый и седьмой триты (и т. Д.), Способствующие x, в то время как второй, пятый и восьмой способствуют y, а третий, шестой и девятый способствуют z.

Например, с помощью k=2давайте посмотрим наx=35 . В сбалансированной троичной форме 35есть 110T(используя обозначение статьи в Википедии, где Tпредставляет -1цифру). Деление тритов вверх дает 1T(первая и третья триты, считая справа) для xкоординаты и 10(вторая и четвертая триты) для yкоординаты. Преобразовав каждую координату обратно в десятичную, получим 2, 3.

Конечно, я не преобразовываю целое число в сбалансированное троичное сразу в коде гольфа. Я просто вычисляю один трит за раз (в vпеременной) и добавляю его значение непосредственно к соответствующей координате.

Вот обратная функция ungolfed, которая берет список координат и возвращает число:

def inverse_f(coords):
    x = 0
    i = 0
    while any(coords):
        v = (coords[i%3]+1) % 3 - 1
        coords[i%3] = coords[i%3] // 3 + (v==-1)
        x += v * 3**i
        i += 1
    return x

Моя fфункция, пожалуй, отличается своей эффективностью. Он использует только O(k)память и требует O(k) + O(log(x))времени, чтобы найти результаты, поэтому он может работать с очень большими входными значениями. Попробуйте, f(10000, 10**10000)например, и вы получите ответ почти мгновенно (добавив дополнительный ноль к показателю степени, такx это 10**100000делает его занять 30 секунд или около того на моем старом компьютере). Обратная функция не такая быстрая, в основном потому, что ей сложно определить, когда она будет выполнена (она сканирует все координаты после каждого изменения, поэтому для этого требуется O(k*log(x))время). Возможно, его можно оптимизировать, чтобы он был быстрее, но, вероятно, он уже достаточно быстр для обычных параметров.


Вы можете удалить пробелы (новые строки) внутри цикла while
Mr. Xcoder

Спасибо, я ошибочно подумал, что существует какой-то конфликт между циклом и использованием ;цепочек операторов в одной строке.
Blckknght

9

Шелуха , 10 байт

§~!oΠR€Θݱ

Попробуйте онлайн!

Обратная функция также составляет 10 байтов.

§o!ȯ€ΠRΘݱ

Попробуйте онлайн!

объяснение

Прямое направление:

§~!oΠR€Θݱ  Implicit inputs, say k=3 and x=-48
        ݱ  The infinite list [1,-1,2,-2,3,-3,4,-4,..
       Θ    Prepend 0: [0,1,-1,2,-2,3,-3,4,-4,..
 ~    €     Index of x in this sequence: 97
§    R      Repeat the sequence k times: [[0,1,-1,..],[0,1,-1,..],[0,1,-1,..]]
   oΠ       Cartesian product: [[0,0,0],[1,0,0],[0,1,0],[1,1,0],[-1,0,0],[0,0,1],..
  !         Index into this list using the index computed from x: [-6,1,0]

Обратное направление:

§o!ȯ€ΠRΘݱ  Implicit inputs, say k=3 and y=[-6,1,0]
     ΠRΘݱ  As above, k-wise Cartesian product of [0,1,-1,2,-2,..
   ȯ€       Index of y in this sequence: 97
§o!         Index into the sequence [0,1,-1,2,-2,.. : -48

Встроенное декартово произведение Πведет себя хорошо для бесконечных списков, перечисляя каждый k- кортеж ровно один раз.


[[0,1,-1,..],[[0,1,-1,..],[[0,1,-1,..]]эта часть должна быть [[0,1,-1,..],[0,1,-1,..],[0,1,-1,..]]?
Эрик Outgolfer

@EriktheOutgolfer Угу, исправлено.
Згарб

Это прекрасно. Как программист J, знаете ли вы, есть ли хороший способ преобразовать решение с отложенным списком, подобное этому, в J, которое не поддерживает их? ^:^:_
Иона

@ Джона, я не уверен. Вы можете попытаться вычислить массив всех k- кортежей с записями из i: xи отсортировать его по сумме абсолютных значений, а затем индексировать по ним. Идея состоит в том, что эти массивы являются префиксами одного «бесконечного массива», который содержит все k- кортежи.
Згарб

7

Wolfram Language (Mathematica) , 61 байт

SortBy[Range[-(x=2Abs@#+Boole[#>=0]),x]~Tuples~#2,#.#&][[x]]&

Попробуйте онлайн!

(Принимает целое число, а затем длину кортежа в качестве входных данных.)

Inverse:

If[OddQ[#],#-1,-#]/2&@Tr@Position[SortBy[Range[-(x=Ceiling@Norm@#),x]~Tuples~Length@#,#.#&],#]&

Попробуйте онлайн!

Как это работает

Идея проста: мы превращаем целочисленный вход в положительное целое число (отображая 0,1,2,3, ... в 1,3,5,7, ... и -1, -2, -3, ... в 2,4,6, ...) , а затем индекс во всех к кортежам, отсортированным по расстоянию от начала координат, а затем по расстановке связей по умолчанию в Mathematica.

Но мы не можем использовать бесконечный список, поэтому, когда мы ищем n- й k- кортеж, мы генерируем только k- кортежей целых чисел в диапазоне {- n , ..., n }. Это гарантированно будет достаточно, потому что n- й наименьший k- кортеж по норме имеет норму меньше n , и все кортежи с нормой n или меньше включены в этот список.

Для обратного мы просто генерируем достаточно длинный список из k- кортежей, находим позицию данного k- кортежа в этом списке, а затем инвертируем операцию «сложить в натуральное число».


2
Запуск с входами [15, 5]разбил мой компьютер ...
JungHwan Мин

2
Это случится В принципе, алгоритм работает для чего угодно, но в вашем случае он работает, генерируя все 5 кортежей из диапазона {-31, .., 31}, а затем беря 31-й, так что он довольно интенсивно использует память.
Миша Лавров

3

J, 7 байт

#.,|:#:

Код J, чтобы сделать это невероятно простым

Очень простая функция сопряжения (или функция кортежа) заключается в простом чередовании цифр двоичного разложения каждого из чисел. Так, например (47, 79), будет в паре так:

1_0_0_1_1_1_1
 1_0_1_1_1_1
-------------
1100011111111

или 6399. Очевидно, мы можем легко обобщить любой n-кортеж.

Давайте рассмотрим, как это работает глагол за глаголом.

#:является антибазисом два, когда используется монадически, он возвращает двоичное расширение числа. #: 47 79дает результат:

0 1 0 1 1 1 1
1 0 0 1 1 1 1

|:является оператором транспонирования, который просто вращает массив. Вращение результата #: 47 79дает:

0 1
1 0
0 0
1 1
1 1
1 1
1 1

При монадическом использовании ,является оператором ravel, он создает одномерный список из таблицы:

0 1 1 0 0 0 1 1 1 1 1 1 1 1

Наконец, #.преобразует двоичное расширение обратно, давая нам результат 6339.

Это решение будет работать для любой строки целых чисел.


7
Как это работает для отрицательных чисел?
Нил

2

Perl 6 , 148 байт

my@s=map ->\n{|grep {n==abs any |$_},(-n..n X -n..n)},^Inf;my&f={$_==1??+*!!do {my&g=f $_-1;my@v=map {.[0],|g .[1]},@s;->\n{@v[n>=0??2*n!!-1-2*n]}}}

Попробуйте онлайн!

Ungolfed:

sub rect($n) {
    grep ->[$x,$y] { abs($x|$y) == $n }, (-$n..$n X -$n..$n);
}

my @spiral = map { |rect($_) }, ^Inf;

sub f($k) {
    if ($k == 1) {
        -> $_ { $_ }
    } else {
        my &g = f($k-1);
        my @v = map -> [$x, $y] { $x, |g($y) }, @spiral;
        -> $_ { $_ >= 0 ?? @v[2*$_] !! @v[-1-2*$_] }
    }
}

Объяснение:

  • rect($n)вспомогательная функция, которая генерирует координаты целых точек на краю прямоугольника от координат (-$n,$n)до ($n, $n).

  • @spiral это ленивый, бесконечный список целых точек на краях прямоугольников возрастающего размера, начиная с 0.

  • f($k)возвращает функцию, которая представляет собой биекцию из целых чисел в $k-топки целых чисел.

Если $kесть 1, fвозвращает отображение идентичности -> $_ { $_ }.

В противном случае, &gэто рекурсивно полученное отображение из целых чисел в $k-1-такле целых чисел.

Затем мы выходим @spiralиз начала координат и в каждой точке формируем $kкортеж, беря X-координату и сглаженный результат вызова gс Y-координатой. Это лениво сгенерированное отображение хранится в массиве@v .

@vсодержит все $kкортежи, начинающиеся с индекса 0, поэтому для расширения индексации на отрицательные целые числа мы просто сопоставляем положительные входные данные с четными числами, а отрицательные входные данные с нечетными числами. Возвращается функция (замыкание), которая ищет элементы @vтаким образом.


2

JavaScript, 155 байт

f=k=>x=>(t=x<0?1+2*~x:2*x,h=y=>(g=(v,p=[])=>1/p[k-1]?v||t--?0:p.map(v=>v&1?~(v/2):v/2):[...Array(1+v)].map((_,i)=>g(v-i,[...p,i])).find(u=>u))(y)||h(y+1))(0)

Предварительная версия:

k => x => {
  // Map input to non-negative integer
  if (x > 0) t = 2 * x; else t = 2 * -x - 1;
  // we try to generate all triples with sum of v
  g = (v, p = []) => {
    if (p.length === k) {
      if (v) return null;
      if (t--) return null;
      // if this is the t-th one we generate then we got it
      return p;
    }
    for (var i = 0; i <= v; i++) {
      var r = g(v-i, [...p, i]);
      if (r) return r;
    }
  }
  // try sum from 0 to infinity
  h = x => g(x) || h(x + 1);
  // map tuple of non-negative integers back
  return h(0).map(v => {
    if (v % 2) return -(v + 1) / 2
    else return v / 2;
  });
}
  • Сначала мы отображаем все целые числа на все неотрицательные целые числа одно за другим:
    • если n> 0, то результат = n * 2
    • в противном случае результат = -n * 2 - 1
  • Во-вторых, мы присваиваем всем кортежам неотрицательные целые числа длины k:
    • рассчитать сумму всех элементов, меньший идет первым
    • если сумма равна, сравните слева направо, меньший идет первым
    • В результате мы получили отображение всех неотрицательных целых чисел в кортежи с k неотрицательными целыми числами.
  • Наконец, отобразите неотрицательные целые числа в кортеже, заданном на втором шаге, на все целые числа с похожей формулой на первом шаге.

Я думаю, x<0?~x-x:x+xэкономит 2 байта.
Нил

2

Wolfram Language (Mathematica) , 107 байт

(-1)^#⌈#/2⌉&@Nest[{w=⌊(√(8#+1)-1)/2⌋;x=#-w(w+1)/2,w-x}~Join~{##2}&@@#&,{2Abs@#-Boole[#<0]},#2-1]&

Попробуйте онлайн!

Обратное, 60 байт

(-1)^#⌈#/2⌉&@Fold[+##(1+##)/2+#&,2Abs@#-Boole[#<0]&/@#]&

Попробуйте онлайн!

Объяснение:

Z -> N0 через f(n) = 2n if n>=0 and -2n-1 if n<0

N0 -> N0 ^ 2 через обратную функцию сопряжения

N0 -> N0 ^ k. Повторно применяйте вышеизложенное к крайнему левому числу, пока мы не получим длину k

N0 ^ k -> Z ^ k via f(n) = (-1)^n * ceil(n/2), поэлементно


Mathematica, 101 байт

(-1)^#⌈#/2⌉&@Nest[{a=#~IntegerExponent~2+1,#/2^a+1/2}~Join~{##2}&@@#&,{2Abs@#+Boole[#<=0]},#2-1]&

Аналогично выше (использует N вместо N0), но использует обратную биекцию f: N ^ 2 -> N через f(a, b) = 2^(a - 1)(2b - 1)


Вы имеете в виду ... для этого нет встроенной Mathematica (когда у Алисы она есть)? Я потерял дар речи.
JayCe

1

JavaScript, 112 байт

k=>x=>(r=Array(k).fill(''),[...`${x<0?2*~x+1:2*x}`].map((c,i,s)=>r[(s.length-i)%k]+=c),r.map(v=>v&1?~(v/2):v/2))
  1. преобразовать в неотрицательный
  2. (n * k + i) -я цифра для i-го числа
  3. преобразовать обратно

@HermanLauenstein не нужно возвращаться назад?
TSH

Я думаю, x<0?~x-x:x+xэкономит 2 байта.
Нил

-5 байт используется [...BT${x<0?~x-x:x+x}BT].reverse().map((c,i)=>r[i%k]+=c),(кредит @Neil для x<0?~x-x:x+x). .reverse()используется вместо того, (s.length-i)чтобы избежать необходимости в дополнительном параметре sдля первого .map. Нет необходимости возвращаться назад, поскольку временный массив больше не используется. (Я не проверял это, но это, вероятно, должно работать)
Герман Л

Еще один байты могут быть сохранены путем замены .fill('')с .fill(0), так как ведущий нуль не имеет никакого значения (по крайней мере , не при тестировании в Safari)
Herman L

@HermanLauenstein Вы пробовали .fill`` ? Это может сэкономить еще пару байтов.
Нил


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.