Проектная функция f (f (n)) == -n


841

Вопрос, который я получил во время моего последнего интервью:

Разработайте функцию f, такую ​​что:

f(f(n)) == -n

Где n32-разрядное целое число со знаком ; Вы не можете использовать арифметику комплексных чисел.

Если вы не можете разработать такую ​​функцию для всего диапазона чисел, разработайте ее для максимально возможного диапазона.

Любые идеи?


2
Для какой работы было это интервью?
17

Ответы:


377

Как насчет:

f (n) = знак (n) - (-1) n * n

В Python:

def f(n): 
    if n == 0: return 0
    if n >= 0:
        if n % 2 == 1: 
            return n + 1
        else: 
            return -1 * (n - 1)
    else:
        if n % 2 == 1:
            return n - 1
        else:
            return -1 * (n + 1)

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


Чтобы это работало для действительных чисел, вам нужно заменить n в (-1) n на { ceiling(n) if n>0; floor(n) if n<0 }.

В C # (работает для любого double, кроме случаев переполнения):

static double F(double n)
{
    if (n == 0) return 0;

    if (n < 0)
        return ((long)Math.Ceiling(n) % 2 == 0) ? (n + 1) : (-1 * (n - 1));
    else
        return ((long)Math.Floor(n) % 2 == 0) ? (n - 1) : (-1 * (n + 1));
}

10
Битый за -1, потому что -1 * 0 по-прежнему 0
Джоэл Коухорн

3
Нет, это не так. f (-1) = 0. f (0) = 1
1800 ИНФОРМАЦИЯ

5
Это сломано для 1 однако. f (1) = 0. f (0) = 1
1800 ИНФОРМАЦИЯ

18
Хм, сохраняя состояние с четными и нечетными числами, я должен был подумать об этом.
Неизвестно

38
Я думаю, что самая важная вещь - это не фактическая функция (существует бесконечно много решений), а процесс, с помощью которого вы можете создать такую ​​функцию.
Пион

440

Вы не сказали, какой язык они ожидали ... Вот статичное решение (Хаскелл). Это в основном портит 2 самых значимых бита:

f :: Int -> Int
f x | (testBit x 30 /= testBit x 31) = negate $ complementBit x 30
    | otherwise = complementBit x 30

Это намного проще в динамическом языке (Python). Просто проверьте, является ли аргумент числом X и верните лямбду, которая возвращает -X:

def f(x):
   if isinstance(x,int):
      return (lambda: -x)
   else:
      return x()

23
Круто, я люблю это ... тот же подход в JavaScript: var f = function (n) {return (typeof n == 'function')? n (): function () {return -n; }}
Марк Ренуф

Вероятно, просто мой Haskell очень ржавый, но вы проверили это для (f 0)? Похоже, что это должно привести к тому же результату, что и (f 0x80000000), по крайней мере, если мы имеем дело с 32-битными целыми числами с циклической арифметикой (для операции отрицания). И это было бы плохо.
Дариус Бэкон

11
Будет ли средний интервьюер даже знаю , что лямбда - конструкт это ?
Джереми Пауэлл

4
Конечно, такой тип-обман трюк также работает в Haskell, даже если это статический: class C a b | a->b where { f :: a->b }; instance C Int (()->Int) where { f=const.negate }; instance C (()->Int) Int where { f=($()) },
оставлено около

4
Какая? Откуда вы взяли, что typeof f (n) === 'function', особенно, где n - это число, и вы ожидаете, что число вернется? Я не понимаю, как здесь может применяться случай дела. Я плохо говорю на Python, но в JS проверка аргумента для типа функции в данном случае совершенно неправильная. Здесь применимо только численное решение. f является функцией, f (n) является числом.
Гарри

284

Вот доказательство того, почему такая функция не может существовать для всех чисел, если она не использует дополнительную информацию (кроме 32 битов типа int):

Мы должны иметь f (0) = 0. (Доказательство: предположим, f (0) = x. Тогда f (x) = f (f (0)) = -0 = 0. Теперь -x = f (f (x) )) = f (0) = x, что означает, что x = 0.)

Далее для любого xи y, допустим f(x) = y. Мы хотим f(y) = -xтогда. И f(f(y)) = -y => f(-x) = -y. Подведем итог: если f(x) = y, то f(-x) = -yи f(y) = -x, и f(-y) = x.

Итак, нам нужно разделить все целые числа кроме 0 на наборы по 4, но у нас есть нечетное число таких целых чисел; Мало того, что, если мы удалим целое число, которое не имеет положительного аналога, у нас все еще будет 2 (mod4) числа.

Если мы удалим 2 оставшихся максимальных числа (по значению abs), мы можем получить функцию:

int sign(int n)
{
    if(n>0)
        return 1;
    else 
        return -1;
}

int f(int n)
{
    if(n==0) return 0;
    switch(abs(n)%2)
    {
        case 1:
             return sign(n)*(abs(n)+1);
        case 0:
             return -sign(n)*(abs(n)-1);
    }
}   

Конечно, другой вариант - не соблюдать 0 и получить 2 номера, которые мы удалили в качестве бонуса. (Но это просто глупо, если.)


