Diseño de la función f (f (n)) == -n

841

Una pregunta que recibí en mi última entrevista:

Diseñe una función f, de modo que:

f(f(n)) == -n

Donde nes un entero con signo de 32 bits ; no puedes usar aritmética de números complejos.

Si no puede diseñar dicha función para todo el rango de números, diséñela para el rango más grande posible.

¿Algunas ideas?

Gumbo
fuente
2
¿Para qué trabajo fue esta entrevista?
tymtam

Respuestas:

377

Qué tal si:

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

En 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 promueve automáticamente enteros a longitudes arbitrarias de longitud. En otros idiomas, el número entero positivo más grande se desbordará, por lo que funcionará para todos los números enteros excepto ese.


Para que funcione para números reales, debe reemplazar la n en (-1) n con { ceiling(n) if n>0; floor(n) if n<0 }.

En C # (funciona para cualquier doble, excepto en situaciones de desbordamiento):

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));
}
RossFabricant
fuente
10
Roto por -1, porque -1 * 0 sigue siendo 0
Joel Coehoorn
3
No, no lo es. f (-1) = 0. f (0) = 1
1800 INFORMACIÓN
55
Sin embargo, está roto por 1. f (1) = 0. f (0) = 1
1800 INFORMACIÓN
18
Hmm, guardando estado con números pares e impares, debería haber pensado en eso.
Desconocido
38
Creo que lo más importante no es la función real (hay infinitas soluciones), sino el proceso mediante el cual se puede construir dicha función.
pyon
440

No dijiste qué tipo de lenguaje esperaban ... Aquí hay una solución estática (Haskell). Básicamente está jugando con los 2 bits más significativos:

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

Es mucho más fácil en un lenguaje dinámico (Python). Simplemente verifique si el argumento es un número X y devuelve una lambda que devuelve -X:

def f(x):
   if isinstance(x,int):
      return (lambda: -x)
   else:
      return x()
viraptor
fuente
23
Genial, me encanta esto ... ¿el mismo enfoque en JavaScript: var f = function (n) {return (typeof n == 'function')? n (): function () {return -n; }}
Mark Renouf
Probablemente sea solo que mi Haskell está muy oxidado, pero ¿lo has verificado para (f 0)? Parece que eso debería producir el mismo resultado que (f 0x80000000), al menos si se trata de entradas de 32 bits con aritmética envolvente (en la operación de negación). Y eso sería malo.
Darius Bacon
11
Sería el entrevistador promedio siquiera sabe lo que es un constructo lambda es ?
Jeremy Powell
44
Por supuesto, un truco de trampa de tipo también funciona en Haskell, aunque sea estático class C a b | a->b where { f :: a->b }:; instance C Int (()->Int) where { f=const.negate }; instance C (()->Int) Int where { f=($()) }.
Leftaroundabout
44
¿Qué? ¿De dónde sacaste la idea de que typeof f (n) === 'función', especialmente, donde n es un número y esperas que se devuelva un número? No entiendo cómo podría aplicarse un caso de instancia aquí. No hablo bien Python, pero en JS el argumento de verificación para un tipo de función es simplemente incorrecto en este caso. Aquí solo se aplica la solución numérica. f es una función, f (n) es número.
Harry
284

Aquí hay una prueba de por qué dicha función no puede existir, para todos los números, si no utiliza información adicional (excepto 32 bits de int):

Debemos tener f (0) = 0. (Prueba: supongamos que f (0) = x. Entonces f (x) = f (f (0)) = -0 = 0. Ahora, -x = f (f (x )) = f (0) = x, lo que significa que x = 0.)

Además, para cualquiera xy y, supongamos f(x) = y. Queremos f(y) = -xentonces. Y f(f(y)) = -y => f(-x) = -y. Para resumir: si f(x) = y, entonces f(-x) = -y, y f(y) = -x, y f(-y) = x.

Entonces, necesitamos dividir todos los enteros excepto 0 en conjuntos de 4, pero tenemos un número impar de tales enteros; no solo eso, si eliminamos el número entero que no tiene una contraparte positiva, todavía tenemos 2 números (mod4).

Si eliminamos los 2 números máximos restantes (por valor de abs), podemos obtener la función:

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

Por supuesto, otra opción es no cumplir con 0 y obtener los 2 números que eliminamos como bonificación. (Pero eso es una tontería si).

