Progettazione della funzione f (f (n)) == -n


841

Una domanda che ho ricevuto durante la mia ultima intervista:

Progettare una funzione in modo ftale che:

f(f(n)) == -n

Dove nè un numero intero con segno a 32 bit ; non puoi usare l'aritmetica dei numeri complessi.

Se non è possibile progettare una tale funzione per l'intera gamma di numeri, progettarla per la gamma più ampia possibile.

Qualche idea?


2
A che lavoro è stata questa intervista?
Tymtam,

Risposte:


377

Che ne dite di:

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

In 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 promuove automaticamente numeri interi in lunghezze arbitrarie. In altre lingue il numero intero positivo più grande traboccerà, quindi funzionerà per tutti i numeri interi tranne quello.


Per farlo funzionare con numeri reali è necessario sostituire n in (-1) n con { ceiling(n) if n>0; floor(n) if n<0 }.

In C # (funziona per qualsiasi doppio, tranne in situazioni di overflow):

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
Rotto per -1, perché -1 * 0 è ancora 0
Joel Coehoorn,

3
No non lo è. f (-1) = 0. f (0) = 1
1800 INFORMAZIONI

5
È rotto per 1 comunque. f (1) = 0. f (0) = 1
1800 INFORMAZIONI

18
Hmm, salvando lo stato con numeri pari e dispari, avrei dovuto pensarci.
Sconosciuto l'

38
Penso che la cosa più importante non sia la funzione effettiva (ci sono infinite soluzioni), ma il processo attraverso il quale è possibile costruire tale funzione.
pyon,

440

Non hai detto che tipo di linguaggio si aspettavano ... Ecco una soluzione statica (Haskell). Fondamentalmente si scherza con i 2 bit più significativi:

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

È molto più semplice in un linguaggio dinamico (Python). Basta controllare se l'argomento è un numero X e restituire un lambda che restituisce -X:

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

23
Bene, adoro questo ... lo stesso approccio in JavaScript: var f = function (n) {return (typeof n == 'function')? n (): function () {return -n; }}
Mark Renouf,

