Cómo saber si / cuándo puede aplicar la manipulación de bits para resolver un problema

Además de usar el desplazamiento de bits y cosas así para la aritmética como sugiere Lucas Sampaio, la manipulación de bits es muy eficiente para almacenar “matrices” de propiedades booleanas. Si desea poder comprobar rápidamente si las propiedades de un objeto coinciden con algún conjunto de propiedades, puede utilizar el enmascaramiento de bits para ello. Vea este ejemplo trivial, escrito en Python:

# banderas de vehículos
VFLAG_FAST = 1
VFLAG_LUXURY = 2
VFLAG_CAR = 4
VFLAG_BIKE = 8
VFLAG_SAFE = 16
VFLAG_TRANSPORT = 32
VFLAG_ECONOMY = 64
VFLAG_ELECTRIC = 128

Vehículo de clase:
def __init __ (self, name, flags):
self.name = name
self.flags = flags

VEHÍCULOS = [
Vehículo (“Tesla”, VFLAG_FAST | VFLAG_CAR | VFLAG_ELECTRIC | VFLAG_SAFE),
Vehículo (“Yamaha”, VFLAG_FAST | VFLAG_BIKE),
Vehículo (“WV Transporter”, VFLAG_CAR | VFLAG_TRANSPORT),
Vehículo (“Ciclomotor”, VFLAG_BIKE | VFLAG_TRANSPORT | VFLAG_ECONOMY)
]

banderas = VFLAG_TRANSPORT | VFLAG_ECONOMY
para vehículo en filtro (lambda v: v.flags & flags == flags, VEHICLES):
imprimir vehículo.nombre
# “Ciclomotor”

Cada VFLAG_ * es una potencia de dos enteros, comenzando en 1, porque todos corresponden a un solo bit que se establece. Al crear un nuevo vehículo, usamos la manipulación de bits para establecer las banderas en ese vehículo, con el | operador siendo un bit a bit o . Eso significa que el número de salida tendrá unos en todos los lugares donde cualquiera de los números de entrada tiene unos:

a = 0b0001
b = 0b0100
papelera de impresión (a | b)
>>> 0b0101

Al buscar un vehículo que coincida con algunos criterios, aplicamos el operador &, que es el bit y. El resultado son unos en todos los lugares donde ambas entradas tienen unos:

a = 0b0001
b = 0b0111
Papelera de impresión (a & b)
>>> 0b0001

Esto permite comparaciones excepcionalmente rápidas, especialmente al escribir código en C o C ++ o lo que sea, ya que los operadores lógicos bit a bit son una sola instrucción y el almacenamiento de las banderas es muy compacto (un solo entero). La desventaja es que está limitado por el tamaño de un número entero en su sistema, generalmente de 32 o 64 bits. Eso significa que sus vehículos no pueden tener más de 32 (o 64) tipos diferentes de propiedades. Dependiendo de lo que esté haciendo, esto puede o no ser un problema.

Una instancia común del uso de la manipulación de bits es en los problemas en los que tenemos que hacer un seguimiento y acceder a los subconjuntos de un conjunto dado, generalmente para un algoritmo de seguimiento (enumerando todos los subconjuntos posibles) o de programación dinámica (visitando algunos subconjuntos como estados dp) . Esto generalmente ocurre en problemas donde N es muy pequeño, porque un conjunto con N elementos tiene 2 ^ N subconjuntos, lo que hace que sea muy poco eficiente buscar cada subconjunto.

Los números binarios son excelentes para almacenar subconjuntos, ya que nos permiten verificar si algún valor pertenece a nuestro subconjunto o no, y también agregar un nuevo valor al subconjunto en complejidad O (1). Por ejemplo, supongamos que tenemos S = {2, 5, 7, 4, 9} . Podemos representar subconjuntos de S usando una máscara de bits de modo que esta máscara tenga el valor 1 en las posiciones donde el valor está en nuestro subconjunto, y 0 de lo contrario. Por ejemplo, el subconjunto S ‘= {2,7,9} puede representarse por La máscara binaria 10101.

Ahora, podemos verificar si alguna posición pertenece al subconjunto usando el subconjunto de operación & 1 << posición . Esta operación devuelve un número distinto de cero si el valor en la posición dada pertenece a nuestro subconjunto. Por ejemplo, podemos verificar si el valor “7” pertenece al subconjunto usando el subconjunto de operación & 1 << 2 , porque 2 es la posición del valor “7” en S (recuerde que la primera posición es 0, no 1 )

También podemos actualizar nuestra máscara, haciendo la operación subconjunto | = 1 << posición . Esto agrega el valor en la posición dada a nuestro subconjunto. En el ejemplo anterior, si queremos agregar el valor “5” a nuestro subconjunto, podemos aplicar la operación subconjunto | = 1 << 1 porque 1 es la posición del valor “5” en S.

PS: 1 << N es una forma de calcular el valor 2 ^ N de manera muy eficiente, utilizando el operador de desplazamiento de bit "<<"

Siempre que use una potencia de dos (2 ^ x) como variable constante, puede usar la manipulación de bits. Sin embargo, me sorprendería si los compiladores modernos no hicieran esto automáticamente.

Cuando alguna variable no se divide por cero. Dividir por cero es ilegal.