Projetando a função f (f (n)) == -n


841

Uma pergunta que recebi na minha última entrevista:

Projete uma função f, de modo que:

f(f(n)) == -n

Onde né um número inteiro assinado de 32 bits ; você não pode usar aritmética de números complexos.

Se você não pode projetar essa função para todo o intervalo de números, projete-o para o maior intervalo possível.

Alguma ideia?


2
Para que trabalho foi essa entrevista?
tymtam

Respostas:


377

E se:

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

Em 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)

O Python promove automaticamente números inteiros com comprimentos arbitrários. Em outros idiomas, o maior número inteiro positivo excederá o limite, portanto funcionará para todos os números inteiros, exceto aquele.


Para fazê-lo funcionar com números reais, você precisa substituir n em (-1) n por { ceiling(n) if n>0; floor(n) if n<0 }.

Em C # (funciona para qualquer duplo, exceto em situações de estouro):

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
Quebrado para -1, porque -1 * 0 ainda é 0
Joel Coehoorn

3
Não, não é. f (-1) = 0. f (0) = 1
1800 INFORMAÇÃO

5
Porém, está quebrado por 1. f (1) = 0. f (0) = 1
1800 INFORMAÇÃO

18
Hmm, salvando o estado com números pares e ímpares, eu deveria ter pensado nisso.
314 Unknown

38
Penso que o mais importante não é a função real (existem infinitas soluções), mas o processo pelo qual você pode construir essa função.
pyon

440

Você não disse que tipo de linguagem eles esperavam ... Aqui está uma solução estática (Haskell). É basicamente mexer com os 2 bits mais significativos:

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

É muito mais fácil em uma linguagem dinâmica (Python). Basta verificar se o argumento é um número X e retornar um lambda que retorne -X:

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

23
Legal, eu amo isso ... a mesma abordagem em JavaScript: var f = function (n) {return (typeof n == 'function')? n (): function () {retorno -n; }}
Mark Renouf

Provavelmente é que meu Haskell está muito enferrujado, mas você verificou isso para (f 0)? Parece que deve produzir o mesmo resultado que (f 0x80000000), pelo menos se estivermos lidando com entradas de 32 bits com aritmética envolvente (na operação de negação). E isso seria ruim.
Darius Bacon

11
Será que o entrevistador média mesmo saber o que uma construção lambda é ?
Jeremy Powell

4
Obviamente, esse tipo de truque de trapaça também funciona em Haskell, mesmo que seja 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

4
O que? De onde você tirou a idéia de que tipo de f (n) === 'função', especialmente onde n é um número e você espera que um número seja retornado? Não entendo como um caso de instância se aplica aqui. Eu não falo bem Python, mas no JS, o argumento de verificação de um tipo de função está totalmente errado nesse caso. Somente a solução numérica se aplica aqui. f é uma função, f (n) é número.
Harry

284

Aqui está uma prova do porquê de tal função não existir, para todos os números, se ela não usar informações extras (exceto 32 bits de int):

Devemos ter f (0) = 0. (Prova: Suponha que f (0) = x. Então f (x) = f (f (0)) = -0 = 0. Agora, -x = f (f (x) )) = f (0) = x, o que significa que x = 0.)

Além disso, para qualquer xe y, suponha f(x) = y. Nós queremos f(y) = -xentão. E f(f(y)) = -y => f(-x) = -y. Para resumir: se f(x) = y, então f(-x) = -y, ef(y) = -x , e f(-y) = x.

Portanto, precisamos dividir todos os números inteiros, exceto 0, em conjuntos de 4, mas temos um número ímpar desses números inteiros; além disso, se removermos o número inteiro que não possui uma contraparte positiva, ainda temos 2 números (mod4).

Se removermos os 2 números máximos restantes (pelo valor abs), podemos obter a função:

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

É claro que outra opção é não cumprir com 0 e obter os 2 números que removemos como bônus. (Mas isso é apenas um tolo se.)


29
Não acredito que precisei ler até aqui para encontrar uma boa solução processual que lide com números negativos sem recorrer a variáveis ​​globais ou truques que ofuscam o código. Se eu pudesse votar em você mais de uma vez, eu o faria.
Kyle Simek #

Boa observação, que existe um número ímpar de números inteiros diferentes de zero em qualquer n bits com sinal.
Andres Jaan Tack

Essa seria a minha resposta também, mas cuidado com o caso extremo n = -2147483648(valor mínimo); você não pode abs(n), nesse caso, e o resultado será indefinido (ou uma exceção).
precisa