Probabilmente è solo che il mio Haskell è molto arrugginito, ma lo hai verificato per (f 0)? Sembra che dovrebbe produrre lo stesso risultato di (f 0x80000000), almeno se abbiamo a che fare con ints a 32 bit con aritmetica avvolgente (sull'operazione di negazione). E sarebbe male.
Darius Bacon,

11
Sarebbe l'intervistatore media nemmeno cosa un costrutto lambda è ?
Jeremy Powell,

4
Ovviamente, un simile trucco di tipo truffatore funziona anche in Haskell, anche se è statico class C a b | a->b where { f :: a->b }:; instance C Int (()->Int) where { f=const.negate }; instance C (()->Int) Int where { f=($()) }.
circa il

4
Che cosa? Da dove hai avuto l'idea che typeof f (n) === 'function', in particolare, dove n è un numero e ti aspetti che venga restituito un numero? Non capisco come si possa applicare qui un caso di istanza. Non parlo bene Python, ma in JS il controllo dell'argomento per un tipo di funzione è chiaramente sbagliato in questo caso. Qui si applica solo la soluzione numerica. f è una funzione, f (n) è numero.
Harry,

284

Ecco una prova del perché una tale funzione non può esistere, per tutti i numeri, se non utilizza informazioni aggiuntive (tranne 32 bit di int):

Dobbiamo avere f (0) = 0. (Prova: supponiamo f (0) = x. Quindi f (x) = f (f (0)) = -0 = 0. Ora, -x = f (f (x )) = f (0) = x, il che significa che x = 0.)

Inoltre, per qualsiasi xe y, supponiamo f(x) = y. Vogliamo f(y) = -xallora. E f(f(y)) = -y => f(-x) = -y. Riassumendo: if f(x) = y, then f(-x) = -y, e f(y) = -x, and f(-y) = x.

Quindi, abbiamo bisogno di dividere tutti i numeri interi tranne 0 in set di 4, ma abbiamo un numero dispari di tali numeri interi; non solo, se rimuoviamo il numero intero che non ha una controparte positiva, abbiamo ancora 2 (mod4) numeri.

Se rimuoviamo i 2 numeri massimi rimasti (in base al valore abs), possiamo ottenere la funzione:

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

Naturalmente un'altra opzione, è di non rispettare 0, e ottenere i 2 numeri che abbiamo rimosso come bonus. (Ma è solo uno sciocco se.)


29
Non riesco a credere di aver letto fino in fondo per trovare una buona soluzione procedurale che gestisca i numeri negativi senza ricorrere a variabili globali o trucchi che offuscano il codice. Se potessi votarti più di una volta, lo farei.
Kyle Simek,

Bella osservazione, che esiste un numero dispari di numeri interi diversi da zero in ogni n bit con segno .
Andres Jaan Tack,

Anche questa sarebbe la mia risposta, ma attenzione al caso limite n = -2147483648(valore minimo); abs(n)in tal caso non è possibile e il risultato sarà indefinito (o un'eccezione).
Kirk Broadhurst,

1
@ a1kmm: Siamo spiacenti, -2³² sopra avrebbe dovuto essere -2³¹. Comunque, il caso in cui f (0) ≠ 0 (e quindi f (0) = - 2³¹) è in realtà il caso più semplice, poiché abbiamo mostrato che questi due sono disconnessi dal resto. L'altro caso che dobbiamo considerare è che f (0) = 0, ma f (x) = - 2³¹ per alcuni x ≠ 0, x ≠ -2³¹. In tal caso, f (-2³¹) = f (f (x)) = - x (la nota -x non può essere -2³¹, perché non esiste tale x). Inoltre, f (-x) = y. Quindi f (y) = f (f (-x)) = x. Ancora una volta y non può essere -2³¹ (come f (y) = x, ma f (-2³¹) = - x, e x non è 0). Quindi, -2³¹ = f (x) = f (f (y)) = - y, che è impossibile. Quindi, in effetti, 0 e -2³¹ devono essere scollegati dal resto (non l'immagine di nient'altro).
ShreevatsaR,

1
@will Non ci sono zero con segno, se (come presumo) stiamo parlando di numeri interi a 32 bit con complemento a due.
Goffrie,

146

Grazie al sovraccarico in 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
Sfortunatamente, a causa della modifica del nome, le funzioni chiamate "f" in realtà hanno nomi più strani.
pyon,

1
Ho pensato a qualcosa del genere, ma pensando in C, questo è stato gettato via ... buon lavoro!
Liran Orevi,

@Rui Craverio: non funzionerebbe in .NET 3.5+ perché l'autore ha scelto di usare la parola chiave var come nome di variabile.
Kredns,

72
tecnicamente ... questo non è ciò che la domanda richiede. hai definito 2 funzioni f (), f (int) ef (float) e la domanda pone "Progetta una funzione f () ..."
elcuco

2
@elcuco Tecnicamente, certo, ma logicamente è una funzione con sovraccarichi multipli (puoi fare f (f (42)) con quello). Dal momento che la definizione non dice nulla sui parametri e sul valore restituito, difficilmente posso accettarlo come una definizione tecnica.
Marek Toman,

135

Oppure, potresti abusare del preprocessore:

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

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

Quindi saresti Konrad "Le Chiffre" Rudolph allora? Prendo il cappotto. Sì, conosco l'intera cosa "void main", ma aggiungendo un "return 0;" è solo uno sforzo extra ;-)
Skizz

25
@Skizz, restituire 0 da main non è richiesto in c ++ anche con il valore di ritorno int ... quindi, facendolo bene, in realtà digiti un carattere in meno!
Dan Olson,

10
Skizz abusa sempre del preprocessore: D
Arnis Lapsa,

23
Questa non è una funzione ... quindi questa non è una soluzione valida
smerlin

3
@smerlin: tecnicamente è una funzione inline che restituisce una funzione inline: i corpi di entrambi sono espansi in fase di compilazione, o piuttosto prima. Non posso essere molto più efficiente di così.
Jon Purdy,

103

Questo è vero per tutti i numeri negativi.

    f (n) = abs (n)

Poiché esiste un numero più negativo di quanti siano i numeri positivi per i numeri interi a due complementi, f(n) = abs(n)è valido per un caso in più rispetto alla f(n) = n > 0 ? -n : nsoluzione uguale a f(n) = -abs(n). Ti ho preso per uno ...: D

AGGIORNARE

No, non è valido per un altro caso in quanto ho appena riconosciuto dal commento di Litb ... traboccerà abs(Int.Min)...

Ho pensato di usare anche le informazioni sulla mod 2, ma ho concluso che non funziona ... troppo presto. Se fatto bene, funzionerà per tutti i numeri tranne Int.Minperché questo traboccerà.

AGGIORNARE

Ci ho giocato per un po ', cercando un bel trucco di manipolazione, ma non sono riuscito a trovare un buon one-liner, mentre la soluzione mod 2 si inserisce in uno.

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

In C #, questo diventa il seguente:

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

Per farlo funzionare per tutti i valori, è necessario sostituirlo Math.Abs()con (n > 0) ? +n : -ne includere il calcolo in un uncheckedblocco. Quindi vieni persino Int.Minmappato su se stesso come fa la negazione non selezionata.

AGGIORNARE

Ispirato da un'altra risposta, spiegherò come funziona la funzione e come costruirla.

Cominciamo dall'inizio. La funzione fviene ripetutamente applicata a un dato valore nproducendo una sequenza di valori.

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

La domanda richiede f(f(n)) = -n, cioè due successive applicazioni di fnegare l'argomento. Altre due applicazioni di f- quattro in totale - annullano nuovamente l'argomentazione n.

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

Ora c'è un ovvio ciclo di lunghezza quattro. Sostituendo x = f(n)e notando che l'equazione ottenuta f(f(f(n))) = f(f(x)) = -xvale, si ottiene quanto segue.

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

Quindi otteniamo un ciclo di lunghezza quattro con due numeri e i due numeri negati. Se immagini il ciclo come un rettangolo, i valori negati si trovano agli angoli opposti.

Una delle tante soluzioni per costruire un tale ciclo è la seguente a partire da n.

 n => annulla e sottrai uno
-n - 1 = - (n + 1) => aggiungi uno
-n => annulla e aggiungi uno
 n + 1 => sottrai uno
 n

Un esempio concreto è di tale ciclo +1 => -2 => -1 => +2 => +1. Abbiamo quasi finito. Notando che il ciclo costruito contiene un numero positivo dispari, il suo successore pari ed entrambi i numeri negano, possiamo facilmente suddividere gli interi in molti di questi cicli ( 2^32è un multiplo di quattro) e abbiamo trovato una funzione che soddisfa le condizioni.

Ma abbiamo un problema con zero. Il ciclo deve contenere 0 => x => 0perché zero è negato a se stesso. E poiché il ciclo afferma già 0 => xsegue 0 => x => 0 => x. Questo è solo un ciclo di lunghezza due e xsi trasforma in se stesso dopo due applicazioni, non in -x. Fortunatamente c'è un caso che risolve il problema. Se è Xuguale a zero otteniamo un ciclo di lunghezza uno contenente solo zero e abbiamo risolto il problema concludendo che zero è un punto fisso di f.

Fatto? Quasi. Abbiamo 2^32numeri, zero è un punto fisso che lascia 2^32 - 1numeri e dobbiamo suddividere quel numero in cicli di quattro numeri. Bad che 2^32 - 1non è un multiplo di quattro - rimarranno tre numeri non in nessun ciclo di lunghezza quattro.

Spiegherò la parte rimanente della soluzione usando l'insieme più piccolo di iteger con firma a 3 bit che vanno da -4a +3. Abbiamo finito con zero. Abbiamo un ciclo completo +1 => -2 => -1 => +2 => +1. Ora costruiamo il ciclo a partire da +3.

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

Il problema che si pone è che +4non è rappresentabile come numero intero a 3 bit. Otterremmo +4negando -3a +3- quello che è ancora un po '3 intero valido - ma poi l'aggiunta di uno a +3(binario 011) produce 100binario. Interpretato come intero senza segno, +4ma dobbiamo interpretarlo come intero con segno -4. Quindi in realtà -4per questo esempio o Int.MinValuenel caso generale è un secondo punto fisso di negazione aritmetica intera - 0 e Int.MinValuesono mappati su loro stessi. Quindi il ciclo è in realtà il seguente.

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

È un ciclo di lunghezza due ed +3entra inoltre nel ciclo tramite -4. Di conseguenza, -4viene correttamente mappato su se stesso dopo due applicazioni, +3viene correttamente mappato su -3due applicazioni, ma -3viene erroneamente mappato su se stesso dopo due applicazioni.

Quindi abbiamo costruito una funzione che funziona per tutti gli interi tranne uno. Possiamo fare di meglio? No, non possiamo. Perché? Dobbiamo costruire cicli di lunghezza quattro e siamo in grado di coprire l'intero intervallo intero fino a quattro valori. I valori rimanenti sono i due punti fissi 0e Int.MinValueche devono essere mappati su se stessi e due numeri interi arbitrari xe -xche devono essere mappati tra loro da due applicazioni di funzione.

Per mappare xa -xe viceversa devono formare un ciclo a quattro e devono essere posizionati in angoli opposti di tale ciclo. Di conseguenza 0e Int.MinValueanche agli angoli opposti. Ciò mappare correttamente xe -xma scambiare i due punti fissi 0e Int.MinValuedopo due applicazioni funzionali e lasciarli con due ingressi fallimento. Quindi non è possibile costruire una funzione che funzioni per tutti i valori, ma ne abbiamo una che funziona per tutti i valori tranne uno e questo è il meglio che possiamo ottenere.


Non soddisfa i criteri: abs (abs (n))! = -N
Dan Olson

Certo che lo è, per tutti i numeri negativi, come ha detto. Questo era parte della domanda: se non riesci a trovarne uno generale, inventane uno che funzioni per la gamma più ampia possibile.
jalf

Questa risposta è valida almeno quanto quella di Marj Synowiec e Rowland Shaw, funziona solo per un diverso intervallo di numeri
1800 INFORMAZIONI

19
Amico, puoi anche sbarazzarti dell '"AGGIORNAMENTO" e scrivere una risposta corretta e coerente. Il 3/4 in basso ("ispirato da un'altra risposta") è fantastico.
Andres Jaan Tack,

1
Mi piace molto la soluzione abs per i numeri negativi. Semplice e facilmente comprensibile
Thorbjørn Ravn Andersen,

97

Utilizzando numeri complessi, è possibile dividere in modo efficace l'attività di negare un numero in due passaggi:

  • moltiplica n per i e otterrai n * i, che viene ruotato di 90 ° in senso antiorario
  • moltiplicare di nuovo per i e si ottiene -n

Il bello è che non hai bisogno di alcun codice di gestione speciale. Basta moltiplicare per il lavoro.

Ma non ti è permesso usare numeri complessi. Quindi devi in ​​qualche modo creare il tuo asse immaginario, usando parte del tuo intervallo di dati. Dato che hai bisogno esattamente della stessa quantità di valori immaginari (intermedi) dei valori iniziali, ti rimane solo metà dell'intervallo di dati.

Ho provato a visualizzarlo nella figura seguente, assumendo dati con segno a 8 bit. Dovresti ridimensionarlo per numeri interi a 32 bit. L'intervallo consentito per l'iniziale n è compreso tra -64 e +63. Ecco cosa fa la funzione per n positivo:

  • Se n è in 0..63 (intervallo iniziale), la chiamata di funzione aggiunge 64, mappando n nell'intervallo 64..127 (intervallo intermedio)
  • Se n è in 64..127 (intervallo intermedio), la funzione sottrae n da 64, mappando n nell'intervallo 0 ..- 63

Per n negativo, la funzione utilizza l'intervallo intermedio -65 ..- 128.

testo alternativo


4
@geschema, quale strumento hai usato per creare quella bella grafica?
jwfearn,

10
Siamo spiacenti, la domanda dice esplicitamente nessun numero complesso.
Rui Craveiro,

6
@Liran: ho usato OmniGraffle (solo per Mac)
geschema,

39
+1 Penso che questa sia la risposta migliore. Non credo che le persone leggano abbastanza, perché tutti hanno notato che la domanda diceva che non si potevano usare numeri complessi. Ho letto tutto e tu hai descritto la soluzione in numeri complessi per preparare il terreno per la soluzione non complessa alla domanda posta. Molto ben fatto.
jrista,

1
@jrista tutte le soluzioni usano una seconda dimensione, che è tutto ciò che realmente sono i "numeri complessi" (la maggior parte usa dispari vs pari e sopra usa floatvs int). L '"anello a 4 elementi" che molte risposte descrivono richiede 4 stati, che possono essere rappresentati come 2 dimensioni ciascuna con 2 stati. Il problema con questa risposta è che richiede ulteriore spazio di elaborazione (solo "funziona" per -64..63, ma necessita di -128..127 spazio) e non indica esplicitamente la formula scritta!
Kirk Broadhurst,

65

Funziona ad eccezione di int.MaxValue e 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));
    }

