Функція проектування f (f (n)) == -n


841

Питання, яке я отримав на своєму останньому інтерв'ю:

Створіть таку функцію f, що:

f(f(n)) == -n

Де ціле числоn з 32 бітами ; ви не можете використовувати складні арифметичні числа.

Якщо ви не можете спроектувати таку функцію для всього діапазону чисел, спроектуйте її для максимально можливого діапазону.

Будь-які ідеї?


2
Яка робота була для цього інтерв'ю?
tymtam

Відповіді:


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 # (працює для будь-якого подвійного, крім випадків переповнення):

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

Ви не сказали, якої мови вони очікували ... Ось статичне рішення (Haskell). Це в основному возиться з 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 (): функція () {повернути -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 = -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. Знову ж y не може бути -2³¹ (як f (y) = x, але f (-2³¹) = - x, а x не 0). Отже, -2³¹ = f (x) = f (f (y)) = - y, що неможливо. Тож дійсно 0 і -2³¹ слід відключити від решти (не зображення нічого іншого).
ShreevatsaR

1
@will Немає підписаних нулів, якщо (як я припускаю) ми говоримо про два 32-бітні цілі числа, що доповнюються.
goffrie

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;
}

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

25
@Skizz, повернення 0 від основного не потрібно в 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 => відмінити і відняти один
-n - 1 = - (n + 1) => додати один
-n => відмінити і додати його
 n + 1 => відняти
 н

Конкретний приклад такого циклу є +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 - бітових підписали itegers в межах від -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
Dan Olson

Впевнений, це робить для всіх негативних цифр, як він сказав. Це було частиною питання: Якщо ви не можете придумати загальне, придумайте той, який працює для найширшого діапазону.
jalf

Ця відповідь принаймні така ж гарна, як і відповідь Мардж Синовец та Роуленд Шоу, вона просто працює для іншого діапазону чисел
1800 ІНФОРМАЦІЯ

19
Чувак, ти можеш просто позбутися "ОНОВЛЕННЯ" і просто написати одну згуртовану правильну відповідь. Нижня 3/4 ("натхненна іншою відповіддю") є приголомшливою.
Андрес Яан Так

1
Мені дуже подобається abs-рішення для від'ємних чисел. Простий і зрозумілий.
Thorbjørn Ravn Andersen

97

Використовуючи складні числа, ви можете ефективно розділити завдання відкидання числа на два етапи:

  • помножимо n на i, і ви отримаєте n * i, який n повернутий на 90 ° проти годинникової стрілки
  • помножити знову на i, і ви отримаєте -n

Чудова річ, що вам не потрібен спеціальний код обробки. Просто множення на i виконує роботу.

Але вам заборонено використовувати складні числа. Тож вам доведеться якось створити власну уявну вісь, використовуючи частину діапазону даних. Оскільки вам потрібно рівно стільки уявних (проміжних) значень, скільки початкових, вам залишається лише половина діапазону даних.

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

  • Якщо n знаходиться в 0..63 (початковий діапазон), виклик функції додає 64, відображаючи n до діапазону 64..127 (проміжний діапазон)
  • Якщо n знаходиться в 64..127 (проміжний діапазон), функція віднімає n від 64, відображаючи n до діапазону 0 ..- 63

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

alt текст


4
@geschema, який інструмент ти використав для створення приємної графіки?
jwfearn

10
Вибачте, в цьому питанні явно немає складних цифр.
Rui Craveiro

6
@Liran: Я використовував OmniGraffle (лише для Mac)
geschema

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

1
@jrista в усіх рішеннях використовується другий вимір - це все, що насправді є «складними числами» (більшість використовує непарні проти парних, а вище використання float- проти int). «Кільце з 4 елементами», яке описує багато відповідей, потребує 4 станів, які можна представити у вигляді двох вимірів, кожен з яких має 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?!?
comonad

1
Зображення дійсно добре. Надіслати 0в 0і -2147483648до , -2147483648так як ці два числа є нерухомими точками для оператора заперечення, x => -x. Для решти цифр слідкуйте за стрілками на зображенні вище. Як видно з відповіді SurDin та її коментарів, у цьому випадку буде два числа, 2147483647і -2147483647жодна пара не може залишитись на обмін.
Jeppe Stig Nielsen