1
@ a1kmm: Desculpe, -2³² acima deveria ter sido -2³¹. De qualquer forma, o caso em que f (0) ≠ 0 (e então f (0) = - 2³ 2) é realmente o caso mais fácil, pois mostramos que esses dois estão desconectados do resto. O outro caso que precisamos considerar é que f (0) = 0, mas f (x) = - 2³¹ para alguns x ≠ 0, x ≠ -2³¹. Nesse caso, f (-2³¹) = f (f (x)) = - x (note -x não pode ser -2³¹, porque esse x não existe). Além disso, deixe f (-x) = y. Então f (y) = f (f (-x)) = x. Novamente, y não pode ser -2³¹ (como f (y) = x, mas f (-2³¹) = - xex não é 0). Então, -2³¹ = f (x) = f (f (y)) = - y, o que é impossível. Portanto, de fato, 0 e -2³ disconn devem ser desconectados do restante (e não a imagem de qualquer outra coisa).
ShreevatsaR

1
@will Não há zeros assinados, se (como eu assumo) estamos falando de números inteiros de 32 bits com complemento de dois.
goffrie

146

Graças à sobrecarga em 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
Infelizmente, por causa do nome incorreto, as funções que você chama de "f" na verdade têm nomes mais estranhos.
pyon

1
Eu pensei em algo assim, mas pensando em C, isso foi jogado fora ... bom trabalho!
Liran Orevi

@Rui Craverio: Não funcionaria no .NET 3.5+ porque o autor optou por usar a palavra-chave var como um nome de variável.
Kredns

72
tecnicamente ... não é isso que a pergunta exige. você definiu 2 f () funções, f (int) e F (float) e as perguntas pede "Projete uma função f () ..."
elcuco

2
@elcuco Tecnicamente, é claro, mas logicamente é uma função com várias sobrecargas (você pode fazer f (f (42)) com isso). Como a definição não diz nada sobre parâmetros e valor de retorno, dificilmente posso aceitá-la como uma definição técnica.
Marek Toman

135

Ou você pode abusar do pré-processador:

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

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

Então você seria Konrad "Le Chiffre" Rudolph, então? Vou pegar meu casaco. Sim, eu sei sobre toda a coisa "void main", mas adicionando um "return 0;" é apenas muito esforço extra ;-)
Skizz

25
@ Skizz, o retorno 0 do main não é necessário em c ++, mesmo com o valor de retorno int ... então, fazendo isso corretamente, você digita um caractere a menos!
11119 Dan Olson

10
Skizz sempre abusos pré-processador: D
Arnis Lapsa

23
Esta não é uma função ..assim isso não é uma solução válida
smerlin

3
@smerlin: Tecnicamente, é uma função inline retornando uma função inline: os corpos de ambos são expandidos em tempo de compilação, ou melhor, pouco antes. Não pode ser muito mais eficiente que isso.
amigos estão dizendo sobre jon

103

Isso é verdade para todos os números negativos.

    f (n) = abs (n)

Como há mais um número negativo do que números positivos para dois números inteiros de complemento, f(n) = abs(n)é válido para mais um caso que a f(n) = n > 0 ? -n : nsolução que é a mesma que f(n) = -abs(n). Peguei você por um ...: D

ATUALIZAR

Não, não é válido para mais um caso, pois acabei de reconhecer pelo comentário do litb ... abs(Int.Min)apenas transbordará ...

Também pensei em usar as informações do mod 2, mas concluí que elas não funcionam ... até cedo. Se bem feito, funcionará para todos os números, exceto Int.Minporque isso excederá.

ATUALIZAR

Eu brinquei com ele por um tempo, procurando um bom truque de manipulação, mas não consegui encontrar um bom one-liner, enquanto a solução mod 2 se encaixa em um.

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

Em C #, isso se torna o seguinte:

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

Para fazê-lo funcionar para todos os valores, você tem que substituir Math.Abs()com (n > 0) ? +n : -ne incluir o cálculo em um uncheckedbloco. Então você é Int.Minmapeado para si mesmo como a negação desmarcada.

ATUALIZAR

Inspirado por outra resposta, vou explicar como a função funciona e como construí-la.

Vamos começar do começo. A função fé aplicada repetidamente a um determinado valor, nproduzindo uma sequência de valores.

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

