Funkcja projektowania f (f (n)) == -n


841

Pytanie, które otrzymałem podczas mojej ostatniej rozmowy:

Zaprojektuj funkcję ftaką, aby:

f(f(n)) == -n

Gdzie njest 32-bitowa liczba całkowita ze znakiem ; nie można używać arytmetyki liczb zespolonych.

Jeśli nie możesz zaprojektować takiej funkcji dla całego zakresu liczb, zaprojektuj ją dla największego możliwego zakresu.

Jakieś pomysły?


2
Do jakiej pracy była ta rozmowa kwalifikacyjna?
tymtam

Odpowiedzi:


377

Co powiesz na:

f (n) = znak (n) - (-1) n * n

W Pythonie:

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 automatycznie promuje liczby całkowite do dowolnych długości. W innych językach przepełni się największa dodatnia liczba całkowita, więc będzie działać dla wszystkich liczb całkowitych oprócz tej.


Aby działało dla liczb rzeczywistych, musisz zastąpić nw (-1) n przez { ceiling(n) if n>0; floor(n) if n<0 }.

W języku C # (działa dla każdego podwójnego, z wyjątkiem sytuacji przepełnienia):

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
Zepsuty dla -1, ponieważ -1 * 0 wciąż wynosi 0
Joel Coehoorn

3
Nie, nie jest. f (-1) = 0. f (0) = 1
1800 INFORMACJE

5
Jest jednak zepsuty dla 1. f (1) = 0. f (0) = 1
1800 INFORMACJE

18
Hmm, zapisywanie stanu z liczbami parzystymi i nieparzystymi, powinienem o tym pomyśleć.
Nieznany

38
Myślę, że najważniejszą rzeczą nie jest faktyczna funkcja (istnieje nieskończenie wiele rozwiązań), ale proces, w którym można zbudować taką funkcję.
pyon

440

Nie powiedziałeś, jakiego języka się spodziewają ... Oto rozwiązanie statyczne (Haskell). Zasadniczo zadziera z 2 najbardziej znaczącymi bitami:

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

Jest to o wiele łatwiejsze w dynamicznym języku (Python). Po prostu sprawdź, czy argument jest liczbą X i zwróć lambda, która zwraca -X:

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

23
Fajnie, uwielbiam to ... to samo podejście w JavaScript: var f = function (n) {return (typeof n == 'function')? n (): function () {return -n; }}
Mark Renouf,

Prawdopodobnie po prostu mój Haskell jest bardzo zardzewiały, ale czy sprawdziłeś to pod kątem (f 0)? Wygląda na to, że powinno to dać taki sam wynik jak (f 0x80000000), przynajmniej jeśli mamy do czynienia z 32-bitowymi liczbami całkowitymi z otaczającą arytmetyką (przy operacji negacji). I to byłoby złe.
Darius Bacon

11
Czy przeciętny wywiad nawet wiedzieć co konstrukt lambda jest ?
Jeremy Powell,

4
Oczywiście, taka typu oszustwo trik działa również w Haskell, nawet jeśli jest to statyczna: class C a b | a->b where { f :: a->b }; instance C Int (()->Int) where { f=const.negate }; instance C (()->Int) Int where { f=($()) }.
lewo około

4
Co? Skąd pomysł, że typeof f (n) === „funkcja”, szczególnie gdzie n jest liczbą i oczekujesz, że liczba zostanie zwrócona? Nie rozumiem, w jaki sposób można zastosować tutaj przypadek instancji. Nie mówię dobrze w Pythonie, ale w JS argument sprawdzania typu funkcji jest w tym przypadku po prostu niepoprawny. Obowiązuje tylko rozwiązanie numeryczne. f jest funkcją, f (n) jest liczbą.
Harry

284

Oto dowód, dlaczego taka funkcja nie może istnieć dla wszystkich liczb, jeśli nie używa dodatkowych informacji (z wyjątkiem 32 bitów int):

Musimy mieć f (0) = 0. (Dowód: Załóżmy, że f (0) = x. Następnie f (x) = f (f (0)) = -0 = 0. Teraz, -x = f (f (x )) = f (0) = x, co oznacza, że ​​x = 0.)

Ponadto, dla każdego xi y, przypuśćmy f(x) = y. Chcemy f(y) = -xwięc. I f(f(y)) = -y => f(-x) = -y… Podsumowując: jeśli f(x) = y, to f(-x) = -y, i f(y) = -x, i f(-y) = x.

Musimy więc podzielić wszystkie liczby całkowite oprócz 0 na zestawy 4, ale mamy nieparzystą liczbę takich liczb całkowitych; nie tylko to, że jeśli usuniemy liczbę całkowitą, która nie ma dodatniego odpowiednika, nadal będziemy mieć 2 (mod4) liczby.

Jeśli usuniemy 2 maksymalne liczby, które pozostały (według wartości abs), możemy uzyskać funkcję:

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

Oczywiście inną opcją jest nieprzestrzeganie 0 i otrzymanie 2 liczb, które usunęliśmy jako bonus. (Ale to tylko głupie, jeśli.)


29
Nie mogę uwierzyć, że musiałem przeczytać to tak daleko, aby znaleźć dobre rozwiązanie proceduralne, które obsługuje liczby ujemne bez uciekania się do zmiennych globalnych lub sztuczek, które zaciemniają kod. Gdybym mógł zagłosować więcej niż raz, zrobiłbym to.
Kyle Simek