SurDin
fuente
29
No puedo creer que haya tenido que leer hasta aquí para encontrar una buena solución de procedimiento que maneje números negativos sin recurrir a variables globales o trucos que ofuscan el código. Si pudiera votarte más de una vez, lo haría.
Kyle Simek
Buena observación, que hay un número impar de enteros distintos de cero en cualquier n bits con signo.
Andres Jaan Tack
Esta sería mi respuesta también, pero tenga cuidado con el caso límite n = -2147483648(valor mínimo); no puede abs(n)en ese caso, y el resultado será indefinido (o una excepción).
Kirk Broadhurst
1
@ a1kmm: Lo siento, -2³² arriba debería haber sido -2³¹. De todos modos, el caso donde f (0) ≠ 0 (y por lo tanto f (0) = - 2³ actually) es en realidad el caso más fácil, ya que mostramos que estos dos están desconectados del resto. El otro caso que debemos considerar es que f (0) = 0, pero f (x) = - 2³¹ para algunos x ≠ 0, x ≠ -2³¹. En ese caso, f (-2³¹) = f (f (x)) = - x (nota -x no puede ser -2³¹, porque no existe tal x). Además, f (-x) = y. Entonces f (y) = f (f (-x)) = x. Nuevamente, y no puede ser -2³¹ (como f (y) = x, pero f (-2³¹) = - x, yx no es 0). Entonces, -2³¹ = f (x) = f (f (y)) = - y, lo cual es imposible. De hecho, 0 y -2³¹ deben desconectarse del resto (no la imagen de nada más).
ShreevatsaR
1
@will No hay ceros con signo, si (como supongo) estamos hablando de enteros de 32 bits con dos complementos.
goffrie
146

Gracias a la sobrecarga en 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));
}
Comptrol
fuente
44
Desafortunadamente, debido al cambio de nombre, las funciones que llama "f" en realidad tienen nombres más extraños.
pyon
1
He pensado en algo así, pero pensando en C, esto fue descartado ... ¡buen trabajo!
Liran Orevi
@Rui Craverio: No funcionaría en .NET 3.5+ porque el autor eligió usar la palabra clave var como nombre de variable.
Kredns
72
técnicamente ... esto no es lo que exige la pregunta. definió 2 f (), las funciones f (int) y F (flotante) y las preguntas se refiere a "Diseño de una función f () ..."
elcuco
2
@elcuco Técnicamente, por supuesto, pero lógicamente es una función con múltiples sobrecargas (puede hacer f (f (42)) con eso). Como la definición no dice nada sobre los parámetros y el valor de retorno, apenas puedo aceptarlo como una definición técnica.
Marek Toman
135

O puede abusar del preprocesador:

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

int main()
{
  int n = -42;
  cout << "f(f(" << n << ")) = " << f(f(n)) << endl;
}
Skizz
fuente
¿Entonces serías Konrad "Le Chiffre" Rudolph? Traeré mi abrigo. Sí, sé sobre todo el asunto "vacío principal", pero agregando un "retorno 0"; es mucho esfuerzo extra ;-)
Skizz
25
@Skizz, el retorno 0 desde main no es necesario en c ++ incluso con el valor de retorno int ... ¡así que al hacerlo correctamente, realmente escribe un carácter menos!
Dan Olson
10
Skizz siempre abusa del preprocesador: D
Arnis Lapsa
23
Esto no es una función ... así que esta no es una solución válida
smerlin
3
@smerlin: Técnicamente es una función en línea que devuelve una función en línea: los cuerpos de ambos se expanden en el momento de la compilación, o más bien justo antes. No puede ser mucho más eficiente que esto.
Jon Purdy
103

Esto es cierto para todos los números negativos.

    f (n) = abs (n)

Debido a que hay un número negativo más que números positivos para dos enteros complementarios, f(n) = abs(n)es válido para un caso más que una f(n) = n > 0 ? -n : nsolución que sea la misma que f(n) = -abs(n). Te tengo por uno ...: D

ACTUALIZAR

No, no es válido para un caso más, como acabo de reconocer por el comentario de litb ... abs(Int.Min)simplemente se desbordará ...

También pensé en usar la información de mod 2, pero concluí que no funciona ... demasiado pronto. Si se hace correctamente, funcionará para todos los números, excepto Int.Minporque se desbordará.

ACTUALIZAR

Jugué con él por un tiempo, buscando un buen truco de manipulación, pero no pude encontrar una buena línea, mientras que la solución mod 2 encaja en una.

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

En C #, esto se convierte en lo siguiente:

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

Para conseguir que funcione para todos los valores, tiene que sustituir Math.Abs()con (n > 0) ? +n : -ne incluir el cálculo en un uncheckedbloque. Luego, incluso te Int.Minasignas a ti mismo como lo hace la negación sin control.

ACTUALIZAR

Inspirado por otra respuesta, voy a explicar cómo funciona la función y cómo construirla.

Comencemos desde el principio. La función fse aplica repetidamente a un valor dado que nproduce una secuencia de valores.

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