A pergunta exige f(f(n)) = -n, ou seja, duas aplicações sucessivas de fnegar o argumento. Duas outras aplicações f- quatro no total - negam que o argumento volte a render-se nnovamente.

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

Agora há um ciclo óbvio de comprimento quatro. Substituindo x = f(n)e observando que a equação obtida se f(f(f(n))) = f(f(x)) = -xmantém, produz o seguinte.

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

Então, temos um ciclo de comprimento quatro com dois números e os dois números negados. Se você imaginar o ciclo como um retângulo, os valores negativos estão localizados em cantos opostos.

Uma das muitas soluções para construir esse ciclo é a seguinte, partindo de n.

 n => nega e subtrai um
-n - 1 = - (n + 1) => adicione um
-n => negue e adicione um
 n + 1 => subtrai um
 n

Um exemplo concreto é esse ciclo +1 => -2 => -1 => +2 => +1. Estamos quase terminando. Observando que o ciclo construído contém um número positivo ímpar, seu sucessor par e ambos os números negam, podemos facilmente dividir os números inteiros em muitos desses ciclos ( 2^32é um múltiplo de quatro) e descobrimos uma função que satisfaz as condições.

Mas temos um problema com zero. O ciclo deve conter 0 => x => 0porque o zero é negado para si mesmo. E porque o ciclo já indica 0 => xa seguir 0 => x => 0 => x. Este é apenas um ciclo de comprimento dois e xé transformado em si mesmo após duas aplicações, não em -x. Felizmente, há um caso que resolve o problema. Se Xigual a zero, obtemos um ciclo de comprimento um contendo apenas zero e resolvemos esse problema concluindo que o zero é um ponto fixo de f.

Feito? Quase. Temos 2^32números, zero é um ponto fixo que deixa 2^32 - 1números e devemos particionar esse número em ciclos de quatro números. Ruim que 2^32 - 1não é múltiplo de quatro - restarão três números que não estão em nenhum ciclo de comprimento quatro.

Explicarei a parte restante da solução usando o conjunto menor de itegers assinados de 3 bits, que variam de -4até +3. Terminamos com zero. Temos um ciclo completo +1 => -2 => -1 => +2 => +1. Agora vamos construir o ciclo começando em +3.

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

O problema que surge é que +4não é representável como número inteiro de 3 bits. Nós +4obteríamos negando -3a +3- o que ainda é um número inteiro válido de 3 bits - mas adicionando um a +3(binário 011) produz 100binário. Interpretado como número inteiro não assinado, +4mas temos que interpretá-lo como número inteiro assinado -4. Portanto, na verdade, -4para este exemplo ou Int.MinValuepara o caso geral, existe um segundo ponto fixo de negação aritmética inteira - 0 e Int.MinValuesão mapeados para eles. Portanto, o ciclo é realmente o seguinte.

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

É um ciclo de duração dois e, adicionalmente, +3entra no ciclo via -4. Em conseqüência, -4é corretamente mapeado para si mesmo após duas aplicações de funções, +3é corretamente mapeado para -3após duas aplicações de funções, mas -3é erroneamente mapeado para si mesmo após duas aplicações de funções.

Então construímos uma função que funciona para todos os números inteiros, exceto um. Podemos fazer melhor? Não nós não podemos. Por quê? Temos que construir ciclos de comprimento quatro e somos capazes de cobrir todo o intervalo inteiro até quatro valores. Os valores restantes são os dois pontos fixos 0e Int.MinValuedevem ser mapeados para si mesmos e dois números inteiros arbitrários xe -xque devem ser mapeados entre si por dois aplicativos de função.

Para mapear xa -xe vice-versa, devem formar um ciclo de quatro e que devem estar localizados em cantos opostos desse ciclo. Em conseqüência, 0e Int.MinValuetambém deve estar em cantos opostos. Isto irá mapear corretamente xe -xmas trocar os dois pontos fixos 0e Int.MinValueapós duas aplicações de função e deixar-nos com duas entradas de falha. Portanto, não é possível construir uma função que funcione para todos os valores, mas temos uma que funcione para todos os valores, exceto um, e isso é o melhor que podemos alcançar.


Não cumprir os critérios: abs (abs (n)) = -n!
Dan Olson

Claro que sim, para todos os números negativos, como ele disse. Isso fazia parte da pergunta: se você não conseguir criar uma questão geral, escolha uma que funcione para a maior variedade possível.
jalf

Esta resposta é pelo menos tão boa como a resposta por Marj Synowiec e Rowland Shaw, ele só funciona para uma gama diferente de números
1800 INFORMATION