29
Я не могу поверить, что мне пришлось прочитать это далеко, чтобы найти хорошее процедурное решение, которое обрабатывает отрицательные числа, не прибегая к глобальным переменным или трюкам, которые запутывают код. Если бы я мог голосовать за вас более одного раза, я бы.
Кайл Симек

Приятное наблюдение, что в любых n знаковых битах есть нечетное число ненулевых целых чисел .
Андрес Яан Так

Это был бы и мой ответ, но остерегайтесь крайнего случая n = -2147483648(минимальное значение); Вы не можете abs(n)в этом случае, и результат будет неопределенным (или исключением).
Кирк Бродхерст

1
@ a1kmm: Извините, -2³² выше должно было быть -2³¹. В любом случае, случай, когда f (0) ≠ 0 (и, следовательно, f (0) = - 2³¹), на самом деле является более простым случаем, как мы показали, что эти два не связаны с остальными. Другой случай, который нам нужно рассмотреть, состоит в том, что f (0) = 0, но f (x) = - 2³¹ для некоторого x ≠ 0, x ≠ -2³¹. В этом случае f (-2³¹) = f (f (x)) = - x (примечание -x не может быть -2³¹, поскольку такого x не существует). Далее, пусть f (-x) = y. Тогда f (y) = f (f (-x)) = x. Снова у не может быть -2³ -2 (так как f (y) = x, но f (-2³¹) = - x, и x не равен 0). Итак, -2³¹ = f (x) = f (f (y)) = - y, что невозможно. Так что действительно 0 и -2³¹ должны быть отключены от остальных (не изображение чего-либо еще).
ShreevatsaR

1
@will Нет нулей со знаком, если (как я полагаю) мы говорим о 32-разрядных целых числах, дополняющих два.
Гоффри

146

Благодаря перегрузке в C ++:

double f(int var)
{
 return double(var);
} 

int f(double var)
{
 return -int(var);
}

int main(){
int n(42);
std::cout<<f(f(n));
}

4
К сожалению, из-за искажения имен у функций, которые вы называете "f", на самом деле странные имена.
Пион

1
Я думал о чем-то подобном, но, думая о Си, это было отброшено ... хорошая работа!
Лиран Ореви

@Rui Craverio: Это не сработает в .NET 3.5+, потому что автор решил использовать ключевое слово var в качестве имени переменной.
Креднс

72
технически ... это не то, что требует вопрос. Вы определили 2 функции f (), f (int) и f (float), и вопросы задают «Разработайте функцию f () ...»
elcuco

2
@elcuco Технически, конечно, но логически это одна функция с множественными перегрузками (вы можете сделать f (f (42)) с этим). Поскольку определение ничего не говорит о параметрах и возвращаемом значении, я вряд ли могу принять это как техническое определение.
Марек Томан

135

Или вы можете злоупотребить препроцессором:

#define f(n) (f##n)
#define ff(n) -n

int main()
{
  int n = -42;
  cout << "f(f(" << n << ")) = " << f(f(n)) << endl;
}

Значит, ты станешь Конрадом "Ле Шиффр", Рудольфом? Я возьму свое пальто. Да, я знаю обо всем "void main", но добавляю "return 0;" просто так много дополнительных усилий ;-)
Skizz

25
@Skizz, возвращение 0 из main не требуется в c ++ даже с возвращаемым значением int ... так что, выполнив все правильно, вы на самом деле наберете на один символ меньше!
Дэн Олсон

10
Skizz всегда злоупотребляет препроцессором: D
Арнис Лапса

23
Это не функция .. так что это
неверное

3
@smerlin: технически это встроенная функция, возвращающая встроенную функцию: тела обоих раскрываются во время компиляции, а точнее, перед этим. Не может быть намного более эффективным, чем это.
Джон Перди

103

Это верно для всех отрицательных чисел.

    f (n) = abs (n)

Поскольку существует еще одно отрицательное число, чем положительное число для целых чисел, дополняющих двойку, f(n) = abs(n)оно действительно для еще одного случая, чем f(n) = n > 0 ? -n : nрешение, которое совпадает с f(n) = -abs(n). Получил тебя по одному ...: D

ОБНОВИТЬ

Нет, это не действительно для одного случая больше, поскольку я только что узнал по комментарию Литба ... abs(Int.Min)просто переполнится ...

Я тоже думал об использовании информации о моде 2, но пришел к выводу, что она не работает ... до раннего. Если все сделано правильно, это будет работать для всех чисел, за исключением того, Int.Minчто это будет переполнено.

ОБНОВИТЬ

Я поиграл с ним некоторое время, ища хороший трюк с манипуляциями, но я не мог найти хороший однострочный, в то время как решение для мод 2 вписывается в один.

    f (n) = 2n (abs (n)% 2) - n + sgn (n)

В C # это становится следующим:

public static Int32 f(Int32 n)
{
    return 2 * n * (Math.Abs(n) % 2) - n + Math.Sign(n);
}

Для того, чтобы получить его работу для всех значений, вы должны заменить Math.Abs()с (n > 0) ? +n : -nи включают в расчет в качестве uncheckedблока. Тогда вы даже Int.Minсопоставлены с самим собой, как непроверенное отрицание.

ОБНОВИТЬ

Вдохновленный другим ответом, я собираюсь объяснить, как работает функция и как ее создать.