La pregunta exige f(f(n)) = -n, es decir, dos aplicaciones sucesivas de fnegar el argumento. Dos aplicaciones adicionales de f- cuatro en total - niegan el argumento nuevamente dando como resultado nnuevamente.

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

Ahora hay un ciclo obvio de longitud cuatro. Sustituyendo x = f(n)y observando que la ecuación obtenida se f(f(f(n))) = f(f(x)) = -xcumple, se obtiene lo siguiente.

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

Entonces obtenemos un ciclo de longitud cuatro con dos números y los dos números negados. Si imagina el ciclo como un rectángulo, los valores negados se encuentran en las esquinas opuestas.

Una de las muchas soluciones para construir dicho ciclo es la siguiente a partir de n.

 n => negar y restar uno
-n - 1 = - (n + 1) => agregar uno
-n => negar y agregar uno
 n + 1 => restar uno
 norte

Un ejemplo concreto es de tal ciclo es +1 => -2 => -1 => +2 => +1. Casi terminamos. Al observar que el ciclo construido contiene un número positivo impar, su sucesor par, y ambos números se niegan, podemos dividir fácilmente los enteros en muchos de estos ciclos ( 2^32es un múltiplo de cuatro) y hemos encontrado una función que satisface las condiciones.

Pero tenemos un problema con cero. El ciclo debe contener 0 => x => 0porque el cero se niega a sí mismo. Y porque el ciclo ya dice 0 => xque sigue 0 => x => 0 => x. Este es solo un ciclo de longitud dos y xse convierte en sí mismo después de dos aplicaciones, no en -x. Afortunadamente, hay un caso que resuelve el problema. Si Xes igual a cero, obtenemos un ciclo de longitud uno que contiene solo cero y resolvemos ese problema concluyendo que cero es un punto fijo de f.

¿Hecho? Casi. Tenemos 2^32números, el cero es un punto fijo que deja 2^32 - 1números, y debemos dividir ese número en ciclos de cuatro números. Malo que 2^32 - 1no es un múltiplo de cuatro: quedarán tres números que no estarán en ningún ciclo de longitud cuatro.

Explicaré la parte restante de la solución usando el conjunto más pequeño de iteradores con signo de 3 bits que van desde -4hasta +3. Hemos terminado con cero. Tenemos un ciclo completo +1 => -2 => -1 => +2 => +1. Ahora construyamos el ciclo comenzando en +3.

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

El problema que surge es que +4no es representable como un entero de 3 bits. Obtendríamos +4negando -3a +3- lo que todavía es un número entero de 3 bits válido - pero luego sumando uno al +3(binario 011) los rendimientos 100binario. Se interpreta como un entero sin signo, +4pero tenemos que interpretarlo como un entero con signo -4. Entonces, en realidad, -4para este ejemplo o Int.MinValueen el caso general, hay un segundo punto fijo de negación aritmética de enteros, 0 y Int.MinValuese asignan a sí mismos. Entonces el ciclo es en realidad como sigue.

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

Es un ciclo de longitud dos y además +3ingresa al ciclo a través de -4. En consecuencia, -4se asigna correctamente a sí mismo después de dos aplicaciones de función, +3se asigna correctamente -3después de dos aplicaciones de función, pero -3se asigna erróneamente a sí mismo después de dos aplicaciones de función.

Así que construimos una función que funciona para todos los enteros menos uno. ¿Podemos hacerlo mejor? No podemos. ¿Por qué? Tenemos que construir ciclos de longitud cuatro y podemos cubrir todo el rango entero hasta cuatro valores. Los valores restantes son los dos puntos fijos 0y Int.MinValueque deben asignarse a sí mismos y a dos enteros arbitrarios xy -xdeben correlacionarse entre sí mediante dos aplicaciones de función.

Para asignar xa -xy viceversa deben formar un ciclo de cuatro y deben estar ubicados en las esquinas opuestas de ese ciclo. En consecuencia 0y Int.MinValuetiene que estar en las esquinas opuestas, también. Esto asignará correctamente xy cambiará -xlos dos puntos fijos 0y Int.MinValuedespués de dos aplicaciones de función y nos dejará con dos entradas fallidas. Por lo tanto, no es posible construir una función que funcione para todos los valores, pero tenemos una que funciona para todos los valores excepto uno y esto es lo mejor que podemos lograr.