Dobra obserwacja, że ​​w każdym n podpisanych bitach jest nieparzysta liczba niezerowa .
Andres Jaan Tack

To też byłaby moja odpowiedź, ale uwaga na przypadek na krawędzi n = -2147483648(minimalna wartość); nie możesz abs(n)w takim przypadku, a wynik będzie niezdefiniowany (lub wyjątek).
Kirk Broadhurst

1
@ a1kmm: Przepraszam, -2³² powyżej powinno być -2³¹. W każdym razie przypadek, w którym f (0) ≠ 0 (a więc f (0) = - 2³¹) jest w rzeczywistości łatwiejszym przypadkiem, ponieważ pokazaliśmy, że te dwa są odłączone od reszty. Innym przypadkiem, który musimy wziąć pod uwagę, jest to, że f (0) = 0, ale f (x) = - 2³¹ dla niektórych x ≠ 0, x ≠ -2³¹. W takim przypadku f (-2³¹) = f (f (x)) = - x (uwaga -x nie może wynosić -2³¹, ponieważ takiego x nie istnieje). Dalej niech f (-x) = y. Następnie f (y) = f (f (-x)) = x. Ponownie y nie może być -2³¹ (jako f (y) = x, ale f (-2³¹) = - x, a x nie jest równe 0). Zatem -2³¹ = f (x) = f (f (y)) = - y, co jest niemożliwe. Zatem rzeczywiście 0 i -2³¹ muszą być odłączone od reszty (a nie obrazu czegokolwiek innego).
ShreevatsaR

1
@will Nie ma zer zerowych, jeśli (jak zakładam) mówimy o 32-bitowych liczbach całkowitych z dopełnieniem dwóch.
goffrie

146

Dzięki przeciążeniu w 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
Niestety, z powodu zmiany nazwy, funkcje, które nazywasz „f”, faktycznie mają dziwniejsze nazwy.
pyon

1
Myślałem o czymś takim, ale myśląc w C, to zostało wyrzucone ... dobra robota!
Liran Orevi

@Rui Craverio: Nie działałoby w .NET 3.5+, ponieważ autor wybrał użycie słowa kluczowego var jako nazwy zmiennej.
Kredns

72
technicznie ... nie tego wymaga pytanie. zdefiniowałeś 2 funkcje f (), f (int) if (float), a pytania
brzmią:

2
@elcuco Technicznie rzecz biorąc, ale logicznie jest to jedna funkcja z wieloma przeciążeniami (z tym można zrobić f (f (42))). Ponieważ definicja nie mówi nic o parametrach i zwracanej wartości, nie mogę zaakceptować jej jako definicji technicznej.
Marek Toman,

135

Lub możesz nadużyć preprocesora:

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

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

Więc byłbyś Konradem „Le Chiffre” Rudolph? Wezmę płaszcz. Tak, wiem o całej rzeczy „void main”, ale dodając „return 0;” to po prostu dodatkowy wysiłek ;-)
Skizz

25
@Skizz, zwróć 0 z main nie jest wymagane w c ++, nawet jeśli int zwraca wartość ... więc robiąc to dobrze, wpisujesz jeden znak mniej!
Dan Olson,

10
Skizz zawsze nadużywa preprocesora: D
Arnis Lapsa

23
To nie jest funkcja ... więc to nie jest poprawne rozwiązanie
smerlin

3
@ smerlin: Technicznie jest to funkcja wbudowana zwracająca funkcję wbudowaną: ciała obu są rozszerzane podczas kompilacji, a raczej tuż przed nią. Nie można uzyskać dużo bardziej wydajnego niż to.
Jon Purdy,

103

Dotyczy to wszystkich liczb ujemnych.

    f (n) = abs (n)

Ponieważ istnieje jeszcze jedna liczba ujemna niż liczba dodatnia dla dwóch liczb całkowitych uzupełniających, f(n) = abs(n) jest ważna dla jednego przypadku więcej niż f(n) = n > 0 ? -n : nrozwiązanie, które jest takie samo jak f(n) = -abs(n). Mam cię po jednym ...: D

AKTUALIZACJA

Nie, nie dotyczy to więcej niż jednego przypadku, jak właśnie rozpoznałem w komentarzu litba ... abs(Int.Min) po prostu się przepełni ...

Myślałem też o użyciu informacji o modzie 2, ale doszedłem do wniosku, że to nie działa ... na początku. Jeśli zrobisz to poprawnie, zadziała dla wszystkich liczb opróczInt.Min tego, że się przepełni.

AKTUALIZACJA

Bawiłem się nim przez pewien czas, szukając ładnej sztuczki manipulacji, ale nie mogłem znaleźć ładnego jednoliniowego, podczas gdy rozwiązanie mod 2 pasuje do jednego.

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

W języku C # wygląda to następująco:

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

Aby to działa dla wszystkich wartości, trzeba wymienić Math.Abs()z (n > 0) ? +n : -nobejmują obliczenia w uncheckedbloku. Następnie zostajesz nawet Int.Minzmapowany do siebie, tak jak robi to niesprawdzona negacja.

AKTUALIZACJA

Zainspirowany inną odpowiedzią wyjaśnię, jak działa ta funkcja i jak ją zbudować.