Це схоже на смайлик - з великою кількістю зморшок
Аншул

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
Підлий. Ліміт символів
Джо Філіпс

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
ІМО: функція f (n) {повернути n.пройдений? -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 зі "статичним 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 ++, ймовірно, дещо згинає правила, але працює для всіх числових типів (floats, ints, double) і навіть типів класів, які перевантажують одинарний мінус:

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 (underflow).


19

Використовує глобали ... але так?

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

3
Не впевнений, що це був задум запитувача, але +1 для "роздуму".
Ліран Ореві

5
Замість того, щоб умовно сказати "зроблено = правда", ви завжди повинні говорити "зроблено =! Зроблено", таким чином ваша функція може використовуватися не один раз.
Кріс Лутц

@Chris, оскільки параметр done to true - це блок if (! Done), він еквівалентний done =! Зроблено, але! Done не потрібно обчислювати (або оптимізувати компілятор, якщо він досить розумний) .
nsayer

1
Моя перша думка полягала також у тому, щоб вирішити це за допомогою глобальної змінної, навіть немовби це відчувало як обман щодо цього конкретного питання. Однак я б заперечував, що глобальне змінне рішення є найкращим рішенням, враховуючи специфікації в цьому питанні. Використання глобального дуже легко зрозуміє, що відбувається. Я погодився б, що краще! Просто перемістіть це поза пункту if.
Alderath

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", а потім наступного разу, коли він виконує функцію ... якщо (префікс == First_Time_ ") замінити" First_Time_ "на" - "
Альберт Реншо

@AlbertRenshaw Не впевнений, звідки ти береш ці ідеї. (1) Тут точно не задіяні глобальні змінні. (2) Якщо ви надаєте функції int, ви отримаєте назад int - або посилання на int, якщо ви будете називати функцію непарною кількістю разів. (3) Мабуть, найбільш принципово, це Perl . Для всіх практичних цілей вставки та рядки повністю взаємозамінні. Рядки, схожі на цифри, будуть функціонувати чудово, як цифри в більшості контекстів, і цифри будуть щасливо впорядковані, коли їх запитують.
FMc

Вибачте, я не знаю багато чого, здавалося, що ви використовуєте глобальний масив ха-ха
Альберт Реншо

18

Ніхто ніколи не говорив, що f (x) повинен бути одного типу.

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


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

16

Я насправді не намагаюся сам вирішити проблему, але маю пару коментарів, оскільки в питанні зазначено, що ця проблема була частиною інтерв'ю (робота?):

  • Я б спершу запитав "Навіщо потрібна така функція? Яка більша проблема, до якої належить це?" замість того, щоб намагатися вирішити фактично поставлену проблему на місці. Це показує, як я думаю і як вирішую подібні проблеми. Хто знає? Це може бути навіть справжньою причиною, що запитання задається в першу чергу інтерв'ю. Якщо відповідь - Нічого не заперечуйте, припустіть, що це потрібно, і покажіть мені, як би ви створили цю функцію. Тоді я б продовжував це робити.
  • Тоді я б написав код тесту C #, який би я використав (очевидно: цикл від int.MinValueдо int.MaxValue, і для кожного nвиклику в цьому діапазоні f(f(n))та перевірка результату є -n), кажу, що потім я використовую Test Driven Development, щоб дістатися до такої функції.
  • Тільки якщо інтерв'юер продовжує просити мене вирішити поставлену проблему, я б насправді почав намагатися писати псевдокод під час самого інтерв'ю, щоб спробувати дістати якусь відповідь. Однак я не думаю, що я б стрибав, щоб взяти на роботу, якщо інтерв'юер буде будь-яким свідченням того, що таке компанія ...

О, ця відповідь передбачає, що співбесіда була для позиції, пов'язаної з програмуванням C #. Звичайно, це було б дурною відповіддю, якби інтерв'ю займало математичну позицію. ;-)


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