Daniel Brückner
fuente
No cumple con los criterios: abs (abs (n))! = -N
Dan Olson
Claro que sí, para todos los números negativos, como él dijo. Esa fue parte de la pregunta: si no puede encontrar una general, cree una que funcione para el rango más amplio posible.
jalf
Esta respuesta es al menos tan buena como la respuesta de Marj Synowiec y Rowland Shaw, solo funciona para un rango diferente de números
1800 INFORMACIÓN
19
Amigo, también puedes deshacerte de las "ACTUALIZACIONES" y escribir una respuesta correcta coherente. El 3/4 inferior ("inspirado en otra respuesta") es increíble.
Andres Jaan Tack
1
Realmente me gusta la solución abs para números negativos. Simple y fácil de entender.
Thorbjørn Ravn Andersen
97

Usando números complejos, puede dividir efectivamente la tarea de negar un número en dos pasos:

  • multiplica n por i, y obtienes n * i, que n gira 90 ° en sentido antihorario
  • multiplica de nuevo por i, y obtienes -n

Lo bueno es que no necesita ningún código de manejo especial. Simplemente multiplicando por i hace el trabajo.

Pero no puedes usar números complejos. Entonces, de alguna manera, debe crear su propio eje imaginario, utilizando parte de su rango de datos. Como necesita exactamente tantos valores imaginarios (intermedios) como valores iniciales, solo le queda la mitad del rango de datos.

Traté de visualizar esto en la siguiente figura, suponiendo datos firmados de 8 bits. Tendría que escalar esto para enteros de 32 bits. El rango permitido para n inicial es -64 a +63. Esto es lo que hace la función para n positivo:

  • Si n está en 0..63 (rango inicial), la llamada a la función agrega 64, asignando n al rango 64..127 (rango intermedio)
  • Si n está en 64..127 (rango intermedio), la función resta n de 64, asignando n al rango 0 ..- 63

Para n negativo, la función usa el rango intermedio -65 ..- 128.

texto alternativo

geschema
fuente
44
@geschema, ¿qué herramienta usaste para crear esos bonitos gráficos?
jwfearn
10
Lo sentimos, la pregunta dice explícitamente que no hay números complejos.
Rui Craveiro
66
@Liran: Usé OmniGraffle (solo Mac)
geschema 05 de
39
+1 Creo que esta es la mejor respuesta. No creo que la gente lea lo suficiente, porque todos notaron que la pregunta decía que no se podían usar números complejos. Leí todo, y usted describió la solución en números complejos para establecer el escenario para la solución no compleja a la pregunta formulada. Muy bien hecho.
jrista
1
@jrista todas las soluciones usan una segunda dimensión, que es todo lo que realmente son los 'números complejos' (la mayoría usa impar frente a par, y arriba usa floatvs int). El 'anillo de 4 elementos' que describen muchas respuestas necesita 4 estados, que se pueden representar como 2 dimensiones, cada una con 2 estados. El problema con esta respuesta es que requiere espacio de procesamiento adicional (solo 'funciona' para -64..63, pero necesita -128..127 espacio) y no establece explícitamente la fórmula escrita.
Kirk Broadhurst
65

Funciona excepto int.MaxValue y 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));
    }

pictórico

Rodrick Chapman
fuente
No estoy seguro de por qué esto fue rechazado. ¿Para qué entradas falla?
Rodrick Chapman
¿Por qué no usas la función signum?!?
comonad
1
La imagen es realmente buena. Enviar 0a 0y -2147483648a -2147483648puntos ya que estos dos números son fijos para el operador de negación, x => -x. Para el resto de los números, siga las flechas en la imagen de arriba. Como queda claro por la respuesta de SurDin y sus comentarios, habrá dos números, en este caso 2147483647y -2147483647sin otro par que intercambiar.
Jeppe Stig Nielsen
Parece un smiley - con muchas arrugas
Anshul
48

La pregunta no dice nada acerca de lo que el tipo de entrada y el valor de retorno de la función ftienen que ser (al menos no de la manera que has presentado) ...

... solo que cuando n es un entero de 32 bits f(f(n)) = -n

Entonces, ¿qué tal algo como

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

Si n es un entero de 32 bits, la declaración f(f(n)) == -nserá verdadera.

Obviamente, este enfoque podría extenderse para que funcione para un rango aún más amplio de números ...

Daniel LeCheminant
fuente
2
Furtivo. Límite de caracteres.
Joe Phillips
2
Sí, estaba trabajando en un enfoque similar. Sin embargo, me ganaste. +1 :)
jalf
1
¡Muy inteligente! Esto está muy cerca (y efectivamente lo mismo) de usar números complejos, lo que sería la solución obvia e ideal, pero no se permite explícitamente. Trabajando fuera del rango de números permitidos.
Kirk Broadhurst el
48

para javascript (u otros lenguajes escritos dinámicamente) puede hacer que la función acepte un int o un objeto y devuelva el otro. es decir

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

alternativamente, podría usar la sobrecarga en un lenguaje fuertemente tipado, aunque eso puede romper las reglas