19
Cara, você também pode se livrar das "ATUALIZAÇÕES" e escrever uma resposta correta e coesa. Os 3/4 inferiores ("inspirados em outra resposta") são impressionantes.
Andres Jaan Tack

1
Eu realmente gosto da solução abs para números negativos. Simples e fácil de entender.
Thorbjørn Ravn Andersen

97

Usando números complexos, você pode efetivamente dividir a tarefa de negar um número em duas etapas:

  • multiplique n por i e você obtém n * i, que é n girado 90 ° no sentido anti-horário
  • multiplique novamente por i, e você obtém -n

O melhor é que você não precisa de nenhum código de manipulação especial. Apenas multiplicando por i faz o trabalho.

Mas você não tem permissão para usar números complexos. Então você precisa criar de alguma forma seu próprio eixo imaginário, usando parte do seu intervalo de dados. Como você precisa exatamente dos valores imaginários (intermediários) dos valores iniciais, você terá apenas metade do intervalo de dados.

Tentei visualizar isso na figura a seguir, assumindo dados de 8 bits assinados. Você teria que escalar isso para números inteiros de 32 bits. O intervalo permitido para n inicial é de -64 a +63. Aqui está o que a função faz para n positivo:

  • Se n estiver em 0..63 (intervalo inicial), a chamada da função adicionará 64, mapeando n para o intervalo 64..127 (intervalo intermediário)
  • Se n estiver em 64..127 (intervalo intermediário), a função subtrai n de 64, mapeando n para o intervalo 0 ..- 63

Para n negativo, a função usa o intervalo intermediário -65 ..- 128.

texto alternativo


4
@eschema, que ferramenta você usou para criar esses gráficos legais?
jwfearn

10
Desculpe, a pergunta diz explicitamente que não há números complexos.
Rui Craveiro

6
@Liran: Eu usei OmniGraffle (Mac-only)
geschema

39
+1 Acho que esta é a melhor resposta. Não acho que as pessoas leiam o suficiente, porque todas notaram que a pergunta dizia que números complexos não podiam ser usados. Eu li a coisa toda e você descreveu a solução em números complexos para preparar o cenário para a solução não complexa para a pergunta. Muito bem feito.
jrista

1
@jrista todas as soluções usam uma segunda dimensão, que é tudo o que realmente são 'números complexos' (a maioria usa ímpar vs par e acima usa floatvs int). O 'anel de 4 elementos' que muitas respostas descrevem requer 4 estados, que podem ser representados como 2 dimensões, cada uma com 2 estados. O problema com esta resposta é que ela requer espaço de processamento adicional (apenas 'funciona' para -64..63, mas precisa de espaço -128..127) e não indica explicitamente a fórmula escrita!
precisa

65

Funciona, exceto 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));
    }

pictórico


Não sei por que isso foi prejudicado. Para quais entradas ele falha?
Rodrick Chapman

Por que você não usa a função signum?!?
comonad

1
A imagem é realmente boa. Enviar 0para 0e -2147483648para, -2147483648pois esses dois números são pontos fixos para o operador de negação x => -x,. Para o restante dos números, siga as setas na imagem acima. Como fica claro na resposta de SurDin e seus comentários, haverá dois números, neste caso, 2147483647e -2147483647sem nenhum outro par para trocar.
Jeppe Stig Nielsen

Parece que um smiley - com grande quantidade de rugas
Anshul

48

A questão não diz nada sobre o que o tipo de entrada e valor de retorno da função ftem que ser (pelo menos não da maneira que você apresentou-o) ...

... apenas quando n é um número inteiro de 32 bits, f(f(n)) = -n

Então, que tal algo como

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

Se n for um número inteiro de 32 bits, a instrução f(f(n)) == -nserá verdadeira.

Obviamente, essa abordagem pode ser estendida para trabalhar com uma variedade ainda maior de números ...


2
Sorrateiro. Limite de caracteres.
31410 Joe Phillips

2
Sim, eu estava trabalhando em uma abordagem semelhante. Você me venceu no entanto. 1 :)
jalf 08/04/09

1
Muito esperto! Isso está muito próximo (e efetivamente é o mesmo) do uso de números complexos, que seria a solução óbvia e ideal, mas explicitamente proibida. Trabalhando fora do intervalo de números permitidos.
Kirk Broadhurst

48

para javascript (ou outras linguagens de tipo dinâmico), você pode fazer com que a função aceite um int ou um objeto e retorne o outro. ie

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