Дійсно, якби я навіть дійшов до пункту, щоб насправді написати цей тест і запустити його під час інтерв'ю. ;-) Моя думка: я б намагався взагалі не дійти до цього пункту в інтерв'ю. На мою думку, програмування - це скоріше "спосіб мислення", ніж "як він пише рядки коду".
peSHIr

7
Не слідкуйте за цими порадами в реальному інтерв'ю. Інтерв'юер очікує, що ви дійсно відповісте на питання. Запитання про відповідність питання нічого не купить, але це може дратувати інтерв'юера. Створення тривіального тесту не наблизить вас до відповіді, і ви не можете його виконати в інтерв'ю. Якщо ви отримаєте додаткову інформацію (32 біт), спробуйте розібратися, чим це може бути корисно.
Стефан Хауштейн

Інтерв'юер, який дратується, коли я прошу отримати додаткову інформацію (при цьому, можливо, ставити під сумнів відповідність його питання в процесі), - це не інтерв'юер, над яким я обов'язково хочу працювати / з яким. Тож я продовжуватиму задавати такі питання в інтерв'ю. Якщо їм це не подобається, я, мабуть, закінчую інтерв'ю, щоб перестати витрачати обидва наш час. Не подобається, що розум "Я тільки виконував замовлення" встановлював один біт. Чи ти..?
peSHIr

16

Я б вам змінив 2 найбільш значущі біти.

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

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

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

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

Як бачимо, це лише доповнення, що виключає винесений шматочок.

Як я дійшов до відповіді? Перша моя думка була просто потребою в симетрії. 4 повороти, щоб повернутися з того місця, де я почав. Спочатку я подумав, що це 2bit сірий код. Тоді я подумав, що насправді стандартного бінарного достатньо.


Проблема такого підходу полягає в тому, що він не працює з компліментарними негативними цифрами двох (для цього використовується кожен сучасний процесор). Тому я видалив свою ідентичну відповідь.
Тамас Цінеге

У питанні вказано 32-бітні цілі числа. Це рішення не працює для представлень двох-доповнення або доповнення одного 32-бітових підписаних цілих чисел. Він буде працювати лише для представлення знаків і величин, які є дуже рідкісними для сучасних комп'ютерів (крім цифр з плаваючою комою).
Джеффрі Л Уітлідж

1
@DrJokepu - Нічого, через півроку - сурочок!
Джефрі Л Вітлідж

Чи не просто вам потрібно перетворити числа на представлення знаків і величин всередині функції, виконати перетворення, а потім перетворити назад у будь-яке місцеве ціле представлення перед поверненням?
Білл Мішель

Мені подобається, що ви в основному реалізували складні числа, ввівши уявний біт :)
jabirali

16

Ось рішення, яке надихається вимогою чи твердженням, що складні числа не можна використовувати для вирішення цієї проблеми.

Помноження на квадратний корінь на -1 - це ідея, яка здається невдалою, оскільки -1 не має квадратного кореня над цілими числами. Але граючи з такою програмою, як математика, дає, наприклад, рівняння

(1849436465 2 +1) мод (2 32 -3) = 0.

і це майже так само добре, як мати квадратний корінь -1. Результатом функції повинно бути підписане ціле число. Отже, я буду використовувати модифіковані модулі операцій модуля (x, n), які повертають ціле число y конгруентне до x modulo 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, але ви можете зробити це з цим мало, якщо вам не дозволяють повернути ширший тип.


ви можете "оновити" його, щоб запитати про адресу (так, ви повинні отримати її за посиланням \ як вказівник) - наприклад, в C: int f (int & n) {static int * addr = & n; якщо (addr == & n) {повернути -n; } повернути n; }
IUnknownPointer

11

Я міг би уявити, що використання 31-го біта як уявного ( і ) біта було б підходом, який підтримував би половину всього діапазону.


Це було б складніше, але не більш ефективно, ніж найкраща відповідь
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-розрядною. Я не маніпулюю окремими байтами, тому порядок байтів не вплине на це.
finnw

Це звучить досить близько. Можна припустити, що вхід є 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-бітного випадку, Voila!

І так, виявляється, що змінити перші 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

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

Тож чи не може він просто зробити кроки двічі замість одного разу?
Носредна

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