Ya sabes cómo implementar la multiplicación como suma repetida y la exponenciación como multiplicación repetida, así que en lugar de eso, veamos si podemos hacer algo más inteligente.
Abordaremos la multiplicación primero.
Queremos calcular b * n. Si n es cero, el resultado también es cero. De lo contrario, si n es par, entonces b * n = b * 2 * k para algunos k; si es impar, entonces b * n = b * (2 * k + 1) = b * 2 * k + b. En ambos casos, 2 * b * k se puede calcular como (b * k) + (b * k), donde k es menor que n. Es fácil decidir si n es par o impar y calcular k, cuando está escrito en binario. Estas observaciones nos dan una función recursiva para calcular b * n.
- ¿Qué es un algoritmo de aproximación?
- Dado un conjunto de datos sin clasificar de tamaño n, si usa la selección de clasificación para ordenar los datos, ¿cuántas búsquedas binarias necesitaría realizar en el conjunto de datos sin clasificar para "recomprar" el costo que conlleva la clasificación de sus datos si n = (2 ^ 4)?
- ¿Qué trabajos recomienda como introducción a la teoría de la complejidad y la teoría de la votación?
- ¿Cuáles son algunos temas de doctorado en ciencias de la computación sin matemáticas?
- ¿Cuál es el problema P vs. NP y por qué es tan importante?
La función que describimos anteriormente se puede escribir en Haskell de la siguiente manera:
import Data.Bits -- 'nat' is a number type with a binary representation
mul :: (Num nat, Bits nat) => nat -> nat -> nat
b `mul` 0 = 0
b `mul` n = bk + bk + if testBit n 0 then b else 0
where
k = n `shiftR` 1
bk = b `mul` k
import Data.Bits -- 'nat' is a number type with a binary representation
mul :: (Num nat, Bits nat) => nat -> nat -> nat
b `mul` 0 = 0
b `mul` n = bk + bk + if testBit n 0 then b else 0
where
k = n `shiftR` 1
bk = b `mul` k
Tenga en cuenta que este algoritmo es asintóticamente más rápido que la adición repetida ingenua. Por ejemplo, para calcular 1024 * 1024, el algoritmo ingenuo necesita realizar 1024 adiciones, mientras que este solo necesita 2 * log_2 (1024) = 20.
Ahora a la exponenciación.
Queremos calcular b ^ n. Si n es cero, el resultado es uno. De lo contrario, si n es par, entonces b ^ n = b ^ (2 * k) = (b ^ k) ^ 2 para algunos k; si es impar, entonces b ^ n = b ^ (2 * k + 1) = (b ^ k) ^ 2 * b. En ambos casos, (b ^ k) ^ 2 se puede calcular como (b ^ k) * (b ^ k), donde k es menor que n. Es fácil decidir si n es par o impar y calcular k, cuando está escrito en binario. Estas observaciones nos dan una función recursiva para calcular b ^ n:
pow :: (Num nat, Bits nat) => nat -> nat -> nat
b `pow` 0 = 1
b `pow` n = bk `mul` bk `mul` if testBit n 0 then b else 1
where
k = n `shiftR` 1
bk = b `pow` k
Este también es más rápido que su contraparte ingenua. Para tener una idea de cuánto más rápido es, intente calcular 3 ^ 65535 usando ambos algoritmos.
Espera, recién estamos comenzando.
Tenga en cuenta que hay un patrón que es común tanto a los argumentos como a los programas que utilizamos para b * ny b ^ n. En nombre de DRY, factoricemos ese patrón en una función y reutilicémoslo para implementar tanto mul
como pow
.
import Data.Bits
mul :: (Num nat, Bits nat) => nat -> nat -> nat
mul = rep (+) 0
pow :: (Num nat, Bits nat) => nat -> nat -> nat
pow = rep mul 1
representante :: (Num nat, Bits nat)
=> (nat -> nat -> nat) – la operación para repetir
-> nat – el elemento neutral
-> (nat -> nat -> nat) – la operación resultante
rep op zb 0 = z
rep op zbn = bk `op` bk` op` si testBit n 0 entonces b else z
dónde
k = n `shiftR` 1
bk = rep op zbk