Como alternativa, você pode usar a sobrecarga em uma linguagem fortemente tipada, embora isso possa violar as regras.

int f(long n) {
    return n;
}

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

O último não significa o requisito da função "a" (singular). :)
de Drew

Remova a segunda metade da resposta e esta é uma resposta correta.
jmucchiello

@Drew é por isso que eu mencionei que ele pode quebrar as regras
cobbal

2
Em JavaScript, uma função é um objeto e, portanto, pode manter um estado.
Nosredna

1
IMO: função f (n) {retorno n.passado? -n.val: {val: n, passou: 1}} é mais legível e mais curto.
SamGoody

46

Dependendo da sua plataforma, alguns idiomas permitem manter o estado na função. VB.Net, por exemplo:

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

    Return n * flag
End Function

IIRC, C ++ também permitiu isso. Eu suspeito que eles estão procurando uma solução diferente.

Outra idéia é que, como eles não definiram o resultado da primeira chamada para a função, você pode usar ímpar / imparcialidade para controlar se deve inverter o sinal:

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

Adicione um à magnitude de todos os números pares e subtraia um da magnitude de todos os números ímpares. O resultado de duas chamadas tem a mesma magnitude, mas a única chamada em que é trocamos o sinal. Existem alguns casos em que isso não funciona (-1, max ou min int), mas funciona muito melhor do que qualquer outra coisa sugerida até o momento.


1
Acredito que funciona para MAX_INT, pois isso é sempre estranho. Não funciona para MIN_INT e -1.
Airsource Ltd

9
Não é uma função se tiver efeitos colaterais.
nºs

12
Isso pode ser verdade em matemática, mas é irrelevante na programação. Portanto, a questão é se eles estão procurando uma solução matemática ou uma solução de programação. Mas dado que é para um trabalho de programação ...
Ryan Lundy

+1 eu ia postar um com C com "static int x" implementando um FIFO com negação da saída. Mas isso está perto o suficiente.
phkahler

2
@nos: Sim, apenas não é referencialmente transparente.
Clark Gaebel

26

Explorando exceções de JavaScript.

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

f(f(0)) => 0

f(f(1)) => -1


Duvido exceções têm sido utilizados como isso antes ... :)
NoBugs

+1 Pensamento pronto para uso. Legal! Mas no código de produção eu usaria typeof apenas para ser seguro.

21

Para todos os valores de 32 bits (com a ressalva de que -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);
}

Você basicamente precisa emparelhar cada loop -x => x => -x com um loop ay => -y => y. Então eu emparelhei lados opostos dosplit .

por exemplo, para números inteiros de 4 bits:

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

21

Uma versão C ++, provavelmente dobrando as regras um pouco, mas funciona para todos os tipos numéricos (flutuantes, ints, duplos) e até tipos de classe que sobrecarregam o menos unário:

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

Boa ideia. Como alternativa, você provavelmente pode perder a estrutura e, em vez disso, uma função retorna um ponteiro, a outra função desreferencia e nega.
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 os possíveis números inteiros de 32 bits passados, erro com -2147483647 (underflow).


19

Usa globais ... mas e daí?

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

3
Não tenho certeza de que essa era a intenção do autor da pergunta, mas +1 por "pensar fora da caixa".
Liran Orevi

5
Em vez de dizer condicionalmente "done = true", você sempre deve dizer "done =! Done", para que sua função possa ser usada mais de uma vez.
31711 Chris Lutz

@ Chris, já que a configuração done como true está dentro de um bloco if (! Done), é equivalente a done =! Done, mas! Done não precisa ser computado (ou otimizado pelo compilador, se for inteligente o suficiente) .
Nsayer

1
Meu primeiro pensamento também foi resolver isso usando uma variável global, mesmo que isso parecesse trapacear para essa pergunta em particular. No entanto, eu argumentaria que uma solução de variável global é a melhor solução, dadas as especificações da pergunta. Usar um global facilita a compreensão do que está acontecendo. Concordo que um feito! = Feito seria melhor. Apenas mova isso para fora da cláusula if.
Alderath

3
Tecnicamente, qualquer coisa que mantenha estado não é uma função, mas uma máquina de estado. Por definição , uma função sempre fornece a mesma saída para a mesma entrada.
Ted Hopp

19

Essa solução Perl funciona para números inteiros, flutuantes e seqüências de caracteres .

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

Experimente alguns dados de teste.

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