Zacznijmy od samego początku. Funkcja fjest wielokrotnie stosowana do danej wartościn dając ciąg wartości.

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

Pytanie wymaga f(f(n)) = -n, aby były to dwa kolejne zastosowania fnegacji argumentu. Dwa kolejne zastosowania f- w sumie cztery - negują ponownie ten argumentn ponownie.

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

Teraz jest oczywisty cykl długości czterech. Podstawiając x = f(n)i zauważając, że otrzymane równanie f(f(f(n))) = f(f(x)) = -xzachowuje, otrzymujemy następujące wyniki.

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

Otrzymujemy więc cykl długości cztery z dwiema liczbami i dwie liczby zanegowane. Jeśli wyobrażasz sobie cykl jako prostokąt, zanegowane wartości znajdują się w przeciwległych rogach.

Jednym z wielu rozwiązań pozwalających skonstruować taki cykl jest początek od n.

 n => neguj i odejmij jeden
-n - 1 = - (n + 1) => dodaj jeden
-n => zaneguj i dodaj jeden
 n + 1 => odejmij jeden
 n

Konkretnym przykładem takiego cyklu jest +1 => -2 => -1 => +2 => +1. Prawie skończyliśmy. Zwracając uwagę, że skonstruowany cykl zawiera nieparzystą liczbę dodatnią, jego parzysty następca i obie liczby negują, możemy z łatwością podzielić liczby całkowite na wiele takich cykli ( 2^32jest wielokrotnością czterech) i znaleźliśmy funkcję, która spełnia warunki.

Ale mamy problem ze zerem. Cykl musi zawierać, 0 => x => 0ponieważ zero jest negowane samo w sobie. A ponieważ cykl stwierdza, 0 => xże już następuje 0 => x => 0 => x. Jest to tylko cykl długości dwóch i xzamienia się w siebie po dwóch aplikacjach, a nie w -x. Na szczęście jest jeden przypadek, który rozwiązuje problem. Jeśli Xjest równy zeru, otrzymujemy cykl długości zawierający tylko zero i rozwiązaliśmy ten problem, stwierdzając, że zero jest stałym punktem f.

Gotowy? Prawie. Mamy 2^32liczby, zero to stały punkt pozostawiający 2^32 - 1liczby i musimy podzielić tę liczbę na cykle czterech liczb. Złe, że 2^32 - 1nie jest wielokrotnością czterech - pozostaną trzy liczby nie w żadnym cyklu długości czterech.

Wyjaśnię pozostałą część rozwiązania przy użyciu mniejszego zestawu 3-bitowych liczb całkowitych ze znakiem od -4do +3. Skończyliśmy z zerem. Mamy jeden pełny cykl +1 => -2 => -1 => +2 => +1. Teraz skonstruujmy cykl zaczynając od +3.

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

Powstaje problem polegający na tym, że +4nie jest reprezentowana jako 3-bitowa liczba całkowita. Chcielibyśmy uzyskać +4poprzez zanegowanie -3się +3- co jest jeszcze ważne całkowitą 3 bit - ale potem dodając jeden do +3(binarnie 011) daje 100binarny. Jest interpretowany jako liczba całkowita bez znaku, +4ale musimy interpretować go jako liczbę całkowitą ze znakiem -4. Tak więc w rzeczywistości -4w tym przykładzie lub Int.MinValuew ogólnym przypadku jest drugim stałym punktem całkowitej negacji arytmetycznej - 0 i Int.MinValuesą one odwzorowane na nich samych. Tak więc cykl jest następujący.

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

Jest to cykl długości dwóch i dodatkowo +3wchodzi w cykl poprzez -4. W konsekwencji -4jest poprawnie odwzorowany na siebie po dwóch aplikacjach funkcyjnych, +3jest poprawnie odwzorowany na -3dwóch aplikacjach funkcyjnych, ale -3błędnie jest odwzorowany na sobie po dwóch aplikacjach funkcyjnych.

Stworzyliśmy więc funkcję, która działa dla wszystkich liczb całkowitych oprócz jednej. Czy możemy zrobić lepiej? Nie, nie możemy. Dlaczego? Musimy skonstruować cykle o długości czterech i jesteśmy w stanie objąć cały zakres liczb całkowitych do czterech wartości. Pozostałe wartości są dwa punkty stałe 0i Int.MinValuektóre muszą być przypisane do siebie i dwóch dowolnych liczb całkowitych xi -xktóre muszą być przypisane do siebie przez dwa wnioski funkcyjnych.

Mapować xdo -xi odwrotnie muszą tworzyć cztery cyklu i muszą być umieszczone na przeciwległych rogach tego cyklu. W konsekwencji 0i Int.MinValuemuszą znajdować się również w przeciwległych rogach. To będzie poprawnie map xi -xjednak zamienić dwa punkty stałe 0i Int.MinValuepo dwóch zastosowaniach funkcyjnych i zostawić nas z dwoma wejściami upadających. Dlatego nie jest możliwe zbudowanie funkcji, która działa dla wszystkich wartości, ale mamy taką, która działa dla wszystkich wartości oprócz jednej i jest to najlepsze, co możemy osiągnąć.


Nie spełnia kryteriów: abs (abs (n))! = -N
Dan Olson

Jasne, że tak, dla wszystkich liczb ujemnych, jak powiedział. To było częścią pytania: jeśli nie możesz wymyślić ogólnego, wymyśl taki, który działa w możliwie najszerszym zakresie.
lipiec