int f(long n) {
    return n;
}

long f(int n) {
    return -n;
}
cobbal
fuente
Esto último no significa el requisito de la función "a" (singular). :)
Dibujó el
Elimine la segunda mitad de la respuesta y esta es una respuesta correcta.
jmucchiello
@Drew por lo que he mencionado que puede romper las reglas
cobbal
2
En JavaScript, una función es un objeto y, por lo tanto, puede mantener un estado.
Nosredna
1
OMI: función f (n) {retorno n. pasado? -n.val: {val: n, pasado: 1}} es más legible y más corto.
SamGoody
46

Dependiendo de su plataforma, algunos idiomas le permiten mantener el estado en la función. VB.Net, por ejemplo:

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

    Return n * flag
End Function

IIRC, C ++ permitió esto también. Sin embargo, sospecho que están buscando una solución diferente.

Otra idea es que, dado que no definieron el resultado de la primera llamada a la función, podría usar impar / par para controlar si invertir el signo:

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

Suma uno a la magnitud de todos los números pares, resta uno de la magnitud de todos los números impares. El resultado de dos llamadas tiene la misma magnitud, pero en una llamada donde incluso cambiamos el signo. Hay algunos casos en los que esto no funcionará (-1, max o min int), pero funciona mucho mejor que cualquier otra cosa sugerida hasta ahora.

Joel Coehoorn
fuente
1
Creo que funciona para MAX_INT ya que eso siempre es extraño. No funciona para MIN_INT y -1.
Airsource Ltd
99
No es una función si tiene efectos secundarios.
nos
12
Eso puede ser cierto en matemáticas, pero es irrelevante en la programación. Entonces, la pregunta es si están buscando una solución matemática o una solución de programación. Pero dado que es para un trabajo de programación ...
Ryan Lundy
+1 Iba a publicar uno en C con "static int x" implementando un FIFO con negación de la salida. Pero esto está lo suficientemente cerca.
phkahler
2
@nos: Sí lo es, simplemente no es referencialmente transparente.
Clark Gaebel
26

Explotación de excepciones de JavaScript.

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

f(f(0)) => 0

f(f(1)) => -1

Anurag
fuente
Dudo excepciones han sido utilizados como esto antes ... :)
NoBugs
+1 Fuera de la caja pensando. ¡Frio! Pero en el código de producción usaría typeof solo para estar seguro.
21

Para todos los valores de 32 bits (con la advertencia de que -0 es -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);
}

Básicamente, necesita emparejar cada bucle -x => x => -x con ay => -y => bucle y. Así que emparejé lados opuestos de la split.

Por ejemplo, para enteros de 4 bits:

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

Una versión de C ++, probablemente doblando las reglas, pero funciona para todos los tipos numéricos (flotantes, ints, dobles) e incluso tipos de clase que sobrecargan el menos 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;
}
Skizz
fuente
Buena idea. Como alternativa, probablemente podría perder la estructura y, en su lugar, hacer que una función devuelva un puntero, la otra función desreferenciar y negar.
Imbue
20

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

Código verificado, todos los enteros posibles de 32 bits pasados, error con -2147483647 (flujo inferior).

LiraNuna
fuente
19

Usa globals ... pero ¿y eso?

bool done = false
f(int n)
{
  int out = n;
  if(!done)
  {  
      out = n * -1;
      done = true;
   }
   return out;
}
teeks99
fuente
3
No estoy seguro de que esta fuera la intención del autor de la pregunta, pero +1 por "pensar fuera de la caja".
Liran Orevi
55
En lugar de decir condicionalmente "done = true", siempre debe decir "done =! Done", de esa manera su función se puede usar más de una vez.
Chris Lutz
@Chris, dado que la configuración de hecho a verdadero está dentro de un bloque if (! Done), es equivalente a done =! Done, pero! Done no necesita ser calculado (u optimizado por el compilador, si es lo suficientemente inteligente) .
nsayer
1
Mi primer pensamiento también fue resolver esto usando una variable global, a pesar de que parecía una trampa para esta pregunta en particular. Sin embargo, diría que una solución variable global es la mejor solución dadas las especificaciones de la pregunta. Usar un global hace que sea muy fácil entender lo que está sucediendo. Sin embargo, estaría de acuerdo en que a done! = Done sería mejor. Simplemente muévalo fuera de la cláusula if.
Alderath
3
Técnicamente, cualquier cosa que mantenga el estado no es una función, sino una máquina de estados. Por definición , una función siempre da la misma salida para la misma entrada.
Ted Hopp
19

Esta solución de Perl funciona para enteros, flotantes y cadenas .

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

Prueba algunos datos de prueba.

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

Salida:

-2 2
0 0
1 -1
1.1 -1.1
-3.3 3.3
foo -foo
-bar +bar
revs FMc
fuente
Pero no lo mantiene como int. Esencialmente está almacenando datos de variables globales en el int "n" en sí mismo ... excepto que no es un int, de lo contrario no podría hacerlo. Por ejemplo, si nfuera una cadena, podría hacer que 548 se convierta en "First_Time_548" y luego la próxima vez que se ejecute a través de la función ... if (prefix == First_Time_ ") reemplaza" First_Time_ "con" - "
Albert Renshaw
@AlbertRenshaw No estoy seguro de dónde sacas esas ideas. (1) Definitivamente no hay variables globales involucradas aquí. (2) Si asigna a la función un int, obtendrá un int back, o una referencia a un int, si llama a la función un número impar de veces. (3) Quizás más fundamentalmente, este es Perl . A todos los efectos prácticos, las entradas y las cadenas son totalmente intercambiables. Las cadenas que parecen números funcionarán perfectamente como números en la mayoría de los contextos, y los números se encadenarán felizmente cuando se les solicite.
FMc
Lo siento, no sé mucho Perl, parecía que estaba usando una matriz global jaja
Albert Renshaw
18

Nadie dijo que f (x) tenía que ser del mismo tipo.

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


f(2) => [2]
f(f(2)) => -2
Marcar Z
fuente
16

Realmente no estoy tratando de dar una solución al problema en sí, pero tengo un par de comentarios, ya que la pregunta dice que este problema fue parte de una entrevista (de trabajo):

  • Primero preguntaría "¿Por qué se necesitaría tal función? ¿Cuál es el mayor problema del que forma parte?" en lugar de tratar de resolver el problema real planteado en el acto. Esto muestra cómo pienso y cómo abordo problemas como este. ¿Quien sabe? Esa podría incluso ser la razón real por la que se hace la pregunta en una entrevista en primer lugar. Si la respuesta es "No te preocupes, asume que es necesario y muéstrame cómo diseñarías esta función". Entonces continuaría haciéndolo.
  • Luego, escribiría el código de caso de prueba de C # que usaría (lo obvio: bucle de int.MinValue a int.MaxValue, y para cada nllamada dentro de ese rango f(f(n))y verificar el resultado es -n), diciéndole que luego usaría Test Driven Development para llegar a tal función.
  • Solo si el entrevistador continúa pidiéndome que resuelva el problema planteado, comenzaría a tratar de garabatear pseudocódigo durante la entrevista para intentar obtener algún tipo de respuesta. Sin embargo, realmente no creo que estaría saltando para tomar el trabajo si el entrevistador fuera una indicación de cómo es la compañía ...

Oh, esta respuesta asume que la entrevista fue para un puesto relacionado con la programación de C #. Por supuesto, sería una respuesta tonta si la entrevista fuera para un puesto relacionado con las matemáticas. ;-)

revs peSHIr
fuente
77
Tienes suerte que pidieron 32 int, si era de 64 bits de la entrevista no continuará después de ejecutar las pruebas ;-)
alex2k8
De hecho, si llegara a un punto para escribir esa prueba y ejecutarla durante una entrevista. ;-) Mi punto: trataría de no llegar a ese punto en una entrevista. La programación es más "una forma de pensar" que "cómo escribe líneas de código" en mi opinión.
peSHIr
77
No sigas este consejo en una entrevista real. El entrevistador espera que realmente responda la pregunta. Cuestionar la relevancia de la pregunta no le comprará nada, pero puede molestar al entrevistador. Diseñar una prueba trivial no te acerca a la respuesta, y no puedes ejecutarla en la entrevista. Si obtiene información adicional (32 bits), intente descubrir cómo podría ser útil.
Stefan Haustein
Un entrevistador que se molesta cuando le pido más información (aunque posiblemente cuestione la relevancia de su pregunta en el proceso) no es un entrevistador con el que necesariamente quiero trabajar. Así que seguiré haciendo preguntas como esa en entrevistas. Si no les gusta, probablemente terminaré la entrevista para dejar de perder más tiempo. No me gusta la mentalidad de "Solo estaba siguiendo órdenes" un poco. Vos si..?
PERS
16

Me gustaría cambiar los 2 bits más significativos.

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

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

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

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

Como puede ver, es solo una adición, dejando fuera el bit llevado.

¿Cómo llegué a la respuesta? Mi primer pensamiento fue solo una necesidad de simetría. 4 vueltas para volver a donde empecé. Al principio pensé, ese es el código Gray de 2 bits. Entonces pensé que en realidad el binario estándar es suficiente.