Resultado:

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

Mas isso não mantém int. Você está essencialmente armazenando dados variáveis ​​globais no próprio int "n" ... exceto que não é um int, caso contrário você não poderia fazer isso. Por exemplo, se nfoi uma corda que eu poderia fazer 548 torna-se "First_Time_548" e, em seguida, na próxima vez que ele é executado através da função ... if (prefixo == First_Time_ ") substituir 'First_Time_' com '-'
Albert Renshaw

@AlbertRenshaw Não tenho certeza de onde você tira essas idéias. (1) Definitivamente, não há variáveis ​​globais envolvidas aqui. (2) Se você der um int à função, receberá um int de volta - ou uma referência a um int, se chamar a função um número ímpar de vezes. (3) Talvez o mais fundamental seja o Perl . Para todos os fins práticos, ints e strings são totalmente intercambiáveis. Seqüências de caracteres que se parecem com números funcionarão perfeitamente bem como números na maioria dos contextos, e os números serão felizes quando forem solicitados.
FMc

Desculpe, eu não sei muito perl parecia que você estava usando uma matriz global haha
Albert Renshaw

18

Ninguém nunca disse que f (x) tinha que ser do mesmo tipo.

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


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

16

Na verdade, não estou tentando dar uma solução para o problema em si, mas tenho alguns comentários, pois a pergunta afirma que esse problema foi colocado como parte de uma entrevista (de trabalho?):

  • Eu perguntaria primeiro "Por que essa função seria necessária? Qual é o maior problema disso?" em vez de tentar resolver o problema real no local. Isso mostra como eu penso e como resolvo problemas como este. Quem sabe? Essa pode até ser a verdadeira razão pela qual a pergunta é feita em uma entrevista em primeiro lugar. Se a resposta for "Não se preocupe, suponha que seja necessário e mostre-me como você projetaria essa função". Eu continuaria a fazê-lo.
  • Então, eu escreveria o código do caso de teste C # que usaria (o óbvio: loop de int.MinValuepara int.MaxValuee para cada nchamada nesse intervalo f(f(n))e verificar se o resultado é-n ), dizendo que usaria o Desenvolvimento Orientado a Testes para obter essa função.
  • Somente se o entrevistador continuar me pedindo para resolver o problema proposto, eu realmente começarei a tentar escrever o pseudocódigo durante a própria entrevista para tentar obter algum tipo de resposta. No entanto, eu realmente não acho que gostaria de aceitar o emprego se o entrevistador fosse alguma indicação de como é a empresa ...

Ah, essa resposta assume que a entrevista foi para uma posição relacionada à programação em C #. Obviamente, seria uma resposta tola se a entrevista fosse para uma posição relacionada à matemática. ;-)


7
Você tem sorte que pediram 32 int, se era 64 bits a entrevista nunca vai continuar depois de executar os testes ;-)
alex2k8

De fato, se eu chegasse a um ponto de realmente escrever esse teste e executá-lo durante uma entrevista. ;-) Meu argumento: eu tentaria não chegar a esse ponto em uma entrevista. A programação é mais "uma maneira de pensar" do que "como ele escreve linhas de código" na minha opinião.
peSHIr

7
Não siga este conselho em uma entrevista real. O entrevistador espera que você realmente responda à pergunta. Questionar a relevância da pergunta não comprará nada, mas pode incomodar o entrevistador. Criar um teste trivial não aproxima nem um pouco a resposta e você não pode executá-lo na entrevista. Se você receber informações extras (32 bits), tente descobrir como isso pode ser útil.
Stefan Haustein

Um entrevistador que fica irritado quando solicito mais informações (embora possivelmente questione a relevância de sua pergunta no processo) não é um entrevistador para o qual eu necessariamente quero trabalhar. Então, continuarei fazendo perguntas como essas em entrevistas. Se eles não gostarem, provavelmente terminarei a entrevista para parar de desperdiçar nosso tempo ainda mais. Não goste da ideia de que "eu estava apenas seguindo ordens". Você..?
peSHIr 16/08/13

16

Gostaria de mudar os 2 bits mais significativos.

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

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

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

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

Como você pode ver, é apenas uma adição, deixando de fora a parte transportada.

Como cheguei à resposta? Meu primeiro pensamento foi apenas uma necessidade de simetria. 4 voltas para voltar onde eu comecei. No começo eu pensei que era o código Gray de 2 bits. Então eu pensei que realmente o binário padrão é suficiente.