pittorico


Non sono sicuro del motivo per cui questo è stato sottoposto a downgrade. Per quali input fallisce?
Rodrick Chapman,

Perché non usi la funzione signum?!?
comonad

1
L'immagine è davvero buona. Invia 0per 0e -2147483648per -2147483648i punti dato che questi due numeri sono fissati per l'operatore di negazione, x => -x. Per il resto dei numeri, seguire le frecce nell'immagine sopra. Come si evince dalla risposta di SurDin e dai suoi commenti, ci saranno due numeri, in questo caso 2147483647e -2147483647senza altra coppia con cui scambiare.
Jeppe Stig Nielsen,

Sembra uno smiley - con molte rughe
Anshul

48

La domanda non dice nulla su ciò che fdevono essere il tipo di input e il valore restituito della funzione (almeno non nel modo in cui l'hai presentata) ...

... solo che quando n è un numero intero a 32 bit allora f(f(n)) = -n

Quindi, che ne dici di qualcosa del genere

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

Se n è un numero intero a 32 bit, l'istruzione f(f(n)) == -nsarà vera.

Ovviamente, questo approccio potrebbe essere esteso per funzionare per una gamma ancora più ampia di numeri ...


2
Subdolo. Limite di caratteri.
Joe Phillips,

2
Sì, stavo lavorando su un approccio simile. Mi hai battuto comunque. +1 :)
jalf