Давайте начнем с самого начала. Функция fнеоднократно применяется к заданному значению, nдавая последовательность значений.

    n => f (n) => f (f (n)) => f (f (f (n))) => f (f (f (f (n)))) => ...

Вопрос требует f(f(n)) = -n, чтобы это было два последовательных применения fотрицания аргумента. Два дальнейших применения f- всего четыре - снова сводят на нет аргумент и снова дают результат n.

    n => f (n) => -n => f (f (f (n))) => n => f (n) => ...

Теперь существует очевидный цикл длины четыре. Подставляя x = f(n)и отмечая, что полученное уравнение f(f(f(n))) = f(f(x)) = -xвыполнено, получаем следующее.

    n => x => -n => -x => n => ...

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

Одним из многих решений для построения такого цикла является следующее, начиная с п.

 n => отрицать и вычесть один
-n - 1 = - (n + 1) => добавить один
-n => отменить и добавить один
 n + 1 => вычесть один
 N

Конкретный пример такого цикла есть +1 => -2 => -1 => +2 => +1. Мы почти закончили. Отмечая, что построенный цикл содержит нечетное положительное число, его четный преемник и оба числа отрицательные, мы можем легко разделить целые числа на множество таких циклов ( 2^32кратных четырем) и найти функцию, которая удовлетворяет условиям.

Но у нас проблема с нулем. Цикл должен содержать, 0 => x => 0потому что ноль отрицается сам по себе. И потому что цикл состояний уже 0 => xэто следует 0 => x => 0 => x. Это всего лишь цикл длины два, и xон превращается в себя после двух применений, а не в -x. К счастью, есть один случай, который решает проблему. Если Xравен нулю, мы получаем цикл длины один, содержащий только ноль, и мы решили эту проблему, заключив, что ноль является фиксированной точкой f.

Выполнено? Почти. У нас есть 2^32числа, ноль - фиксированная точка, оставляющая 2^32 - 1числа, и мы должны разбить это число на циклы из четырех чисел. Плохо, что 2^32 - 1не кратно четырем - останется три числа не в любом цикле длины четыре.

Я объясню оставшуюся часть решения, используя меньший набор 3-битных подписанных итераторов в диапазоне от -4до +3. Мы сделали с нуля. У нас есть один полный цикл +1 => -2 => -1 => +2 => +1. Теперь давайте построим цикл, начинающийся с +3.

    +3 => -4 => -3 => +4 => +3

Проблема, которая возникает, состоит в том, что +4не представляется как 3-битное целое число. Мы получили бы +4отрицая , -3чтобы +3- то , что до сих пор действует 3 разрядное целое число - но добавление одного к +3(двоичный 011) дает 100бинарной. Он интерпретируется как целое число без знака, +4но мы должны интерпретировать его как целое число со знаком -4. Так что на самом деле -4для этого примера или Int.MinValueв общем случае это вторая фиксированная точка целочисленного арифметического отрицания - 0 и Int.MinValueотображаются на себя. Таким образом, цикл на самом деле выглядит следующим образом.

    +3 => -4 => -3 => -4 => -3

Это цикл длины два и дополнительно +3входит в цикл через -4. В результате -4правильно отображается на себя после двух приложений функции, +3правильно отображается -3после двух приложений функции, но -3ошибочно отображается на себя после двух приложений функции.

Итак, мы создали функцию, которая работает для всех целых чисел, кроме одного. Можем ли мы сделать лучше? Нет мы не можем. Почему? Мы должны построить циклы длины четыре и иметь возможность охватить весь диапазон целых чисел до четырех значений. Остальные значения являются две неподвижными точками , 0и Int.MinValueкоторые должны быть отображены на себя и два произвольных целые числа xи -xкоторые должны быть отображены друг с другом с помощью двух приложений функции.

Чтобы отобразить xна -xи наоборот , они должны образовывать четыре цикла , и они должны быть расположены на противоположных углах этого цикла. В следствии 0и Int.MinValueдолжны быть на противоположных углах тоже. Это будет правильно карту xи -xно поменять местами две фиксированные точки 0и Int.MinValueпосле двух применений функции и оставить нас с двух провальных входов. Таким образом, невозможно создать функцию, которая работает для всех значений, но у нас есть такая, которая работает для всех значений, кроме одного, и это лучшее, чего мы можем достичь.


Не соответствует критериям: abs (abs (n))! = -N
Дэн Олсон

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

Этот ответ по крайней мере так же хорош, как и ответ Марджа Синовца и Роуленда Шоу, он просто работает для другого диапазона чисел
1800 ИНФОРМАЦИЯ

19
Чувак, ты можешь просто избавиться от «ОБНОВЛЕНИЯ» и написать один сплоченный правильный ответ. Снизу 3/4 («вдохновленный другим ответом») просто потрясающе.
Андрес Яан Тэк

1
Мне очень нравится решение для отрицательных чисел. Просто и понятно.
Турбьёрн Равн Андерсен

97

Используя комплексные числа, вы можете эффективно разделить задачу отрицания числа на два шага:

  • умножьте n на i, и вы получите n * i, который на n повернут на 90 ° против часовой стрелки
  • умножьте снова на I, и вы получите -n

Самое замечательное, что вам не нужен какой-либо специальный код обработки. Просто умножая на я делаю работу.