O problema dessa abordagem é que ela não funciona com os números negativos de dois elogios (que é o que toda CPU moderna usa). Foi por isso que apaguei minha resposta idêntica.
Tamas Czinege 23/11/2009

A pergunta especificava números inteiros assinados de 32 bits. Esta solução não funciona para representações de complemento de dois ou complemento de um dos números inteiros assinados de 32 bits. Ele funcionará apenas para representações de sinal e magnitude, que são muito incomuns em computadores modernos (exceto números de ponto flutuante).
Jeffrey L Whitledge

1
@DrJokepu - Uau, depois de seis meses - jinx!
Jeffrey L Whitledge

Você não precisa apenas converter os números em representação de sinal e magnitude dentro da função, executar a transformação e converter de volta para qualquer que seja a representação inteira nativa antes de devolvê-la?
Bill Michell

Eu gosto que você basicamente implementado números complexos, introduzindo um pouco imaginário :)
jabirali

16

Aqui está uma solução inspirada no requisito ou afirma que números complexos não podem ser usados ​​para resolver esse problema.

Multiplicar pela raiz quadrada de -1 é uma ideia que parece falhar apenas porque -1 não tem uma raiz quadrada sobre os números inteiros. Mas brincar com um programa como o mathematica fornece, por exemplo, a equação

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

e isso é quase tão bom quanto ter uma raiz quadrada de -1. O resultado da função precisa ser um número inteiro assinado. Portanto, vou usar uma operação de módulo modificado mods (x, n) que retorna o número inteiro y congruente para x módulo n mais próximo de 0. Apenas muito poucas linguagens de programação possuem uma operação de módulo, mas ela pode ser facilmente definida . Por exemplo, em python é:

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

Usando a equação acima, o problema agora pode ser resolvido como

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

Isso é satisfatório f(f(x)) = -xpara todos os números inteiros no intervalo . Os resultados de também estão nessa faixa, mas é claro que a computação precisaria de números inteiros de 64 bits.[-231-2, 231-2]f(x)


13

C # para um intervalo de 2 ^ 32 - 1 números, todos os números int32, exceto (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

impressões:

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

Isso também não funciona para f (0), que é 1073741824. f (1073741824) = 0. f (f (1073741824)) = 1073741824
Dinah

Geralmente você pode provar que para um dois do tipo inteiro complemento de qualquer tamanho pouco, a função tem de não trabalho há pelo menos dois valores de entrada.
slacker

12

Essencialmente, a função precisa dividir o intervalo disponível em ciclos de tamanho 4, com -n no final oposto do ciclo de n. No entanto, 0 deve fazer parte de um ciclo de tamanho 1, porque caso contrário0->x->0->x != -x . Devido ao fato de 0 estar sozinho, deve haver 3 outros valores em nosso intervalo (cujo tamanho é um múltiplo de 4) que não estão em um ciclo adequado com 4 elementos.

Eu escolhi estes valores estranhos extra para ser MIN_INT, MAX_INTe MIN_INT+1. Além disso, MIN_INT+1irá mapear MAX_INTcorretamente, mas ficará preso lá e não mapeará de volta. Eu acho que esse é o melhor compromisso, porque ele tem a propriedade legal de apenas os valores extremos não funcionarem corretamente. Além disso, significa que funcionaria para todos os 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

Ninguém disse que tinha que ser apátrida.

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

Trapaça, mas não tanto quanto muitos exemplos. Ainda mais mal seria espiar a pilha para ver se o endereço do chamador é & f, mas isso será mais portátil (embora não seja seguro para threads ... a versão segura para threads usaria TLS). Ainda mais mal:

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

Obviamente, nenhuma dessas funciona muito bem para o caso de MIN_INT32, mas há muito pouco que você pode fazer sobre isso, a menos que tenha permissão para retornar um tipo mais amplo.


você pode 'atualizá-lo' para perguntar sobre o endereço (sim, você deve obtê-lo por ref \ como um ponteiro) - em C, por exemplo: int f (int & n) {static int * addr = & n; if (addr == & n) {return -n; } retornar n; }
IUnknownPointer

11

Eu poderia imaginar que usar o 31º bit como um bit imaginário ( i ) seria uma abordagem que suportaria metade da faixa total.


Isso seria mais complexo, mas não mais eficaz do que a melhor resposta atual
1800 INFORMATION