1
Molto intelligente! Questo è molto vicino (ed effettivamente uguale a) l'uso di numeri complessi, che sarebbe la soluzione ovvia e ideale, ma esplicitamente vietata. Lavorare al di fuori dell'intervallo di numeri consentiti.
Kirk Broadhurst,

48

per javascript (o altre lingue tipizzate dinamicamente) puoi avere la funzione di accettare un int o un oggetto e restituire l'altro. vale a dire

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

dando

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

in alternativa, è possibile utilizzare il sovraccarico in un linguaggio fortemente tipizzato, sebbene ciò possa infrangere le regole

int f(long n) {
    return n;
}

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

Quest'ultimo non significa il requisito di una "a" (singolare) funzione. :)
Estratto l'

Rimuovi la seconda metà della risposta e questa è una risposta corretta.
jmucchiello,

@Drew ed è per questo che ho detto che potrebbe infrangere le regole
cobbal

2
In JavaScript, una funzione è un oggetto e quindi può mantenere uno stato.
Nosredna,

1
IMO: funzione f (n) {return n.passed? -n.val: {val: n, passato: 1}} è più leggibile e più breve.
SamGoody,

46

A seconda della piattaforma, alcune lingue consentono di mantenere lo stato nella funzione. VB.Net, ad esempio:

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

    Return n * flag