Ta odpowiedź jest co najmniej tak dobra, jak odpowiedź Marja Synowca i Rowlanda Shawa, po prostu działa na inny zakres liczb
1800 INFORMACJI

19
Stary, równie dobrze możesz po prostu pozbyć się „AKTUALIZACJI” i napisać jedną spójną poprawną odpowiedź. Dolna 3/4 („zainspirowana inną odpowiedzią”) jest niesamowita.
Andres Jaan Tack

1
Naprawdę podoba mi się rozwiązanie abs dla liczb ujemnych. Prosty i łatwy do zrozumienia.
Thorbjørn Ravn Andersen

97

Używając liczb zespolonych, możesz skutecznie podzielić zadanie negowania liczby na dwa etapy:

  • pomnóż n przez i, a otrzymasz n * i, który jest n obrócony o 90 ° przeciwnie do ruchu wskazówek zegara
  • pomnóż ponownie przez i, a otrzymasz -n

Wspaniałą rzeczą jest to, że nie potrzebujesz specjalnego kodu obsługi. Po prostu pomnożenie przez i wykonuje pracę.

Ale nie wolno ci używać liczb zespolonych. Musisz więc jakoś stworzyć własną wyobrażoną oś, wykorzystując część swojego zakresu danych. Ponieważ potrzebujesz dokładnie tyle wyimaginowanych (pośrednich) wartości, co wartości początkowe, pozostaje Ci tylko połowa zakresu danych.

Próbowałem to wyobrazić na poniższym rysunku, zakładając podpisane dane 8-bitowe. Trzeba to skalować dla 32-bitowych liczb całkowitych. Dopuszczalny zakres dla początkowego n wynosi od -64 do +63. Oto, co funkcja robi dla dodatniego n:

  • Jeśli n jest w zakresie 0..63 (zakres początkowy), wywołanie funkcji dodaje wartość 64, odwzorowując n na zakres 64..127 (zakres pośredni)
  • Jeśli n jest w zakresie 64..127 (zakres pośredni), funkcja odejmuje n od 64, odwzorowując n na zakres 0 ..- 63

Dla ujemnego n funkcja wykorzystuje zakres pośredni -65 ..- 128.

alternatywny tekst


4
@geschema, jakiego narzędzia użyłeś do stworzenia ładnej grafiki?
jwfearn 12.04.2009

10
Przepraszamy, pytanie wyraźnie nie zawiera liczb zespolonych.
Rui Craveiro

6
@Liran: Użyłem OmniGraffle (tylko Mac)
geschema

39
+1 Myślę, że to najlepsza odpowiedź. Nie sądzę, by ludzie czytali wystarczająco, ponieważ wszyscy zauważyli, że w pytaniu było powiedziane, że nie można użyć liczb zespolonych. Przeczytałem całość i opisałeś rozwiązanie liczbami zespolonymi, aby przygotować grunt pod nieskomplikowane rozwiązanie zadanego pytania. Bardzo ładnie wykonane.
jrista

1
@Jrista wszystkie rozwiązania używają drugiego wymiaru, czyli tak naprawdę wszystkich „liczb zespolonych” (większość używa liczb nieparzystych w porównaniu do parzystych, a powyżej używa w floatporównaniu do int). „4-elementowy pierścień”, który opisuje wiele odpowiedzi, wymaga 4 stanów, które można przedstawić jako 2 wymiary, każdy z 2 stanami. Problem z tą odpowiedzią jest to, że wymaga dodatkowej przestrzeni procesowej (tylko „działa” na -64..63 jeszcze potrzeby -128..127 kosmiczne) i nie ma jednoznacznie stwierdzić, napisany formuły!
Kirk Broadhurst

65

Działa z wyjątkiem int.MaxValue i 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));
    }

obrazowy


Nie jestem pewien, dlaczego zostało to odrzucone. Dla jakich danych wejściowych zawodzi?
Rodrick Chapman

Dlaczego nie korzystasz z funkcji signum?!?
comonad

1
Obraz jest naprawdę dobry. Wyślij 0do 0i -2147483648do -2147483648ponieważ te dwie liczby stałych punktów dla operatora negacji x => -x. W przypadku pozostałych liczb postępuj zgodnie ze strzałkami na obrazku powyżej. Jak wynika z odpowiedzi i komentarzy SurDin, w tym przypadku będą dwie liczby 2147483647i -2147483647nie pozostanie żadna inna para na zamianę.
Jeppe Stig Nielsen

Wygląda jak buźka - z dużą ilością zmarszczek
Anshul

48

Pytanie nie mówi nic o tym, jaki fmusi być typ wejścia i zwracana wartość funkcji (przynajmniej nie tak, jak to przedstawiłeś) ...

... tylko wtedy, gdy n jest 32-bitową liczbą całkowitą f(f(n)) = -n

A co powiesz na coś takiego

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

Jeśli n jest 32-bitową liczbą całkowitą, wówczas instrukcja f(f(n)) == -nbędzie prawdziwa.

Oczywiście to podejście można rozszerzyć na jeszcze szerszy zakres liczb ...


2
Podstępny. Limit znaków.
Joe Phillips

2
Tak, pracowałem nad podobnym podejściem. Ale mnie do tego pobiłeś. +1 :)
lipiec