1
@ 1800 INFORMAÇÃO: Por outro lado, o domínio [-2 ^ 30 + 1, 2 ^ 30-1] é contíguo, o que é mais atraente do ponto de vista matemático.
219 Jochen Walter

10

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

10

O problema declara "números inteiros assinados de 32 bits", mas não especifica se são dois ou dois complementos .

Se você usar um complemento-ones, todos os valores de 2 ^ 32 ocorrerão em ciclos de comprimento quatro - você não precisa de um caso especial para zero e também não precisa de condicionais.

Em C:

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

Isso funciona por

  1. Trocando os blocos alto e baixo de 16 bits
  2. Invertendo um dos blocos

Após duas passagens, temos o inverso bit a bit do valor original. Que na representação de um complemento é equivalente a negação.

Exemplos:

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
E quanto à ordem de bytes em diferentes arquiteturas?
25410 Steven

1
Toda a aritmética é de 32 bits. Como não manipulo bytes individuais, a ordem dos bytes não o afetará.
finnw

Isso parece bem próximo. Você pode assumir que a entrada é de 2 complementos. Então você converte para a representação do bit de sinal. Agora, dependendo do último bit, você vira o primeiro e o último ou apenas o último. Basicamente, você nega apenas números pares e alterna pares / ímpares o tempo todo. Então você volta de ímpar para ímpar e par até mesmo após 2 chamadas. No final, você converte novamente em 2 complementos. Postou o código para isso em algum lugar abaixo.
Stefan Haustein

9

: D

boolean inner = true;

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

5
Você também pode obter uma discussão sobre por que as variáveis ​​globais são ruins se elas não o expulsam da entrevista!
palswim


7

Eu gostaria de compartilhar meu ponto de vista sobre este problema interessante como matemático. Eu acho que tenho a solução mais eficiente.

Se bem me lembro, você nega um número inteiro de 32 bits assinado, apenas lançando o primeiro bit. Por exemplo, se n = 1001 1101 1110 1011 1110 0000 1110 1010, então -n = 0001 1101 1110 1011 1110 0000 1110 1010.

Então, como definimos uma função f que recebe um número inteiro de 32 bits assinado e retorna outro número inteiro de 32 bits assinado com a propriedade de que tomar f duas vezes é o mesmo que inverter o primeiro bit?

Deixe-me reformular a pergunta sem mencionar conceitos aritméticos como números inteiros.

Como definimos uma função f que pega uma sequência de zeros e uns de comprimento 32 e retorna uma sequência de zeros e uns do mesmo comprimento, com a propriedade de que tomar f duas vezes é o mesmo que inverter o primeiro bit?

Observação: se você puder responder à pergunta acima para casos de 32 bits, também poderá responder para casos de 64 bits, casos de 100 bits, etc. Basta aplicar f nos primeiros 32 bits.

Agora, se você puder responder à pergunta no caso de 2 bits, Voila!

E sim, acontece que alterar os 2 primeiros bits é suficiente.

Aqui está o pseudo-có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.

Observação: A etapa 2 e a etapa 3 juntas podem ser exibidas como (a, b) -> (-b, a). Parece familiar? Isso deve lembrá-lo da rotação de 90 graus do plano e da multiplicação pela raiz quadrada de -1.

Se eu apresentasse o pseudo-código sozinho, sem o longo prelúdio, pareceria um coelho, queria explicar como consegui a solução.


6
Sim, é um problema interessante. Você conhece sua matemática. Mas este é um problema de ciência da computação. Então você precisa estudar computadores. A representação em magnitude de sinal é permitida, mas saiu de moda há cerca de 60 anos. O complemento de 2 é mais popular.
o Windows programador

5
Aqui está o que sua função faz com os dois bits quando aplicada duas vezes: (a, b) -> (-b, a) -> (-a, -b). Mas, estamos tentando chegar a (-a, b), não (-a, -b).
buti-oxa

@ buti-oxa, você está certo. A operação de dois bits deve ser como: 00 -> 01 -> 10 -> 11 -> 00. Mas, em seguida, meu algoritmo assume uma representação de magnitude de sinal que é impopular agora, como disse o programador do Windows, então acho que meu algoritmo é de pouca utilidade .
Yoo

Então ele não pode simplesmente dar os passos duas vezes em vez de uma vez?
Nosredna

4
O buti-oxa está totalmente certo: a função nem vira o primeiro bit depois de duas invocações, vira os dois primeiros bits. Virar todos os bits está mais próximo do que o complemento do 2 faz, mas não é exatamente correto.
redtuna
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.