End Function

Anche IIRC, C ++ lo ha permesso. Sospetto però che stiano cercando una soluzione diversa.

Un'altra idea è che dal momento che non hanno definito il risultato della prima chiamata alla funzione, è possibile utilizzare dispari / uniformità per controllare se invertire il segno:

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

Aggiungine uno alla grandezza di tutti i numeri pari, sottrai uno dalla grandezza di tutti i numeri dispari. Il risultato di due chiamate ha la stessa grandezza, ma l'unica chiamata in cui è anche scambiamo il segno. Ci sono alcuni casi in cui questo non funziona (-1, max o min int), ma funziona molto meglio di qualsiasi altra cosa suggerita finora.


1
Credo che funzioni per MAX_INT poiché è sempre strano. Non funziona per MIN_INT e -1.
Airsource Ltd,

9
Non è una funzione se ha effetti collaterali.
nn

12
Questo può essere vero in matematica, ma è irrilevante nella programmazione. Quindi la domanda è se stanno cercando una soluzione matematica o una soluzione di programmazione. Ma dato che è per un lavoro di programmazione ...
Ryan Lundy

+1 Avevo intenzione di postarne uno con in C con "static int x" implementando un FIFO con negazione dell'output. Ma questo è abbastanza vicino.
phkahler,

2
@nos: Sì, non è solo referenzialmente trasparente.
Clark Gaebel

26

Sfruttare le eccezioni JavaScript.

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

f(f(0)) => 0

f(f(1)) => -1


Dubito che le eccezioni siano state usate in questo modo prima ... :)
NoBugs

+1 Pensiero fuori dagli schemi. Freddo! Ma nel codice di produzione userei typeof solo per sicurezza.

21

Per tutti i valori a 32 bit (con l'avvertenza che -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);
}

Fondamentalmente è necessario accoppiare ogni ciclo -x => x => -x con il ciclo ay => -y => y. Quindi ho accoppiato i lati opposti di split.

ad es. per numeri interi a 4 bit:

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

21

Una versione C ++, che probabilmente piega leggermente le regole ma funziona per tutti i tipi numerici (float, ints, doppi) e persino per i tipi di classe che sovraccaricano il meno unario:

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

Buona idea. In alternativa, potresti probabilmente perdere la struttura e invece una funzione restituisce un puntatore, l'altra funzione dereference e negate.
Imbue,

20

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

Codice verificato, tutti i possibili numeri interi a 32 bit passati, errore con -2147483647 (underflow).


19

Usa i globi ... ma così?

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

3
Non sono sicuro che questa fosse l'intenzione del richiedente, ma +1 per "pensare fuori dagli schemi".
Liran Orevi,

5
Invece di dire condizionalmente "fatto = vero", dovresti sempre dire "fatto =! Fatto", in questo modo la tua funzione può essere usata più di una volta.
Chris Lutz,

@Chris, poiché l'impostazione di true su è all'interno di un blocco if (! Done), equivale a done =! Done, ma! Done non ha bisogno di essere calcolato (o ottimizzato dal compilatore, se è abbastanza intelligente) .
sabato

1
Il mio primo pensiero è stato anche quello di risolvere questo problema usando una variabile globale, anche se sembrava un po 'barare per questa domanda particolare. Direi comunque che una soluzione variabile globale è la migliore soluzione date le specifiche nella domanda. L'uso di un globale semplifica la comprensione di ciò che sta accadendo. Concordo sul fatto che un fatto! = Fatto sarebbe meglio però. Spostalo all'esterno della clausola if.
Alderath,