eipipuz
fuente
El problema con este enfoque es que no funciona con los números negativos del complemento de dos (que es lo que usa cada CPU moderna). Es por eso que eliminé mi respuesta idéntica.
Tamas Czinege el
La pregunta especificaba enteros con signo de 32 bits. Esta solución no funciona para las representaciones del complemento a dos o del complemento a uno de los enteros con signo de 32 bits. Solo funcionará para representaciones de signo y magnitud, que son muy poco comunes en las computadoras modernas (que no sean números de coma flotante).
Jeffrey L Whitledge
1
@DrJokepu - Wow, después de seis meses - ¡Maldición!
Jeffrey L Whitledge
¿No solo necesita convertir los números en una representación de signo y magnitud dentro de la función, realizar la transformación y luego volver a convertirla en la representación entera nativa antes de devolverla?
Bill Michell el
Me gusta que básicamente hayas implementado números complejos al introducir un bit imaginario :)
jabirali
16

Aquí hay una solución inspirada en el requisito o la afirmación de que los números complejos no se pueden usar para resolver este problema.

Multiplicar por la raíz cuadrada de -1 es una idea, que solo parece fallar porque -1 no tiene una raíz cuadrada sobre los enteros. Pero jugar con un programa como Mathica da, por ejemplo, la ecuación

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

y esto es casi tan bueno como tener una raíz cuadrada de -1. El resultado de la función debe ser un entero con signo. Por lo tanto, voy a usar un modulo modificado mods de operación (x, n) que devuelve el entero y congruente a x módulo n que está más cerca de 0. Solo muy pocos lenguajes de programación tienen una operación de módulo similar, pero se puede definir fácilmente . Por ejemplo, en python es:

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

Usando la ecuación anterior, el problema ahora se puede resolver como

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

Esto satisface f(f(x)) = -xa todos los enteros en el rango . Los resultados de también están en este rango, pero por supuesto el cálculo necesitaría enteros de 64 bits.[-231-2, 231-2]f(x)

Accipitridae
fuente
13

C # para un rango de 2 ^ 32 - 1 números, todos los números int32 excepto (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

huellas dactilares:

2147483647
3
2
1
0
-1
-2
-3
-2147483647
Pop Catalin
fuente
Esto tampoco funciona para f (0) que es 1073741824. f (1073741824) = 0. f (f (1073741824)) = 1073741824
Dinah
En general, se puede demostrar que para el complemento a dos de tipo entero de cualquier tamaño en bits, la función tiene que no trabajo para al menos dos valores de entrada.
slacker
12

Esencialmente, la función tiene que dividir el rango disponible en ciclos de tamaño 4, con -n en el extremo opuesto del ciclo de n. Sin embargo, 0 debe ser parte de un ciclo de tamaño 1, porque de lo contrario0->x->0->x != -x . Debido a que 0 está solo, debe haber otros 3 valores en nuestro rango (cuyo tamaño es un múltiplo de 4) que no están en un ciclo adecuado con 4 elementos.

Elegí estos valores extraños adicionales a ser MIN_INT, MAX_INTy MIN_INT+1. Además, se MIN_INT+1asignará MAX_INTcorrectamente, pero se quedará atascado allí y no se asignará de nuevo. Creo que este es el mejor compromiso, porque tiene la buena propiedad de que solo los valores extremos no funcionan correctamente. Además, significa que funcionaría para todos los 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)
Strilanc
fuente
12

Nadie dijo que tenía que ser apátrida.

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

Hacer trampa, pero no tanto como muchos de los ejemplos. Aún más mal sería mirar la pila para ver si la dirección de la persona que llama es & f, pero esto será más portátil (aunque no seguro para subprocesos ... la versión segura para subprocesos usaría TLS). Aún más malvado:

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

Por supuesto, ninguno de estos funciona demasiado bien para el caso de MIN_INT32, pero hay poco que pueda hacer al respecto a menos que se le permita devolver un tipo más amplio.

Christopher Smith
fuente
puede 'actualizarlo' para preguntar sobre la dirección (sí, debe obtenerla ref \ como puntero) - en C, por ejemplo: int f (int & n) {static int * addr = & n; if (addr == & n) {return -n; } retorno n; }
IUnknownPointer
11

Me imagino que usar el bit 31 como bit imaginario ( i ) sería un enfoque que admitiría la mitad del rango total.

llamaoo7
fuente
Esto sería más complejo pero no más efectivo que la mejor respuesta actual
1800 INFORMACIÓN
1
@ 1800 INFORMACIÓN: Por otro lado, el dominio [-2 ^ 30 + 1, 2 ^ 30-1] es contiguo, lo que es más atractivo desde un punto de vista matemático.
Jochen Walter el
10

funciona para 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
}
MartinStettner
fuente
10