Но вы не можете использовать комплексные числа. Таким образом, вы должны каким-то образом создать свою собственную воображаемую ось, используя часть вашего диапазона данных. Поскольку вам нужно ровно столько мнимых (промежуточных) значений, сколько первоначальных значений, у вас остается только половина диапазона данных.

Я попытался визуализировать это на следующем рисунке, предполагая подписанные 8-битные данные. Вы должны были бы масштабировать это для 32-битных целых чисел. Допустимый диапазон для начального n составляет от -64 до +63. Вот что делает функция для положительного n:

  • Если n находится в 0..63 (начальный диапазон), вызов функции добавляет 64, отображая n в диапазон 64..127 (промежуточный диапазон)
  • Если n находится в 64..127 (промежуточный диапазон), функция вычитает n из 64, отображая n в диапазон 0 ..- 63

Для отрицательного n функция использует промежуточный диапазон -65 ..- 128.

альтернативный текст


4
@geschema, какой инструмент вы использовали для создания этой красивой графики?
jwfearn

10
Извините, в вопросе явно не указано комплексных чисел.
Руи Крейвейро

6
@Liran: я использовал OmniGraffle (только для Mac)
гешема

39
+1 Я думаю, что это лучший ответ. Я не думаю, что люди читают достаточно, потому что все они отметили, что вопрос сказал, что комплексные числа не могут быть использованы. Я прочитал все это, и вы описали решение в комплексных числах, чтобы подготовить почву для несложного решения поставленного вопроса. Очень красиво сделано.
jrista

1
@jrista во всех решениях используется второе измерение, то есть все, чем на самом деле являются «комплексные числа» (большинство используют нечетное против четного, а выше - floatпротив int). «Кольцо из 4 элементов», которое описывают многие ответы, требует 4 состояний, которые можно представить в виде 2 измерений, каждое из которых имеет 2 состояния. Проблема с этим ответом является то , что оно требует дополнительного пространства для обработки (только «работает» для -64..63, но потребности -128..127 пространства) и прямо не указывается написана формула!
Кирк Бродхерст

65

Работает кроме int.MaxValue и int.MinValue

    public static int f(int x)
    {

        if (x == 0) return 0;

        if ((x % 2) != 0)
            return x * -1 + (-1 *x) / (Math.Abs(x));
        else
            return x - x / (Math.Abs(x));
    }

изобразительный


Не уверен, почему это было понижено. Для каких входов это терпит неудачу?
Родрик Чепмен

Почему вы не используете функцию signum?!?
комонад

1
Изображение действительно хорошее. Отправить 0в 0и -2147483648к , -2147483648так как эти два числа являются неподвижными точками для оператора отрицания, x => -x. Для остальных чисел следуйте стрелкам на изображении выше. Как ясно из ответа SurDin и его комментариев, в этом случае будет два числа, 2147483647и -2147483647не останется ни одной другой пары для обмена.
Джеппе Стиг Нильсен

Это похоже на смайлик - с множеством морщин
Anshul

48

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

... только то, что когда n является 32-разрядным целым числом, то f(f(n)) = -n

Итак, как насчет чего-то вроде

Int64 f(Int64 n)
{
    return(n > Int32.MaxValue ? 
        -(n - 4L * Int32.MaxValue):
        n + 4L * Int32.MaxValue);
}

Если n - 32-разрядное целое число, то утверждение f(f(n)) == -nбудет истинным.

Очевидно, что этот подход может быть расширен для работы с еще более широким диапазоном чисел ...


2
Sneaky. Предел персонажа.
Джо Филлипс

2
Да, я работал над похожим подходом. Вы победили меня, хотя. +1 :)
jalf

1
Очень умный! Это очень близко к (и фактически совпадает с) использованием комплексных чисел, что было бы очевидным и идеальным решением, но явно недопустимо. Работа вне диапазона допустимых чисел.
Кирк Бродхерст

48

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

function f(n) {
    if (n.passed) {
        return -n.val;
    } else {
        return {val:n, passed:1};
    }
}

дающий

js> f(f(10))  
-10
js> f(f(-10))
10

В качестве альтернативы вы можете использовать перегрузку в строго типизированном языке, хотя это может нарушить правила, т.е.

int f(long n) {
    return n;
}

long f(int n) {
    return -n;
}

Последнее не означает требование «а» (единственного числа) функции. :)
Дрю

Удалите вторую половину ответа, и это правильный ответ.
jmucchiello

@Drew, именно поэтому я упомянул, что это может нарушить правила
коббал

2
В JavaScript функция является объектом и поэтому может сохранять состояние.
Носредна

1
IMO: функция f (n) {вернуть n.passed? -n.val: {val: n, пройдено: 1}} более читабельно и короче.
SamGoody

46

В зависимости от вашей платформы, некоторые языки позволяют вам сохранять состояние в функции. VB.Net, например:

Function f(ByVal n As Integer) As Integer
    Static flag As Integer = -1
    flag *= -1

    Return n * flag
End Function

IIRC, C ++ также допустили это. Я подозреваю, что они ищут другое решение, хотя.

Другая идея состоит в том, что, поскольку они не определили результат первого вызова функции, вы можете использовать нечетность / четность, чтобы контролировать, следует ли инвертировать знак:

