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

1. Soluciones en Haskell

module Reconocimiento_de_grandes_potencias_de_2 where

import Data.Bits ((.&.))
import Data.Numbers.Primes (primeFactors)
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
-- ===========

-- 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:
--    4 .&. 3 ==   [1,0,0] .&.   [1,1] == 0
--    8 .&. 7 == [1,0,0,0] .&. [1,1,1] = 0

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

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

-- La verificación es
--    λ> verifica
--    24 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
      ]

-- 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.51 secs, 5,730,072,544 bytes)
--    λ> esPotenciaDeDos2 (2^(3*10^5))
--    True
--    (3.12 secs, 5,755,639,952 bytes)
--    λ> esPotenciaDeDos3 (2^(3*10^5))
--    True
--    (2.92 secs, 5,758,872,040 bytes)
--    λ> esPotenciaDeDos4 (2^(3*10^5))
--    True
--    (0.03 secs, 715,152 bytes)

2. Soluciones en Python

from itertools import count, dropwhile, islice, repeat, starmap
from sys import setrecursionlimit
from timeit import Timer, default_timer
from typing import Iterator

from hypothesis import given
from hypothesis import strategies as st
from sympy import factorint

setrecursionlimit(10**6)

# 1ª solución
# ===========

def esPotenciaDeDos1(n: int) -> bool:
    return n == 1 or (n % 2 == 0 and esPotenciaDeDos1(n // 2))

# 2ª solución
# ===========

# potenciasDeDos() es la lista de las potencias de dos. Por ejemplo,
#    >>> list(islice(potenciasDeDos(), 10))
#    [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
def potenciasDeDos1() -> Iterator[int]:
    n = 1
    while True:
        yield n
        n *= 2

def esPotenciaDeDos2(n: int) -> bool:
    return n == next(dropwhile(lambda x: x < n, potenciasDeDos1()))

# 3ª solución
# ===========

def potenciasDeDos2() -> Iterator[int]:
    return starmap(pow, zip(repeat(2), count()))

def esPotenciaDeDos3(n: int) -> bool:
    return n == next(dropwhile(lambda x: x < n, potenciasDeDos2()))

# 4ª solución
# ===========

def esPotenciaDeDos4(n: int) -> bool:
    return all(x == 2 for x in factorint(n))

# 5ª solución
# ===========

# Usando la función &. 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:
#    4 & 3 ==   [1,0,0] &   [1,1] == 0
#    8 & 7 == [1,0,0,0] & [1,1,1] == 0

def esPotenciaDeDos5(n: int) -> bool:
    return n & (n - 1) == 0

# Verificación
# ============

def test_esPotenciaDeDos() -> None:
    for esPotenciaDeDos in [esPotenciaDeDos1,
                            esPotenciaDeDos2,
                            esPotenciaDeDos3,
                            esPotenciaDeDos4,
                            esPotenciaDeDos5]:
        assert list(islice(potenciasDeDos1(), 10)) ==\
            [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
        assert list(islice(potenciasDeDos2(), 10)) ==\
            [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
        assert esPotenciaDeDos(1)
        assert esPotenciaDeDos(2)
        assert not esPotenciaDeDos(6)
        assert esPotenciaDeDos(8)
        assert esPotenciaDeDos(1024)
        assert not esPotenciaDeDos(1026)
    print("Verificado")

# La verificación es
#    >>> test_esPotenciaDeDos()
#    Verificado

# Comprobación de equivalencia
# ============================

# La propiedad es
@given(st.integers(min_value=1, max_value=1000))
def test_esPotenciaDeDos_equiv(n: int) -> None:
    r = esPotenciaDeDos1(n)
    assert esPotenciaDeDos2(n) == r
    assert esPotenciaDeDos3(n) == r
    assert esPotenciaDeDos4(n) == r
    assert esPotenciaDeDos5(n) == r

# La comprobación es
#    >>> test_esPotenciaDeDos_equiv()
#    >>>

# Comparación de eficiencia
# =========================

def tiempo(e: str) -> None:
    """Tiempo (en segundos) de evaluar la expresión e."""
    t = Timer(e, "", default_timer, globals()).timeit(1)
    print(f"{t:0.2f} segundos")

# La comparación es
#    >>> tiempo('esPotenciaDeDos1(2**(2*10**4))')
#    0.19 segundos
#    >>> tiempo('esPotenciaDeDos2(2**(2*10**4))')
#    0.03 segundos
#    >>> tiempo('esPotenciaDeDos3(2**(2*10**4))')
#    0.25 segundos
#    >>> tiempo('esPotenciaDeDos4(2**(2*10**4))')
#    0.00 segundos
#    >>> tiempo('esPotenciaDeDos5(2**(2*10**4))')
#    0.00 segundos
#
#    >>> tiempo('esPotenciaDeDos2(2**(10**5))')
#    0.18 segundos
#    >>> tiempo('esPotenciaDeDos3(2**(10**5))')
#    8.65 segundos
#    >>> tiempo('esPotenciaDeDos4(2**(10**5))')
#    0.00 segundos
#    >>> tiempo('esPotenciaDeDos5(2**(10**5))')
#    0.00 segundos
#
#    >>> tiempo('esPotenciaDeDos2(2**(10**6))')
#    12.92 segundos
#    >>> tiempo('esPotenciaDeDos4(2**(10**6))')
#    0.01 segundos
#    >>> tiempo('esPotenciaDeDos5(2**(10**6))')
#    0.01 segundos
#
#    >>> tiempo('esPotenciaDeDos4(2**(10**9))')
#    4.51 segundos
#    >>> tiempo('esPotenciaDeDos5(2**(10**9))')
#    4.57 segundos