1
Bardzo mądry! Jest to bardzo bliskie (i faktycznie takie samo jak) używanie liczb zespolonych, co byłoby oczywistym i idealnym rozwiązaniem, ale wyraźnie zabroniono. Praca poza zakresem dopuszczalnych liczb.
Kirk Broadhurst,

48

dla javascript (lub innych języków dynamicznie wpisywanych) możesz mieć funkcję akceptującą int lub obiekt i zwracającą drugą. to znaczy

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

dający

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

alternatywnie możesz użyć przeciążenia w silnie typowanym języku, chociaż może to złamać zasady tj

int f(long n) {
    return n;
}

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

To ostatnie nie oznacza wymogu funkcji „a” (liczba pojedyncza). :)
Drew

Usuń drugą połowę odpowiedzi, a to jest poprawna odpowiedź.
jmucchiello,

@Drew, dlatego wspomniałem, że może to złamać zasady
cobbal

2
W JavaScript funkcja jest obiektem, więc może utrzymać stan.
Nosredna

1
IMO: funkcja f (n) {return n.passed? -n.val: {val: n, pass: 1}} jest bardziej czytelny i krótszy.
SamGoody,

46

W zależności od platformy niektóre języki umożliwiają zachowanie stanu funkcji. VB.Net, na przykład:

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

    Return n * flag
End Function

IIRC, C ++ również na to pozwoliły. Podejrzewam jednak, że szukają innego rozwiązania.

Innym pomysłem jest to, że skoro nie zdefiniowali wyniku pierwszego wywołania funkcji, można użyć nieparzystości / parzystości do kontrolowania, czy odwrócić znak:

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

Dodaj jeden do wielkości wszystkich liczb parzystych, odejmij jeden od wielkości wszystkich liczb nieparzystych. Wynik dwóch wezwań ma taką samą wielkość, ale jedno wywołanie, w którym nawet zamieniamy znak. W niektórych przypadkach to nie zadziała (-1, maks. Lub min. Int), ale działa o wiele lepiej niż cokolwiek innego sugerowanego do tej pory.


1
Wierzę, że to działa dla MAX_INT, ponieważ zawsze jest to dziwne. Nie działa dla MIN_INT i -1.
Airsource Ltd,

9
To nie jest funkcja, jeśli ma skutki uboczne.
nos

12
To może być prawda z matematyki, ale nie ma znaczenia w programowaniu. Pytanie brzmi, czy szukają rozwiązania matematycznego, czy programistycznego. Ale biorąc pod uwagę, że jest to praca programistyczna ...
Ryan Lundy,

+1 Zamierzałem opublikować jeden w C z „static int x” implementującym FIFO z negacją wyjścia. Ale to wystarczająco blisko.
phkahler

2
@nos: Tak, po prostu nie jest referencyjnie przejrzysty.
Clark Gaebel,

26

Wykorzystywanie wyjątków JavaScript.

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

f(f(0)) => 0

f(f(1)) => -1


Wątpię, by wcześniej zastosowano takie wyjątki ... :)
NoBugs

+1 Nieszablonowe myślenie. Fajne! Ale w kodzie produkcyjnym użyłbym typeof tylko dla bezpieczeństwa.

21

Dla wszystkich wartości 32-bitowych (z zastrzeżeniem, że -0 wynosi -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);
}

Zasadniczo musisz sparować każdą pętlę -x => x => -x z pętlą ay => -y => y. Więc sparowałem przeciwne strony split.

np. dla 4-bitowych liczb całkowitych:

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

21

Wersja C ++, prawdopodobnie nieco zginająca reguły, ale działa dla wszystkich typów liczbowych (liczba zmiennoprzecinkowa, liczba całkowita, liczba podwójna), a nawet typów klas, które przeciążają jednoargumentowy minus:

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

Dobry pomysł. Alternatywnie, prawdopodobnie możesz stracić strukturę i zamiast tego jedna funkcja zwróci wskaźnik, a druga dereferencja i negacja.
Imbue

20