int f(int n)
{
   int sign = n>=0?1:-1;
   if (abs(n)%2 == 0)
      return ((abs(n)+1)*sign * -1;
   else
      return (abs(n)-1)*sign;
}

Добавьте одно к величине всех четных чисел, вычтите одно из величины всех нечетных чисел. Результат двух вызовов имеет одинаковую величину, но один вызов, даже если мы меняем знак. В некоторых случаях это не сработает (-1, max или min int), но работает намного лучше, чем все, что предлагалось до сих пор.


1
Я считаю, что это работает для MAX_INT, поскольку это всегда странно. Это не работает для MIN_INT и -1.
Airsource Ltd

9
Это не функция, если у нее есть побочные эффекты.

12
Это может быть правдой в математике, но это не имеет отношения к программированию. Таким образом, вопрос в том, ищут ли они математическое решение или решение для программирования. Но, учитывая, что это для работы по программированию ...
Райан Ланди

+1 Я собирался опубликовать один с C в "static int x", реализующий FIFO с отрицанием вывода. Но это достаточно близко.
phkahler

2
@nos: Да, это так, это просто не прозрачно.
Кларк Гебель

26

Использование исключений JavaScript.

function f(n) {
    try {
        return n();
    }
    catch(e) { 
        return function() { return -n; };
    }
}

f(f(0)) => 0

f(f(1)) => -1


Я сомневаюсь, что исключения использовались, как это раньше ... :)
NoBugs

+1 Из коробки мышление. Круто! Но в рабочем коде я бы использовал typeof просто для безопасности.

21

Для всех 32-битных значений (с оговоркой, что -0 равен -2147483648)

int rotate(int x)
{
    static const int split = INT_MAX / 2 + 1;
    static const int negativeSplit = INT_MIN / 2 + 1;

    if (x == INT_MAX)
        return INT_MIN;
    if (x == INT_MIN)
        return x + 1;

    if (x >= split)
        return x + 1 - INT_MIN;
    if (x >= 0)
        return INT_MAX - x;
    if (x >= negativeSplit)
        return INT_MIN - x + 1;
    return split -(negativeSplit - x);
}

В основном вам нужно соединить каждый цикл -x => x => -x с циклом ay => -y => y. Так что я в паре противоположных сторон split.

Например, для 4-битных целых чисел:

0 => 7 => -8 => -7 => 0
1 => 6 => -1 => -6 => 1
2 => 5 => -2 => -5 => 2
3 => 4 => -3 => -4 => 3

21

Версия C ++, возможно, несколько отклоняющая правила, но работающая для всех числовых типов (с плавающей точкой, целых, двойных) и даже для типов классов, которые перегружают унарный минус:

template <class T>
struct f_result
{
  T value;
};

template <class T>
f_result <T> f (T n)
{
  f_result <T> result = {n};
  return result;
}

template <class T>
T f (f_result <T> n)
{
  return -n.value;
}

void main (void)
{
  int n = 45;
  cout << "f(f(" << n << ")) = " << f(f(n)) << endl;
  float p = 3.14f;
  cout << "f(f(" << p << ")) = " << f(f(p)) << endl;
}

Хорошая идея. В качестве альтернативы вы могли бы, вероятно, потерять структуру и вместо этого иметь одну функцию, возвращающую указатель, а другую функцию разыменовывать и отменять.
Заколдованная

20

x86 asm (стиль AT & T):

; input %edi
; output %eax
; clobbered regs: %ecx, %edx
f:
    testl   %edi, %edi
    je  .zero

    movl    %edi, %eax
    movl    $1, %ecx
    movl    %edi, %edx
    andl    $1, %eax
    addl    %eax, %eax
    subl    %eax, %ecx
    xorl    %eax, %eax
    testl   %edi, %edi
    setg    %al
    shrl    $31, %edx
    subl    %edx, %eax
    imull   %ecx, %eax
    subl    %eax, %edi
    movl    %edi, %eax
    imull   %ecx, %eax
.zero:
    xorl    %eax, %eax
    ret

Код проверен, все возможные 32-битные целые числа переданы, ошибка с -2147483647 (недополнение).


19

Использует глобалы ... но так?

bool done = false
f(int n)
{
  int out = n;
  if(!done)
  {  
      out = n * -1;
      done = true;
   }
   return out;
}

3
Не уверен, что это было целью задающего вопрос, но +1 за «нестандартное мышление».
Лиран Ореви

5
Вместо того чтобы условно сказать «сделано = истина», вы всегда должны сказать «сделано =! Сделано», чтобы ваша функция могла использоваться более одного раза.
Крис Лутц

@Chris, поскольку установка true для true находится внутри блока if (! Done), это эквивалентно done =! Done, но! Done не нужно вычислять (или оптимизировать компилятором, если он достаточно умен) ,
nsayer

1
Моей первой мыслью было также решить эту проблему, используя глобальную переменную, хотя казалось, что она обманывает этот конкретный вопрос. Однако я бы сказал, что решение с глобальными переменными является наилучшим решением с учетом спецификаций в вопросе. Использование глобального позволяет очень легко понять, что происходит. Я согласен, что готово! = Готово будет лучше, хотя. Просто переместите это за пределы условия if.
Альдерат