3
Tecnicamente, tutto ciò che mantiene lo stato non è una funzione, ma una macchina a stati. Per definizione , una funzione fornisce sempre lo stesso output per lo stesso input.
Ted Hopp,

19

Questa soluzione Perl funziona per numeri interi, float e stringhe .

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

Prova alcuni dati di prova.

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

Produzione:

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

Ma non lo tiene int. Stai essenzialmente archiviando i dati delle variabili globali nell'int "n" stesso ... tranne che non è un int altrimenti non potresti farlo. Ad esempio, se nfosse una stringa che potrei fare 548 diventa "First_Time_548" e la prossima volta che esegue la funzione ... se (prefisso == First_Time_ ") sostituisci" First_Time_ "con" - "
Albert Renshaw,

@AlbertRenshaw Non sono sicuro da dove prendi quelle idee. (1) Non ci sono sicuramente variabili globali coinvolte qui. (2) Se dai alla funzione un int, otterrai un int indietro - o un riferimento a un int, se chiami la funzione un numero dispari di volte. (3) Forse fondamentalmente, questo è Perl . Per tutti gli scopi pratici, ints e stringhe sono completamente intercambiabili. Le stringhe che assomigliano a numeri funzioneranno perfettamente come numeri nella maggior parte dei contesti, e i numeri si adatteranno felicemente quando richiesto.
FMc,

Scusa, non so molto perl, sembra che tu stia usando un array globale haha
Albert Renshaw,

18

Nessuno ha mai detto che f (x) doveva essere dello stesso tipo.

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


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

16

In realtà non sto cercando di dare una soluzione al problema stesso, ma ho un paio di commenti, poiché la domanda afferma che questo problema è stato posto faceva parte di un'intervista (di lavoro?):

  • Vorrei prima chiedere "Perché dovrebbe essere necessaria una tale funzione? Qual è il problema più grande di cui fa parte?" invece di cercare di risolvere il problema reale posto sul posto. Questo mostra come penso e come affrontare problemi come questo. Chi lo sa? Questo potrebbe anche essere il vero motivo per cui la domanda viene posta in un'intervista in primo luogo. Se la risposta è "Non importa, supponi che sia necessario e mostrami come progetterai questa funzione." Vorrei quindi continuare a farlo.
  • Quindi, scriverei il codice del test case C # che userei (l'ovvio: loop da int.MinValuea int.MaxValue, e per ciascuno nin quell'intervallo chiama f(f(n))e controlla che il risultato sia-n ), dicendo che avrei quindi usato Test Driven Development per arrivare a tale funzione.
  • Solo se l'intervistatore continua a chiedermi di risolvere il problema posto, in realtà inizierei a cercare di scribacchiare lo pseudocodice durante l'intervista stessa per cercare di ottenere una sorta di risposta. Tuttavia, non penso davvero che salterei per accettare il lavoro se l'intervistatore fosse un indizio di come fosse la società ...

Oh, questa risposta presuppone che l'intervista fosse per una posizione relativa alla programmazione in C #. Naturalmente sarebbe una risposta sciocca se l'intervista riguardasse una posizione matematica. ;-)


7
Sei fortunato hanno chiesto 32 int, se era 64 bit l'intervista non continuerà mai dopo aver eseguito i test ;-)
alex2k8

Anzi, se arrivassi al punto di scrivere effettivamente quel test ed eseguirlo durante un'intervista. ;-) Il mio punto: proverei a non arrivare affatto a quel punto in un'intervista. La programmazione è più un "modo di pensare" che "come scrive righe di codice" secondo me.
peSHIr

7
Non seguire questo consiglio in una vera intervista. L'intervistatore si aspetta che tu risponda effettivamente alla domanda. Mettere in discussione la pertinenza della domanda non ti comprerà nulla ma potrebbe infastidire l'intervistatore. La progettazione di un test banale non ti avvicina alla risposta e non puoi eseguirla nell'intervista. Se ottieni informazioni extra (32 bit), prova a capire come ciò potrebbe essere utile.
Stefan Haustein,

Un intervistatore che si infastidisce quando chiedo ulteriori informazioni (pur mettendo forse in dubbio la pertinenza della sua domanda nel processo) non è un intervistatore per il quale desidero necessariamente lavorare. Quindi continuerò a fare domande del genere nelle interviste. Se non gli piace, probabilmente finirò l'intervista per smettere di perdere entrambi i nostri tempi. Non mi piace l'impostazione mentale "Stavo solo seguendo gli ordini". Fai..?
peSHIr

16

Vorresti cambiare i 2 bit più significativi.

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

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

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

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

Come puoi vedere, è solo un'aggiunta, tralasciando il bit trasportato.