x86 asm (styl 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

Kod sprawdzony, wszystkie możliwe 32-bitowe liczby całkowite przeszły, błąd -2147483647 (niedopełnienie).


19

Używa globali ... ale tak?

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

3
Nie jestem pewien, czy taka była intencja osoby zadającej pytania, ale +1 za „myślenie po wyjęciu z pudełka”.
Liran Orevi

5
Zamiast warunkowego powiedzenia „done = true”, zawsze powinieneś powiedzieć „done =! Done”, w ten sposób możesz użyć swojej funkcji więcej niż raz.
Chris Lutz

@Chris, ponieważ ustawienie true na wartość true znajduje się w bloku if (! Done), jest to równoważne z done =! .
nsayer

1
Moją pierwszą myślą było również rozwiązanie tego problemu za pomocą zmiennej globalnej, chociaż wydawało się, że to oszustwo. Chciałbym jednak argumentować, że rozwiązanie zmiennej globalnej jest najlepszym rozwiązaniem, biorąc pod uwagę specyfikacje w pytaniu. Używanie globalnego bardzo ułatwia zrozumienie, co się dzieje. Zgodziłbym się, że lepiej zrobić! Po prostu przenieś to poza klauzulę if.
Alderath,

3
Technicznie wszystko, co utrzymuje stan, nie jest funkcją, ale maszyną stanu. Z definicji funkcja zawsze daje to samo wyjście dla tego samego wejścia.
Ted Hopp

19

To rozwiązanie Perla działa na liczbach całkowitych, zmiennoprzecinkowych i ciągach .

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

Wypróbuj niektóre dane testowe.

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

Wynik:

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

Ale to nie utrzymuje int. Zasadniczo przechowujesz dane zmiennych globalnych w samym int "n" ... z wyjątkiem tego, że nie jest to int, inaczej nie możesz tego zrobić. Na przykład, jeśli nbyłbym ciągiem, który mógłbym zrobić, 548 staje się „First_Time_548”, a następnie następnym razem przechodzi przez funkcję ... jeśli (przedrostek == First_Time_ ”) zamień„ First_Time_ ”na„ - ”
Albert Renshaw

@AlbertRenshaw Nie jestem pewien, skąd te pomysły. (1) Zdecydowanie nie ma tu żadnych zmiennych globalnych. (2) Jeśli podasz funkcji int, otrzymasz int z powrotem - lub odniesienie do int, jeśli wywołasz funkcję nieparzystą liczbę razy. (3) Być może najbardziej fundamentalnie, to Perl . Dla wszystkich praktycznych celów int i ciągi znaków są w pełni zamienne. Ciągi, które lubią wyglądać jak liczby, będą działały doskonale jako liczby w większości kontekstów, a liczby z przyjemnością będą sznurek na każde zapytanie.
FMc

Przykro mi, ale nie wiem zbyt wiele o perl. Wydawało się, że używasz globalnej tablicy haha
Albert Renshaw,

18

Nikt nigdy nie powiedział, że f (x) musi być tego samego typu.

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


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

16

Właściwie nie próbuję rozwiązać samego problemu, ale mam kilka komentarzy, ponieważ pytanie mówi, że ten problem został postawiony w ramach wywiadu (pracy?):

  • Najpierw zapytam: „Dlaczego taka funkcja byłaby potrzebna? Jaki jest większy problem?” zamiast próbować rozwiązać rzeczywisty problem na miejscu. To pokazuje, jak myślę i jak sobie radzę z takimi problemami. Kto wie? To może być nawet faktyczny powód, dla którego pytanie zadaje się w wywiadzie. Jeśli odpowiedź brzmi: „Nieważne, załóż, że jest potrzebna i pokaż mi, jak zaprojektowałbyś tę funkcję”. W takim razie kontynuowałbym to.
  • Następnie napisałbym kod przypadku testowego C #, którego użyłbym (oczywiste: zapętlenie od int.MinValuedo int.MaxValue, i dla każdego nw tym zakresie wywołanie f(f(n))i sprawdzenie wyniku to-n ), mówiąc, że użyję Test Driven Development, aby dostać się do takiej funkcji.
  • Tylko jeśli osoba przeprowadzająca rozmowę nadal prosi mnie o rozwiązanie postawionego problemu, zacznę pisać kod pseudokodowy podczas samego wywiadu, aby uzyskać odpowiedź. Jednak tak naprawdę nie sądzę, że skoczyłbym do pracy, jeśli osoba przeprowadzająca rozmowę kwalifikacyjną byłaby jakimkolwiek wskaźnikiem tego, jaka jest firma ...

Och, ta odpowiedź zakłada, że ​​wywiad dotyczył stanowiska związanego z programowaniem w języku C #. Byłoby oczywiście głupią odpowiedzią, gdyby wywiad dotyczył stanowiska matematycznego. ;-)


7
Masz szczęście, że poprosili o 32 int. Jeśli to 64-bit, wywiad nigdy nie będzie kontynuowany po uruchomieniu testów ;-)
alex2k8

Rzeczywiście, gdybym nawet doszedł do punktu, aby napisać ten test i przeprowadzić go podczas wywiadu. ;-) Moja uwaga: w ogóle nie próbowałbym dojść do tego punktu w wywiadzie. Moim zdaniem programowanie jest bardziej „sposobem myślenia” niż „jak pisze wiersze kodu”.
peSHIr 14.04.2009

7
Nie postępuj zgodnie z tą radą w prawdziwym wywiadzie. Ankieter oczekuje, że faktycznie odpowiesz na pytanie. Kwestionowanie trafności pytania niczego nie kupi, ale może denerwować ankietera. Zaprojektowanie trywialnego testu nie przybliża cię do odpowiedzi i nie możesz przeprowadzić go w wywiadzie. Jeśli otrzymujesz dodatkowe informacje (32-bitowe), spróbuj dowiedzieć się, jak to może być przydatne.
Stefan Haustein

Ankieter, który denerwuje się, gdy pytam o więcej informacji (choć może podważam trafność jego pytania w tym procesie), nie jest ankieterem, z którym koniecznie chcę współpracować. Będę więc zadawać takie pytania w wywiadach. Jeśli im się to nie spodoba, prawdopodobnie zakończę wywiad, żeby nie marnować więcej czasu. Nie podoba mi się zdanie „Tylko wykonałem rozkazy”. Czy ty..?
peSHIr

16

Chciałbym zmienić 2 najbardziej znaczące bity.

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

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

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

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

Jak widać, to tylko dodatek, pomijając przeniesiony kawałek.