3
Технически все, что поддерживает состояние, является не функцией, а конечным автоматом. По определению , функция всегда дает один и тот же вывод для одного и того же ввода.
Тед Хопп

19

Это решение Perl работает для целых чисел, чисел с плавающей точкой и строк .

sub f {
    my $n = shift;
    return ref($n) ? -$$n : \$n;
}

Попробуйте некоторые тестовые данные.

print $_, ' ', f(f($_)), "\n" for -2, 0, 1, 1.1, -3.3, 'foo' '-bar';

Вывод:

-2 2
0 0
1 -1
1.1 -1.1
-3.3 3.3
foo -foo
-bar +bar

Но это не делает его int. По сути, вы храните данные глобальных переменных в самом int "n" ... за исключением того, что это не int, иначе вы не сможете этого сделать. Например, если бы nбыла строка, которую я мог бы сделать, 548 становится «First_Time_548», а затем в следующий раз, когда он проходит через функцию ... if (prefix == First_Time_ ") замените« First_Time_ »на« - »
Альберт Реншоу

@AlbertRenshaw Не уверен, где вы берете эти идеи. (1) Здесь определенно нет глобальных переменных. (2) Если вы передадите функции int, вы получите int или ссылку на int, если вызовете функцию нечетное количество раз. (3) Возможно, наиболее фундаментально, это Perl . Для всех практических целей, целые и строки полностью взаимозаменяемы. Строки, которые выглядят как числа, в большинстве контекстов будут отлично функционировать как числа, а числа будут с удовольствием структурироваться при любом запросе.
FMC

Извините, я не знаю много Perl, казалось, вы использовали глобальный массив хаха
Альберт Реншоу

18

Никто никогда не говорил, что f (x) должен быть того же типа.

def f(x):
    if type(x) == list:
        return -x[0]
    return [x]


f(2) => [2]
f(f(2)) => -2

16

На самом деле я не пытаюсь дать решение самой проблемы, но у меня есть пара комментариев, поскольку в вопросе говорится, что эта проблема была поставлена ​​как часть интервью (работа?):

  • Сначала я бы спросил: «Зачем нужна такая функция? В чем заключается большая проблема, частью которой она является?» вместо того, чтобы пытаться решить актуальную поставленную проблему на месте. Это показывает, как я думаю и как я решаю подобные проблемы. Кто знает? Это может даже быть фактической причиной, по которой вопрос задается во время интервью. Если ответ «Не бери в голову, подумай, что это необходимо, и покажи мне, как бы ты разработал эту функцию». Я бы тогда продолжил это делать.
  • Затем я написал бы код тестового примера C #, который я использовал бы (очевидный: цикл from int.MinValueв int.MaxValue, и для каждого nв этом вызове диапазона f(f(n))и проверяя результат -n), сказав, что затем я буду использовать Test Driven Development, чтобы добраться до такой функции.
  • Только если интервьюер продолжит просить меня решить поставленную проблему, я действительно начну пытаться набросать псевдокод во время самого интервью, чтобы попытаться найти какой-то ответ. Тем не менее, я не думаю, что буду прыгать, чтобы устроиться на работу, если интервьюер будет каким-либо признаком того, на что похожа компания ...

О, этот ответ предполагает, что интервью было для позиции, связанной с программированием на C #. Конечно, было бы глупым ответом, если бы собеседование проходило по математической позиции. ;-)


7
Вам повезло, что они попросили 32 int, если оно было 64-битным, интервью никогда не будет продолжаться после того, как вы запустите тесты ;-)
alex2k8

Действительно, если бы я даже дошел до того, чтобы действительно написать этот тест и запустить его во время интервью. ;-) Моя точка зрения: я бы постарался вообще не дойти до этого момента в интервью. На мой взгляд, программирование - это скорее «образ мышления», чем «как он пишет строки кода».
ПЕШИР

7
Не следуйте этому совету в реальном интервью. Интервьюер ожидает, что вы действительно ответите на вопрос. Вопрос об актуальности вопроса ничего не купит, но может раздражать интервьюера. Разработка тривиального теста не приближает вас к ответу, и вы не сможете выполнить его на собеседовании. Если вы получаете дополнительную информацию (32 бита), попробуйте выяснить, как это может быть полезно.
Стефан Хаустейн

Интервьюер, который раздражается, когда я спрашиваю дополнительную информацию (возможно, ставя под сомнение актуальность его вопроса в процессе), не является интервьюером, на которого я обязательно хочу работать. Поэтому я буду продолжать задавать подобные вопросы в интервью. Если им это не понравится, я, вероятно, закончу интервью, чтобы больше не тратить время впустую. Не нравится, когда «я следовал только приказам», одна мысль. Вы..?
peSHIr

16

Я бы вам изменил 2 самых значимых бита.

00.... => 01.... => 10.....

01.... => 10.... => 11.....

10.... => 11.... => 00.....

11.... => 00.... => 01.....

Как вы можете видеть, это всего лишь дополнение, исключающее переносимый бит.

Как я дошел до ответа? Моей первой мыслью была просто необходимость симметрии. 4 поворота, чтобы вернуться туда, где я начал. Сначала я подумал, что это 2-битный код Грея. Тогда я подумал, что на самом деле стандартного двоичного файла достаточно.


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

