Ir al contenido principal

Reconocimiento de potencias de 2


Definir la función

esPotenciaDeDos :: Integer -> Bool

tal que (esPotenciaDeDos n) se verifica si n es una potencia de dos (suponiendo que n es mayor que 0). Por ejemplo.

esPotenciaDeDos    1        == True
esPotenciaDeDos    2        == True
esPotenciaDeDos    6        == False
esPotenciaDeDos    8        == True
esPotenciaDeDos 1024        == True
esPotenciaDeDos 1026        == False
esPotenciaDeDos (2^(10^8))  == True

Soluciones

import Data.Bits ((.&.), popCount)
import Data.Numbers.Primes (primeFactors)
import Math.NumberTheory.Logarithms (integerLog2)
import Test.Hspec (Spec, describe, hspec, it, shouldBe)
import Test.QuickCheck (Positive (Positive), quickCheck)

-- 1ª solución
-- ===========

esPotenciaDeDos1 :: Integer -> Bool
esPotenciaDeDos1 1 = True
esPotenciaDeDos1 n
  | even n    = esPotenciaDeDos1 (n `div` 2)
  | otherwise = False

-- 2ª solución
-- ===========

esPotenciaDeDos2 :: Integer -> Bool
esPotenciaDeDos2 n = n ==
  head (dropWhile (<n) potenciasDeDos)

-- potenciasDeDos es la lista de las potencias de dos. Por ejemplo,
--    take 10 potenciasDeDos  == [1,2,4,8,16,32,64,128,256,512]
potenciasDeDos :: [Integer]
potenciasDeDos = iterate (*2) 1

-- 3ª solución
-- ===========

esPotenciaDeDos3 :: Integer -> Bool
esPotenciaDeDos3 x = all (==2) (primeFactors x)

-- 4ª solución
-- ===========

esPotenciaDeDos4 :: Integer -> Bool
esPotenciaDeDos4 n = n == 2 ^ integerLog2 n

-- 5ª solución
-- ===========

-- Usando la función (.&.) de la librería Data.Bits. Dicha función
-- calcula el número correspondiente a la conjunción de las
-- representaciones binarias de sus argumentos. Por ejemplo,
--    6 .&. 3 == 2
-- ya que
--    la representación binaria de 6 es       [1,1,0]
--    la representación binaria de 3 es       [1,1]
--    la conjunción es                        [1,0]
--    la representación decimal de [1,0] es   2
--
-- Otros ejemplos:
--    5 .&. 3 ==   [1,0,0] .&.   [1,1] == 0
--    8 .&. 7 == [1,0,0,0] .&. [1,1,1] = 0
--
-- Usa el truco de que una potencia de 2 en binario tiene exactamente un
-- bit en 1, y al restarle 1, todos los bits se invierten. La operación
-- (.&.) entre ambos da 0. Por ejemplo:
--    8 = 1000₂, 7 = 0111₂ -> 1000 .&. 0111 = 0000
--    6 = 0110₂, 5 = 0101₂ -> 0110 .&. 0101 = 0100

esPotenciaDeDos5 :: Integer -> Bool
esPotenciaDeDos5 n = n .&. (n-1) == 0

-- 6ª solución
-- ===========

esPotenciaDeDos6 :: Integer -> Bool
esPotenciaDeDos6 n = popCount n == 1

-- Verificación
-- ============

verifica :: IO ()
verifica = hspec spec

specG :: (Integer -> Bool) -> Spec
specG esPotenciaDeDos = do
  it "e1" $
    esPotenciaDeDos    1 `shouldBe` True
  it "e2" $
    esPotenciaDeDos    2 `shouldBe` True
  it "e3" $
    esPotenciaDeDos    6 `shouldBe` False
  it "e4" $
    esPotenciaDeDos    8 `shouldBe` True
  it "e5" $
    esPotenciaDeDos 1024 `shouldBe` True
  it "e6" $
    esPotenciaDeDos 1026 `shouldBe` False

spec :: Spec
spec = do
  describe "def. 1" $ specG esPotenciaDeDos1
  describe "def. 2" $ specG esPotenciaDeDos2
  describe "def. 3" $ specG esPotenciaDeDos3
  describe "def. 4" $ specG esPotenciaDeDos4
  describe "def. 5" $ specG esPotenciaDeDos5
  describe "def. 6" $ specG esPotenciaDeDos6

-- La verificación es
--    λ> verifica
--    36 examples, 0 failures

-- Comprobación de equivalencia
-- ============================

-- La propiedad es
prop_esPotenciaDeDos :: Positive Integer -> Bool
prop_esPotenciaDeDos (Positive n) =
  all (== esPotenciaDeDos1 n)
      [ esPotenciaDeDos2 n
      , esPotenciaDeDos3 n
      , esPotenciaDeDos4 n
      , esPotenciaDeDos5 n
      , esPotenciaDeDos6 n
      ]

-- La comprobación es
--    λ> quickCheck prop_esPotenciaDeDos
--    +++ OK, passed 100 tests.

-- Comparación de eficiencia
-- =========================

-- La comparación es
--    λ> esPotenciaDeDos1 (2^(3*10^5))
--    True
--    (3.07 secs, 5,713,366,248 bytes)
--    λ> esPotenciaDeDos2 (2^(3*10^5))
--    True
--    (5.86 secs, 5,665,328,552 bytes)
--    λ> esPotenciaDeDos3 (2^(3*10^5))
--    True
--    (2.39 secs, 5,749,363,752 bytes)
--    λ> esPotenciaDeDos4 (2^(3*10^5))
--    True
--    (0.02 secs, 844,400 bytes)
--    λ> esPotenciaDeDos5 (2^(3*10^5))
--    True
--    (0.03 secs, 803,456 bytes)
--    λ> esPotenciaDeDos6 (2^(3*10^5))
--    True
--    (0.03 secs, 728,296 bytes)
--
--    λ> esPotenciaDeDos4 (2^(2*10^8))
--    True
--    (3.78 secs, 148,890,576 bytes)
--    λ> esPotenciaDeDos5 (2^(2*10^8))
--    True
--    (1.83 secs, 124,751,352 bytes)
--    λ> esPotenciaDeDos6 (2^(2*10^8))
--    True
--    (1.83 secs, 74,751,256 bytes)