El problema establece "enteros con signo de 32 bits", pero no especifica si son dos-complemento o unos-complemento .

Si usa complementos de uno, entonces todos los valores de 2 ^ 32 ocurren en ciclos de longitud cuatro: no necesita un caso especial para cero, y tampoco necesita condicionales.

C ª:

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

Esto funciona por

  1. Intercambiando los bloques altos y bajos de 16 bits
  2. Invertir uno de los bloques

Después de dos pasadas, tenemos el inverso en bits del valor original. Que en la representación del complemento uno es equivalente a la negación.

Ejemplos:

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)
finnw
fuente
1
¿Qué pasa con el orden de bytes en diferentes arquitecturas?
Steven
1
Toda la aritmética es de 32 bits. No manipulo bytes individuales, por lo que el orden de los bytes no lo afectará.
finnw
Esto suena bastante cerca. Puede suponer que la entrada es 2-complemento. Entonces se convierte a la representación de bit de signo. Ahora, dependiendo del último bit, voltea el primer bit y el último bit o simplemente el último bit. Básicamente, usted niega solo números pares y realiza ciclos pares / impares todo el tiempo. Entonces regresa de impar a impar e incluso a incluso después de 2 llamadas. Al final vuelve a convertir a 2-complemento. He publicado el código para esto en algún lugar a continuación.
Stefan Haustein
9

:RE

boolean inner = true;

int f(int input) {
   if(inner) {
      inner = false;
      return input;
   } else {
      inner = true;
      return -input;
   }
}
Dibujó
fuente
55
¡También podría darle una discusión sobre por qué las variables globales son malas si no lo expulsan de la entrevista allí mismo!
palswim
9
return x ^ ((x%2) ? 1 : -INT_MAX);
Mike Meehan
fuente
7

Me gustaría compartir mi punto de vista sobre este interesante problema como matemático. Creo que tengo la solución más eficiente.

Si no recuerdo mal, niegas un entero de 32 bits con signo simplemente volteando el primer bit. Por ejemplo, si n = 1001 1101 1110 1011 1110 0000 1110 1010, entonces -n = 0001 1101 1110 1011 1110 0000 1110 1010.

Entonces, ¿cómo definimos una función f que toma un entero de 32 bits con signo y devuelve otro entero de 32 bits con la propiedad de que tomar f dos veces es lo mismo que voltear el primer bit?

Permítanme reformular la pregunta sin mencionar conceptos aritméticos como enteros.

¿Cómo definimos una función f que toma una secuencia de ceros y unos de longitud 32 y devuelve una secuencia de ceros y unos de la misma longitud, con la propiedad de que tomar f dos veces es lo mismo que voltear el primer bit?

Observación: Si puede responder la pregunta anterior para el caso de 32 bits, también puede responder para el caso de 64 bits, el caso de 100 bits, etc. Simplemente aplique f al primer bit de 32 bits.

Ahora si puedes responder la pregunta para el caso de 2 bits, ¡Voila!

Y sí, resulta que cambiar los primeros 2 bits es suficiente.

Aquí está el pseudocódigo

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.

Observación: El paso 2 y el paso 3 juntos se pueden resumir como (a, b) -> (-b, a). ¿Luce familiar? Eso debería recordarle la rotación de 90 grados del plano y la multiplicación por la raíz cuadrada de -1.

Si solo presentara el pseudocódigo solo sin el largo preludio, parecería un conejo fuera del sombrero, quisiera explicar cómo obtuve la solución.

Yoo
fuente
66
Sí, es un problema interesante. Ya sabes tus matemáticas. Pero este es un problema informático. Entonces necesitas estudiar computadoras. La representación de magnitud de signo es permisible, pero pasó de moda hace unos 60 años. El complemento 2 es el más popular.
Programador de Windows el
55
Esto es lo que su función hace a los dos bits cuando se aplica dos veces: (a, b) -> (-b, a) -> (-a, -b). Pero, estamos tratando de llegar a (-a, b), no (-a, -b).
buti-oxa
@ buti-oxa, tienes razón. La operación de dos bits debería ser como: 00 -> 01 -> 10 -> 11 -> 00. Pero entonces mi algoritmo asume una representación de magnitud de signo que ahora es impopular, como dijo el programador de Windows, así que creo que mi algoritmo es de poca utilidad. .
Yoo
Entonces, ¿no puede simplemente hacer los pasos dos veces en lugar de una?
Nosredna 03 de
44
buti-oxa tiene toda la razón: la función ni siquiera da la vuelta al primer bit después de dos invocaciones, da la vuelta a los primeros dos bits. Voltear todos los bits está más cerca de lo que hace el complemento de 2, pero no es exactamente correcto.
redtuna