Jak dotarłem do odpowiedzi? Moją pierwszą myślą była potrzeba symetrii. 4 obroty, by wrócić tam, gdzie zacząłem. Na początku myślałem, że to 2-bitowy kod Graya. Pomyślałem wtedy, że wystarczy standardowy plik binarny.


Problem z tym podejściem polega na tym, że nie działa on z komplementarnymi liczbami ujemnymi dwóch (czego używa każdy współczesny procesor). Dlatego usunąłem moją identyczną odpowiedź.
Tamas Czinege

W pytaniu określono 32-bitowe liczby całkowite ze znakiem. To rozwiązanie nie działa w przypadku reprezentacji uzupełnienia dwójkowego lub uzupełnienia dwójkowego 32-bitowych liczb całkowitych ze znakiem. Będzie działać tylko w przypadku reprezentacji znaku i wielkości, które są bardzo rzadkie w nowoczesnych komputerach (innych niż liczby zmiennoprzecinkowe).
Jeffrey L Whitledge

1
@DrJokepu - Wow, po sześciu miesiącach - jinx!
Jeffrey L Whitledge

Czy nie musisz po prostu konwertować liczb na reprezentację znaku i wielkości wewnątrz funkcji, wykonać transformację, a następnie przekonwertować z powrotem na natywną reprezentację liczb całkowitych przed jej zwróceniem?
Bill Michell,

Podoba mi się, że w zasadzie zaimplementowałeś liczby zespolone, wprowadzając fikcyjny bit :)
jabirali

16

Oto rozwiązanie inspirowane wymogiem lub twierdzeniem, że nie można użyć liczb zespolonych do rozwiązania tego problemu.

Pomnożenie przez pierwiastek kwadratowy z -1 jest pomysłem, który wydaje się zawodzić, ponieważ -1 nie ma pierwiastka kwadratowego nad liczbami całkowitymi. Ale zabawa z programem takim jak matematyka daje na przykład równanie

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

i jest to prawie tak dobre, jak posiadanie pierwiastka kwadratowego z -1. Wynikiem funkcji musi być liczba całkowita ze znakiem. Dlatego zamierzam użyć zmodyfikowanych modów operacji modulo (x, n), które zwracają liczbę całkowitą y przystającą do x modulo n, która jest najbliższa 0. Tylko kilka języków programowania ma operację modulo, ale można ją łatwo zdefiniować . Np. W python jest to:

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

Korzystając z powyższego równania, problem można teraz rozwiązać jako

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

Spełnia to f(f(x)) = -xwszystkie liczby całkowite w zakresie . Wyniki są również w tym zakresie, ale oczywiście obliczenia wymagałyby 64-bitowych liczb całkowitych.[-231-2, 231-2]f(x)


13

C # dla zakresu 2 ^ 32-1 liczb, wszystkie liczby int32 oprócz (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

drukuje:

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

To również nie działa dla f (0), który wynosi 1073741824. f (1073741824) = 0. f (f (1073741824)) = 1073741824
Dinah

Zasadniczo możesz udowodnić, że dla liczb całkowitych typu uzupełnienie dwóch o dowolnym rozmiarze bitowym funkcja nie musi działać dla co najmniej dwóch wartości wejściowych.
slacker

12

Zasadniczo funkcja musi podzielić dostępny zakres na cykle wielkości 4, przy czym -n na przeciwległym końcu cyklu n. Jednak 0 musi być częścią cyklu o rozmiarze 1, ponieważ w przeciwnym razie0->x->0->x != -x . Ponieważ 0 jest sam, muszą być 3 inne wartości w naszym zakresie (których rozmiar jest wielokrotnością 4), które nie są w prawidłowym cyklu z 4 elementami.

Wybrałem te dodatkowe wartości dziwne być MIN_INT, MAX_INTi MIN_INT+1. Co więcej, MIN_INT+1odwzoruje MAX_INTpoprawnie, ale utknie tam i nie odwróci mapy. Myślę, że jest to najlepszy kompromis, ponieważ ma tę fajną właściwość, że ekstremalne wartości nie działają poprawnie. Oznacza to również, że będzie działać dla wszystkich 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

Nikt nie powiedział, że to musi być bezpaństwowiec.

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

Oszukiwanie, ale nie tak wiele przykładów. Jeszcze większym złem byłoby zerknięcie na stos, aby sprawdzić, czy adres dzwoniącego to & f, ale będzie on bardziej przenośny (chociaż nie jest bezpieczny dla wątków ... wersja dla wątków używa TLS). Jeszcze więcej zła:

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

Oczywiście żadne z tych nie działa zbyt dobrze w przypadku MIN_INT32, ale niewiele można na to poradzić, chyba że pozwolimy ci zwrócić szerszy typ.


możesz go „uaktualnić”, aby zapytać o adres (tak, musisz uzyskać go przez ref \ jako wskaźnik) - w C, na przykład: int f (int & n) {static int * addr = & n; if (addr == & n) {return -n; } return n; }
IUnknownPointer

11

Mogę sobie wyobrazić, że użycie 31-go bitu jako fikcyjnego ( i ) bitu byłoby podejściem, które obsługiwałoby połowę całkowitego zakresu.


Byłoby to bardziej złożone, ale nie bardziej skuteczne niż obecna najlepsza odpowiedź
1800 INFORMACJI z