Come sono arrivato alla risposta? Il mio primo pensiero era solo un bisogno di simmetria. 4 giri per tornare da dove ho iniziato. All'inizio ho pensato, questo è il codice Gray a 2 bit. Poi ho pensato che in realtà il binario standard fosse abbastanza.


Il problema con questo approccio è che non funziona con i numeri negativi del complimento di due (che è ciò che ogni CPU moderna utilizza). Ecco perché ho eliminato la mia risposta identica.
Tamas Czinege,

La domanda specificava numeri interi con segno a 32 bit. Questa soluzione non funziona per le rappresentazioni del complemento a due o del complemento degli interi con segno a 32 bit. Funzionerà solo per le rappresentazioni di segni e grandezza, che sono molto rare nei computer moderni (diversi dai numeri in virgola mobile).
Jeffrey L Whitledge

1
@DrJokepu - Caspita, dopo sei mesi - jinx!
Jeffrey L Whitledge,

Non hai solo bisogno di convertire i numeri in una rappresentazione di segno e grandezza all'interno della funzione, eseguire la trasformazione, quindi riconvertire in qualunque rappresentazione nativa intera prima di restituirla?
Bill Michell,

Mi piace che tu abbia praticamente implementato numeri complessi introducendo un bit immaginario :)
jabirali,

16

Ecco una soluzione che si ispira al requisito o afferma che non è possibile utilizzare numeri complessi per risolvere questo problema.

Moltiplicare per la radice quadrata di -1 è un'idea, che sembra fallire solo perché -1 non ha una radice quadrata sugli interi. Ma giocare con un programma come matematica dà ad esempio l'equazione

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

e questo è quasi buono come avere una radice quadrata di -1. Il risultato della funzione deve essere un numero intero con segno. Quindi userò una mod di operazione modulo modificata (x, n) che restituisce l'intero y congruente a x modulo n che è il più vicino a 0. Solo pochissimi linguaggi di programmazione hanno una operazione modulo, ma può essere facilmente definita . Ad esempio in Python è:

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

Utilizzando l'equazione sopra, il problema può ora essere risolto come

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

Questo soddisfa f(f(x)) = -xper tutti gli interi nell'intervallo . Anche i risultati di sono in questo intervallo, ma ovviamente il calcolo avrebbe bisogno di numeri interi a 64 bit.[-231-2, 231-2]f(x)


13

C # per un intervallo di 2 ^ 32 - 1 numeri, tutti i numeri int32 tranne (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

stampe:

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

Anche questo non funziona per f (0) che è 1073741824. f (1073741824) = 0. f (f (1073741824)) = 1073741824
Dinah,

In genere può dimostrare che per un complemento a due tipo integer di qualsiasi dimensione bit, la funzione deve non funzionare per almeno due valori di ingresso.
fannullone

12

Essenzialmente la funzione deve dividere l'intervallo disponibile in cicli di dimensione 4, con -n all'estremità opposta del ciclo di n. Tuttavia, 0 deve far parte di un ciclo di dimensione 1, perché altrimenti0->x->0->x != -x . Dato che 0 è solo, ci devono essere altri 3 valori nel nostro intervallo (la cui dimensione è un multiplo di 4) non in un ciclo corretto con 4 elementi.

Ho scelto questi valori in più strani di essere MIN_INT, MAX_INTe MIN_INT+1. Inoltre, MIN_INT+1mapperà MAX_INTcorrettamente, ma rimarrà bloccato lì e non mappare indietro. Penso che questo sia il miglior compromesso, perché ha la bella proprietà che solo i valori estremi non funzionano correttamente. Inoltre, significa che funzionerebbe per tutti i 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

Nessuno ha detto che doveva essere apolide.

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

Barare, ma non tanto quanto molti esempi. Ancora più malvagio sarebbe sbirciare lo stack per vedere se l'indirizzo del chiamante è & f, ma questo sarà più portatile (anche se non thread-safe ... la versione thread-safe userebbe TLS). Ancora più cattivo:

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

Naturalmente, nessuno di questi funziona troppo bene nel caso di MIN_INT32, ma c'è poco prezioso che puoi fare a meno che non ti sia permesso di restituire un tipo più ampio.


puoi "aggiornarlo" per chiedere l'indirizzo (sì, devi ottenerlo tramite ref \ come puntatore) - in C, ad esempio: int f (int & n) {static int * addr = & n; if (addr == & n) {return -n; } return n; }
IUnnownPointer

11

Potrei immaginare di usare il 31 bit come bit immaginario ( i ) sarebbe un approccio che supporterebbe metà dell'intervallo totale.


Questo sarebbe più complesso ma non più efficace dell'attuale migliore risposta
1800 INFORMAZIONI

1
@ 1800 INFORMAZIONI: D'altra parte, il dominio [-2 ^ 30 + 1, 2 ^ 30-1] è contiguo, il che è più attraente da un punto di vista matematico.
Jochen Walter,