В вопросе указаны 32-разрядные целые числа со знаком. Это решение не работает для представлений с 32-разрядными знаковыми целыми числами, представляющих два дополнения или одно дополнение. Он будет работать только для представлений со знаком и величиной, что очень редко встречается в современных компьютерах (кроме чисел с плавающей запятой).
Джеффри Л Уитледж

1
@DrJokepu - Ух ты, через шесть месяцев - хрен!
Джеффри Л Уитледж

Разве вам не нужно просто преобразовывать числа в представление знака и величины внутри функции, выполнять преобразование, а затем преобразовывать обратно в то, чем является собственное целочисленное представление, прежде чем возвращать его?
Билл Мичелл

Мне нравится, что вы в основном реализовали комплексные числа, введя воображаемый бит :)
jabirali

16

Вот решение, основанное на требовании или утверждении, что комплексные числа не могут быть использованы для решения этой проблемы.

Умножение на квадратный корень из -1 является идеей, которая, похоже, не удалась, потому что -1 не имеет квадратного корня над целыми числами. Но игра с такой программой, как mathematica, дает, например, уравнение

(1849436465 2 +1) mod (2 32 -3) = 0.

и это почти так же хорошо, как квадратный корень из -1. Результатом функции должно быть целое число со знаком. Следовательно, я собираюсь использовать модифицированные моды по модулю (x, n), которые возвращают целое число y, совпадающее с x по модулю n, которое ближе всего к 0. Только очень немногие языки программирования имеют операцию по модулю, но ее легко определить , Например, в Python это:

def mods(x, n):
    y = x % n
    if y > n/2: y-= n
    return y

Используя приведенное выше уравнение, проблема теперь может быть решена как

def f(x):
    return mods(x*1849436465, 2**32-3)

Это удовлетворяет f(f(x)) = -xвсем целым числам в диапазоне . Результаты также находятся в этом диапазоне, но, конечно, для вычислений потребуются 64-битные целые числа.[-231-2, 231-2]f(x)


13

C # для диапазона от 2 ^ 32 до 1 числа, все числа int32, кроме (Int32.MinValue)

    Func<int, int> f = n =>
        n < 0
           ? (n & (1 << 30)) == (1 << 30) ? (n ^ (1 << 30)) : - (n | (1 << 30))
           : (n & (1 << 30)) == (1 << 30) ? -(n ^ (1 << 30)) : (n | (1 << 30));

    Console.WriteLine(f(f(Int32.MinValue + 1))); // -2147483648 + 1
    for (int i = -3; i <= 3  ; i++)
        Console.WriteLine(f(f(i)));
    Console.WriteLine(f(f(Int32.MaxValue))); // 2147483647

печатает:

2147483647
3
2
1
0
-1
-2
-3
-2147483647

Это также не работает для f (0), который равен 1073741824. f (1073741824) = 0. f (f (1073741824)) = 1073741824
Дина

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

12

По сути, функция должна делить доступный диапазон на циклы размера 4 с -n на противоположном конце цикла n. Тем не менее, 0 должен быть частью цикла размера 1, потому что в противном случае 0->x->0->x != -x. Поскольку 0 один, в нашем диапазоне должно быть 3 других значения (размер которых кратен 4), которые не находятся в правильном цикле с 4 элементами.

Я выбрал эти дополнительные странные значения MIN_INT, MAX_INTи MIN_INT+1. Кроме того, MIN_INT+1будет отображаться MAX_INTправильно, но застрять там, а не обратно. Я думаю, что это лучший компромисс, потому что он обладает хорошим свойством только экстремальных значений, которые не работают правильно. Кроме того, это означает, что это будет работать для всех BigInts.

int f(int n):
    if n == 0 or n == MIN_INT or n == MAX_INT: return n
    return ((Math.abs(n) mod 2) * 2 - 1) * n + Math.sign(n)

12

Никто не сказал, что это должно быть без гражданства.

int32 f(int32 x) {
    static bool idempotent = false;
    if (!idempotent) {
        idempotent = true;
        return -x;
    } else {
        return x;
    }
}

Обман, но не так много примеров. Еще большим злом было бы заглянуть в стек, чтобы увидеть, является ли адрес вашего вызывающего абонента & f, но это будет более переносимым (хотя и не поточно-безопасным ... поточно-ориентированная версия будет использовать TLS). Еще больше зла

int32 f (int32 x) {
    static int32 answer = -x;
    return answer;
}

Конечно, ни один из них не работает слишком хорошо для случая MIN_INT32, но вы мало что можете с этим поделать, если вам не разрешено возвращать более широкий тип.


вы можете «обновить» его, чтобы узнать адрес (да, вы должны получить его по ref \ в качестве указателя) - в C, например: int f (int & n) {static int * addr = & n; if (addr == & n) {return -n; } return n; }
IUnknownPointer

11

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


Это было бы более сложно, но не более эффективно, чем текущий лучший ответ
1800 ИНФОРМАЦИЯ

1
@ 1800 ИНФОРМАЦИЯ: С другой стороны, область [-2 ^ 30 + 1, 2 ^ 30-1] является смежной, что является более привлекательным с математической точки зрения.
Йохен Уолтер,

10

работает для n = [0 .. 2 ^ 31-1]