1
@ 1800 INFORMACJE: Z drugiej strony, domena [-2 ^ 30 + 1, 2 ^ 30-1] jest przyległa, co jest bardziej atrakcyjne z matematycznego punktu widzenia.
Jochen Walter,

10

działa dla 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

Problem mówi „32-bitowe liczby całkowite ze znakiem”, ale nie określa, czy są one dopełniane do dwóch, czy do jednego .

Jeśli użyjesz jedynego uzupełnienia, wówczas wszystkie wartości 2 ^ 32 występują w cyklach o długości czwartej - nie potrzebujesz specjalnego przypadku dla zera, a także nie potrzebujesz warunków warunkowych.

W C:

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

Działa to przez

  1. Wymiana wysokich i niskich 16-bitowych bloków
  2. Odwracanie jednego z bloków

Po dwóch przejściach mamy bitową odwrotność oryginalnej wartości. Które w reprezentacji jednego dopełniacza jest równoważne negacji.

Przykłady:

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
Co z kolejnością bajtów w różnych architekturach?
Steven,

1
Cała arytmetyka jest 32-bitowa. Nie manipuluję poszczególnymi bajtami, więc kolejność bajtów na to nie wpłynie.
finnw

To brzmi dość blisko. Możesz założyć, że dane wejściowe to 2-uzupełnienie. Więc konwertujesz na reprezentację bitów znaku. Teraz, w zależności od ostatniego bitu, odwracasz pierwszy i ostatni bit lub tylko ostatni bit. Zasadniczo negujesz tylko liczby parzyste i cały czas cyklicznie parzyste / nieparzyste. Więc wracasz z nieparzystych na nieparzyste, a nawet nawet po 2 połączeniach. Na koniec konwertujesz z powrotem na 2-uzupełnienia. Umieściłem kod do tego gdzieś poniżej.
Stefan Haustein

9

:RE

boolean inner = true;

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

5
Być może uda Ci się również przedyskutować, dlaczego zmienne globalne są złe, jeśli nie wyrzucą cię z wywiadu!
palswim


7

Chciałbym podzielić się moim poglądem na ten interesujący problem jako matematyk. Myślę, że mam najbardziej wydajne rozwiązanie.

Jeśli dobrze pamiętam, negujesz 32-bitową liczbę całkowitą ze znakiem, po prostu odwracając pierwszy bit. Na przykład, jeśli n = 1001 1101 1110 1011 1110 0000 1110 1010, to -n = 0001 1101 1110 1011 1110 0000 1110 1010.

Jak więc zdefiniować funkcję f, która pobiera 32-bitową liczbę całkowitą ze znakiem i zwraca kolejną 32-bitową liczbę całkowitą ze znakiem, że dwukrotne pobranie f jest tym samym, co odwrócenie pierwszego bitu?

Pozwól, że sformułuję to pytanie, nie wspominając o pojęciach arytmetycznych, takich jak liczby całkowite.

Jak zdefiniujemy funkcję f, która pobiera sekwencję zer i jedynek o długości 32 i zwraca sekwencję zer i jedynek o tej samej długości, z właściwością, że pobranie f dwa razy jest takie samo jak przerzucenie pierwszego bitu?

Uwaga: Jeśli możesz odpowiedzieć na powyższe pytanie w przypadku 32 bitów, możesz także odpowiedzieć w przypadku 64 bitów, 100 bitów itp. Po prostu zastosuj f do pierwszego 32 bitów.

Teraz, jeśli możesz odpowiedzieć na pytanie w przypadku 2 bitów, Voila!

I tak, okazuje się, że wystarczy zmienić pierwsze 2 bity.

Oto pseudo-kod

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.

Uwaga: Krok 2 i krok 3 razem można zapisać na lato jako (a, b) -> (-b, a). Wygląda znajomo? To powinno przypominać o obróceniu płaszczyzny o 90 stopni i pomnożeniu przez pierwiastek kwadratowy z -1.

Gdybym sam przedstawił pseudo-kod bez długiego preludium, wyglądałoby to jak królik z kapelusza, chciałem wyjaśnić, skąd mam rozwiązanie.


6
Tak, to ciekawy problem. Znasz swoją matematykę. Ale to problem informatyczny. Musisz więc uczyć się komputerów. Reprezentacja wielkości znaku jest dopuszczalna, ale wyszła z mody około 60 lat temu. Uzupełnienie 2 jest najpopularniejsze.
programista Windows

5
Oto, co twoja funkcja robi z dwoma bitami, gdy zastosowana dwukrotnie: (a, b) -> (-b, a) -> (-a, -b). Ale staramy się dostać do (-a, b), a nie (-a, -b).
buti-oxa

@ Buti-oxa, masz rację. Operacja dwóch bitów powinna wyglądać następująco: 00 -> 01 -> 10 -> 11 -> 00. Ale wtedy mój algorytm zakłada reprezentację wielkości znaku, która jest obecnie niepopularna, jak powiedział programista Windows, więc myślę, że mój algorytm jest mało przydatny .
Yoo

Czy on nie może po prostu wykonać tych kroków dwa razy zamiast raz?
Nosredna

4
buti-oxa ma całkowitą rację: funkcja nawet nie odwraca pierwszego bitu po dwóch wywołaniach, odwraca pierwsze dwa bity. Przerzucanie wszystkich bitów jest bliższe temu, co robi dopełnienie 2, ale nie jest dokładnie w porządku.
redtuna
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.