10

funziona per 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

Il problema indica "numeri interi con segno a 32 bit" ma non specifica se sono complementi a due o a complementi .

Se si utilizza il complemento a uno, tutti i valori 2 ^ 32 si verificano in cicli di lunghezza quattro: non è necessario un caso speciale per zero e non sono necessari anche i condizionali.

In C:

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

Questo funziona da

  1. Scambio di blocchi alti e bassi a 16 bit
  2. Invertire uno dei blocchi

Dopo due passaggi abbiamo l'inverso bit a bit del valore originale. Che nella rappresentazione ad un complemento equivale alla negazione.

Esempi:

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
Che dire dell'ordine dei byte tra architetture diverse?
Steven,

1
Tutta l'aritmetica è a 32 bit. Non manipolo singoli byte, quindi l'ordine dei byte non lo influenzerà.
finnw,

Sembra abbastanza vicino. Puoi supporre che l'ingresso sia un complemento a 2. Quindi si converte nella rappresentazione bit di segno. Ora, a seconda dell'ultimo bit, capovolgi il primo e l'ultimo bit o solo l'ultimo. Fondamentalmente si annulla solo i numeri pari e si cicla sempre / dispari tutto il tempo. Quindi torni da dispari a dispari e anche fino a dopo 2 chiamate. Alla fine ti converti in 2-complemento. Ho pubblicato il codice per questo da qualche parte qui sotto.
Stefan Haustein,

9

: D

boolean inner = true;

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

5
Potrebbe anche farti discutere sul perché le variabili globali sono cattive se non ti cacciano dall'intervista proprio lì!
palswim,


7

Vorrei condividere il mio punto di vista su questo interessante problema come matematico. Penso di avere la soluzione più efficiente.

Se ricordo bene, si annulla un numero intero a 32 bit con segno semplicemente lanciando il primo bit. Ad esempio, se n = 1001 1101 1110 1011 1110 0000 1110 1010, quindi -n = 0001 1101 1110 1011 1110 0000 1110 1010.

Quindi, come possiamo definire una funzione f che accetta un numero intero a 32 bit con segno e restituisce un altro numero intero a 32 bit con la proprietà che prendere f due volte è lo stesso che lanciare il primo bit?

Permettetemi di riformulare la domanda senza menzionare concetti aritmetici come numeri interi.

Come definiamo una funzione f che accetta una sequenza di zeri e quelli di lunghezza 32 e restituisce una sequenza di zeri e quelli della stessa lunghezza, con la proprietà che prendere f due volte equivale a lanciare il primo bit?

Osservazione: se è possibile rispondere alla domanda sopra per caso a 32 bit, allora si può anche rispondere per caso a 64 bit, caso a 100 bit, ecc. È sufficiente applicare f al primo 32 bit.

Ora, se puoi rispondere alla domanda per il caso a 2 bit, Voila!

E sì, risulta che cambiare i primi 2 bit è sufficiente.

Ecco lo pseudo-codice

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.

Nota: il passaggio 2 e il passaggio 3 insieme possono essere estesi come (a, b) -> (-b, a). Sembra familiare? Ciò dovrebbe ricordare la rotazione di 90 gradi del piano e la moltiplicazione per la radice quadrata di -1.

Se avessi appena presentato lo pseudo-codice da solo senza il lungo preludio, sembrerebbe un coniglio fuori dal cappello, vorrei spiegare come ho ottenuto la soluzione.


6
Sì, è un problema interessante. Conosci la tua matematica. Ma questo è un problema di informatica. Quindi devi studiare i computer. La rappresentazione della grandezza dei segni è consentita ma è passata di moda circa 60 anni fa. Il complemento a 2 è il più popolare.
Programmatore di Windows,

5
Ecco cosa fa la tua funzione sui due bit quando viene applicata due volte: (a, b) -> (-b, a) -> (-a, -b). Ma stiamo provando a raggiungere (-a, b), non (-a, -b).
buti-oxa,

@ buti-oxa, hai ragione. L'operazione a due bit dovrebbe essere simile a: 00 -> 01 -> 10 -> 11 -> 00. Ma poi il mio algoritmo assume una rappresentazione della grandezza del segno che è impopolare ora, come ha detto il programmatore di Windows, quindi penso che il mio algoritmo sia di scarsa utilità .
Yoo

Quindi non può semplicemente fare i passaggi due volte anziché una volta?
Nosredna,

4
buti-oxa ha perfettamente ragione: la funzione non capovolge nemmeno il primo bit dopo due invocazioni, capovolge i primi due bit. Lanciare tutti i bit è più vicino a quello che fa il complemento di 2, ma non è esattamente giusto.
Redtuna,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.