web-dev-qa-db-fra.com

A + B sans opérateurs arithmétiques, Python vs C++

J'essayais de résoudre une vieille question:

Ecrivez une fonction qui ajoute deux nombres [entiers] A et B. Vous ne devez pas utiliser + ni d'opérateurs arithmétiques.

La meilleure solution est la suivante, citée dans " LintCode-A + B Problem ":

Pour a + b dans n'importe quelle base, nous pouvons traiter le plus comme deux parties: 1. a + b sans retenue; 2. le report généré par a + b. Le a + b est alors égal à la partie 1 plus la partie 2. Si part1 + partie2 génère plus de report, nous pouvons alors répéter cette procédure jusqu'à ce qu'il n'y ait plus de report.

Je peux comprendre cet algorithme et tout semble bon, je l’ai donc testé sur lintcode avec le code collé ci-dessous.

class Solution:
    """
    @param a: The first integer
    @param b: The second integer
    @return:  The sum of a and b
    """
    def aplusb(self, a, b):

        while b != 0:
            carry = a & b
            a = a ^ b
            b = carry << 1

        return a

Mais étonnamment, cela m'a donné l'erreur Time Limit Exceeded dans le scénario de test [100, -100]. Alors je l'ai couru localement et imprimer a, b pour chaque boucle:

(-8, 8)
(-16, 16)
(-32, 32)
(-64, 64)
(-128, 128)
(-256, 256)
(-512, 512)
(-1024, 1024)
(-2048, 2048)
(-4096, 4096)
(-8192, 8192)
(-16384, 16384)
(-32768, 32768)
(-65536, 65536)
(-131072, 131072)
...

Le calcul est correct, je pense donc que cet algorithme ne fonctionne pas pour une telle entrée, mais lorsque j'ai écrit le même algorithme en C++, cela fonctionne:

class Solution {
public:
    int aplusb(int a, int b) {
        while (b!=0){
            int carry = a & b;
            a = a^b; 
            b = carry << 1;
        }
        return a;
    }
};

Je ne sais pas ce qui devrait être demandé exactement, les questions sont essentiellement:

  1. Pourquoi C++ donne-t-il le bon résultat 0, contrairement à Python?
  2. Si j'utilise Python, comment puis-je modifier cet algorithme pour le faire fonctionner?
30
laike9m

La représentation binaire, complément à 2 de -4 est

...11100

Oui, je veux vraiment dire infiniment beaucoup de 1 à gauche; c'est un chiffre binaire qui se répète. Techniquement, 4 est également un chiffre qui se répète:

...00100

il ne fait que répéter les 0 à gauche.

Votre problème d'addition est

   ...11100
+  ...00100
--------------------
   ...00000

Les opérateurs ^, << et & n'ont aucune difficulté à calculer avec une infinité de chiffres binaires, mais le problème est qu’il existe un nombre infini de reports et que vous les calculez un chiffre à la fois . Cela ne finira jamais.

Ainsi, vous devez reconnaître quand cet algorithme va rester bloqué dans cette situation et faire autre chose pour en tenir compte.


Vous ne rencontrez pas ce problème en C/C++, car, par exemple, si intest 32 bits, tous les chiffres, à l’exception des 31 derniers chiffres situés à la droite, sont réduits en un seul bit, le reste immediatement.

Cependant, techniquement parlant, le fait de déplacer à gauche un intcorrespond à la valeur en tant qu’entier, plutôt qu’en tant que motif binaire, de sorte que vous appelez comportement non défini si les deux bits les plus significatifs carrysont toujours différent, car carry << 1 produirait un débordement).

25
Hurkyl

Le problème sont les nombres négatifs, ou comment ils sont représentés. En Python, les nombres entiers ont une précision arbitraire, alors que les entiers C++ ont 32 bits ou 64 bits. Donc, en Python, vous devez gérer les nombres négatifs, par exemple. soustraction, séparément ou limiter le nombre de bits à la main.

10
Daniel

Suite à la grande explication de @Hurkyl, j’ai exploré l’algorithme pour a=4 et b=-4 en utilisant le fait que python implémente une représentation complémentaire à deux infinis:

Step 0:

a = ...(0)...000100
b = ...(1)...111100

carry = a & b = ...(0)...000100
a = a ^ b = ...(1)...111000
b = carry << 1 = ...(0)...001000

Step 1:

a = ...(1)...111000
b = ...(0)...001000

carry = a & b = ...(0)...001000
a = a ^ b = ...(1)...110000
b = carry << 1 = ...(0)...010000

Step 2:

a = ...(1)...110000
b = ...(0)...010000

carry = a & b = ...(0)...010000
a = a ^ b = ...(1)...100000
b = carry << 1 = ...(0)...100000

Il est clair qu’il faut une coupure efficace pour émuler quelque chose comme un entier complémentaire de 32 bits signé. Une fois que le bit de retenue bouillonne au-delà du bit le plus élevé, l'algorithme doit être arrêté. Ce qui suit semble fonctionner:

MAX_BIT = 2**32
MAX_BIT_COMPLIMENT = -2**32

def aplusb(a, b):

    while b != 0:
        if b == MAX_BIT:
            return a ^ MAX_BIT_COMPLIMENT
        carry = a & b
        a = a ^ b
        b = carry << 1

    return a

Résultats:

>>> aplusb(100,-100)
0
>>> aplusb(100,-99)
1
>>> aplusb(97,-99)
-2
>>> aplusb(1000,-500)
500
>>> aplusb(-1000,8000)
7000
7
Eric Appelt

Si les opérations mathématiques binaires 1 bit (^) sont interdites, optez pour unaire!

from itertools import chain

def unary(x):
    "Unary representation of x"
    return ''.join(['x' for _ in range(0,x)])

def uplus(x, y):
    "Unary sum of x and y"
    return [c for c in chain(x,y)]

def plus(i, j):
    "Return sum calculated using unary math"
    return len(uplus(unary(i), unary(j)))
3
guest

En effet, python n’utilise normalement pas d’entier signé 32 bits.

Voir: ctypes.c_int32

Solution acceptée:

class Solution:
"""
@param a: The first integer
@param b: The second integer
@return:  The sum of a and b
"""
def aplusb(self, a, b):
    import ctypes
    a = ctypes.c_int32(a).value
    a = ctypes.c_int32(a).value
    while b != 0:
        carry = ctypes.c_int32(a & b).value
        a = ctypes.c_int32(a ^ b).value
        b = ctypes.c_int32(carry << 1).value

    return a
1
Howard Liang

Ma solution:

def foo(a, b):
"""iterate through a and b, count iteration via a list, check len"""
    x = []
    for i in range(a):
            x.append(a)
    for i in range(b):
            x.append(b)
    print len(x)

Comme déjà indiqué, bitwise est préférable.

0
James