int f(int n) {
  if (n & (1 << 31)) // highest bit set?
    return -(n & ~(1 << 31)); // return negative of original n
  else
    return n | (1 << 31); // return n with highest bit set
}

10

Задача гласит «32-битные целые числа со знаком», но не указывает, являются ли они двойным или единичным дополнением .

Если вы используете дополнение с единичным значением, то все значения 2 ^ 32 встречаются в циклах длины четыре - вам не нужен специальный случай для нуля, а также вам не нужны условные выражения.

В С:

int32_t f(int32_t x)
{
  return (((x & 0xFFFFU) << 16) | ((x & 0xFFFF0000U) >> 16)) ^ 0xFFFFU;
}

Это работает

  1. Замена старшего и младшего 16-битных блоков
  2. Инвертирование одного из блоков

После двух проходов мы получаем побитовую инверсию исходного значения. Который в однополном представлении эквивалентен отрицанию.

Примеры:

Pass |        x
-----+-------------------
   0 | 00000001      (+1)
   1 | 0001FFFF (+131071)
   2 | FFFFFFFE      (-1)
   3 | FFFE0000 (-131071)
   4 | 00000001      (+1)

Pass |        x
-----+-------------------
   0 | 00000000      (+0)
   1 | 0000FFFF  (+65535)
   2 | FFFFFFFF      (-0)
   3 | FFFF0000  (-65535)
   4 | 00000000      (+0)

1
Как насчет порядка байтов в разных архитектурах?
Стивен

1
Вся арифметика 32-битная. Я не манипулирую отдельными байтами, поэтому порядок байтов на это не влияет.
Финн

Это звучит довольно близко. Вы можете предположить, что ввод 2-дополнение. Таким образом, вы конвертируете в представление знакового бита. Теперь, в зависимости от последнего бита, вы переворачиваете первый бит и последний бит или только последний бит. По сути, вы отрицаете только четные числа и все время чётные / нечетные. Таким образом, вы возвращаетесь от нечетного к нечетному и даже к четному после 2 вызовов. В конце вы конвертируете обратно в 2-дополнение. Выложили код для этого где-то ниже.
Стефан Хаустейн

9

: D

boolean inner = true;

int f(int input) {
   if(inner) {
      inner = false;
      return input;
   } else {
      inner = true;
      return -input;
   }
}

5
Может также дать вам дискуссию о том, почему глобальные переменные плохи, если они не выгнали вас прямо из интервью!
Palswim


7

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

Если я правильно помню, вы отрицаете 32-разрядное целое число со знаком, просто переворачивая первый бит. Например, если n = 1001 1101 1110 1011 1110 0000 1110 1010, то -n = 0001 1101 1110 1011 1110 0000 1110 1010.

Итак, как нам определить функцию f, которая принимает 32-разрядное целое число со знаком и возвращает другое 32-разрядное целое число со знаком со свойством, что двойное получение f равнозначно переключению первого бита?

Позвольте мне перефразировать вопрос, не упоминая арифметические понятия, такие как целые числа.

Как мы определяем функцию f, которая принимает последовательность нулей и единиц длины 32 и возвращает последовательность нулей и единиц одинаковой длины со свойством, что двойное взятие f совпадает с переключением первого бита?

Замечание: если вы можете ответить на поставленный выше вопрос для 32-битного регистра, то вы также можете ответить для 64-битного регистра, 100-битного регистра и т. Д. Вы просто применяете f к первым 32-битным.

Теперь, если вы можете ответить на вопрос для 2-битного случая, вуаля!

И да, оказывается, что изменение первых двух бит достаточно.

Вот псевдокод

1. take n, which is a signed 32-bit integer.
2. swap the first bit and the second bit.
3. flip the first bit.
4. return the result.

Примечание. Шаг 2 и шаг 3 вместе можно разделить на лету как (a, b) -> (-b, a). Выглядит знакомо? Это должно напомнить вам о повороте плоскости на 90 градусов и умножении на квадратный корень из -1.

Если бы я представил только один псевдокод без длинной прелюдии, это казалось бы кроликом из головы, я хотел бы объяснить, как я получил решение.


6
Да, это интересная проблема. Вы знаете свою математику. Но это проблема информатики. Так что вам нужно изучать компьютеры. Представление величины знака допустимо, но оно вышло из моды около 60 лет назад. 2-е дополнение является самым популярным.
Программист Windows,

5
Вот что ваша функция делает с двумя битами, когда применяется дважды: (a, b) -> (-b, a) -> (-a, -b). Но мы пытаемся добраться до (-a, b), а не (-a, -b).
бути-окса

@ buti-oxa, ты прав. Операция с двумя битами должна иметь вид: 00 -> 01 -> 10 -> 11 -> 00. Но тогда мой алгоритм предполагает представление величины знака, которое сейчас непопулярно, как сказал программист Windows, так что я думаю, что мой алгоритм бесполезен ,
Yoo

Так не может ли он просто выполнить шаги дважды, а не один раз?
Носредна

4
buti-oxa совершенно прав: функция даже не переворачивает первый бит после двух вызовов, она переворачивает первые два бита. Перебрасывание всех битов ближе к тому, что делает дополнение 2, но это не совсем правильно.
Редтуна
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.