Ir al contenido principal

Divide si todos son múltiplos


Definir la función

divideSiTodosMultiplos :: Integral a => a -> [a] -> Maybe [a]

tal que (divideSiTodosMultiplos x ys) es justo la lista de los cocientes de los elementos de ys entre x si todos son múltiplos de de número no nulo x y Nothing en caso contrario. Por ejemplo,

divideSiTodosMultiplos 2 [6,10,4]  ==  Just [3,5,2]
divideSiTodosMultiplos 2 [6,10,5]  ==  Nothing

Soluciones

import Data.Maybe (isNothing, fromJust)
import Test.Hspec (Spec, describe, hspec, it, shouldBe)
import Test.QuickCheck

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

divideSiTodosMultiplos1 :: Integral a => a -> [a] -> Maybe [a]
divideSiTodosMultiplos1 x ys
  | todosMultiplos x ys = Just [y `div` x | y <- ys]
  | otherwise           = Nothing

-- (todosMultiplos x ys) se verifica si todos los elementos de ys son
-- múltiplos de x. Por ejemplo,
--    todosMultiplos 2 [6,10,4]  ==  True
--    todosMultiplos 2 [6,10,5]  ==  False
todosMultiplos :: Integral a => a -> [a] -> Bool
todosMultiplos x ys =
  and [y `mod` x == 0 | y <- ys]

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

divideSiTodosMultiplos2 :: Integral a => a -> [a] -> Maybe [a]
divideSiTodosMultiplos2 x ys
  | todosMultiplos2 x ys = Just [y `div` x | y <- ys]
  | otherwise            = Nothing

todosMultiplos2 :: Integral a => a -> [a] -> Bool
todosMultiplos2 x =
  all (\y -> y `mod` x == 0)

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

divideSiTodosMultiplos3 :: Integral a => a -> [a] -> Maybe [a]
divideSiTodosMultiplos3 0 _  = Nothing
divideSiTodosMultiplos3 _ [] = Just []
divideSiTodosMultiplos3 x (y:ys)
  | y `mod` x /= 0  = Nothing
  | isNothing aux   = Nothing
  | otherwise       = Just ((y `div` x) : fromJust aux)
  where aux = divideSiTodosMultiplos3 x ys

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

divideSiTodosMultiplos4 :: Integral a => a -> [a] -> Maybe [a]
divideSiTodosMultiplos4 0 _  = Nothing
divideSiTodosMultiplos4 _ [] = Just []
divideSiTodosMultiplos4 x (y:ys)
  | y `mod` x /= 0        = Nothing
  | otherwise = case divideSiTodosMultiplos4 x ys of
                  Nothing   -> Nothing
                  Just qs   -> Just (y `div` x : qs)

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

divideSiTodosMultiplos5 :: Integral a => a -> [a] -> Maybe [a]
divideSiTodosMultiplos5 x ys =
  sequence (map (x `divide`) ys)

-- (divide x y) es justo el cociente de x entre y, si x es divisible por
-- y y Nothing, en caso contrario. Por ejemplo,
divide :: Integral a => a -> a -> Maybe a
divide x y
  | y `mod` x == 0 = Just (y `div` x)
  | otherwise      = Nothing

-- Nota. En la solución anterior se usa la función
--    sequence :: Monad m => [m a] -> m [a]
-- tal que (sequence xs) es la mónada obtenida evaluando cada una de las
-- de xs de izquierda a derecha. Por ejemplo,
--    sequence [Just 2, Just 5]   ==  Just [2,5]
--    sequence [Just 2, Nothing]  ==  Nothing
--    sequence [[2,4],[5,7]]      ==  [[2,5],[2,7],[4,5],[4,7]]
--    sequence [[2,4],[5,7],[6]]  ==  [[2,5,6],[2,7,6],[4,5,6],[4,7,6]]
--    sequence [[2,4],[5,7],[]]   ==  []

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

divideSiTodosMultiplos6 :: Integral a => a -> [a] -> Maybe [a]
divideSiTodosMultiplos6 x =
  sequence . map (x `divide`)

-- 7ª solución
-- ===========

divideSiTodosMultiplos7 :: Integral a => a -> [a] -> Maybe [a]
divideSiTodosMultiplos7 x =
  mapM (x `divide`)

-- Nota. En la solución anterior se usa la función mapM ya que
--    mapM f
-- es equivalente a
--    sequence . map f
-- Por ejemplo,
--    λ> mapM (\n -> if even n then Just (2*n) else Nothing) [4,6,10]
--    Just [8,12,20]
--    λ> mapM (\n -> if even n then Just (2*n) else Nothing) [4,6,11]
--    Nothing

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

verifica :: IO ()
verifica = hspec spec

specG :: (Int -> [Int] -> Maybe [Int]) -> Spec
specG divideSiTodosMultiplos = do
  it "e1" $
    divideSiTodosMultiplos 2 [6,10,4]  `shouldBe`  Just [3,5,2]
  it "e2" $
    divideSiTodosMultiplos 2 [6,10,5]  `shouldBe`  Nothing

spec :: Spec
spec = do
  describe "def. 1" $ specG divideSiTodosMultiplos1
  describe "def. 2" $ specG divideSiTodosMultiplos2
  describe "def. 3" $ specG divideSiTodosMultiplos3
  describe "def. 4" $ specG divideSiTodosMultiplos4
  describe "def. 5" $ specG divideSiTodosMultiplos5
  describe "def. 6" $ specG divideSiTodosMultiplos6
  describe "def. 7" $ specG divideSiTodosMultiplos7

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

-- Equivalencia de las definiciones
-- ================================

-- La propiedad es
prop_divideSiTodosMultiplos :: NonZero Int -> [Int] -> Bool
prop_divideSiTodosMultiplos x' ys =
  all (== divideSiTodosMultiplos1 x ys)
      [divideSiTodosMultiplos2 x ys,
       divideSiTodosMultiplos3 x ys,
       divideSiTodosMultiplos4 x ys,
       divideSiTodosMultiplos5 x ys,
       divideSiTodosMultiplos6 x ys]
  where x = getNonZero x'

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

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

-- La comparación es
--    λ> last <$> (divideSiTodosMultiplos1 2 [2,4..10^6])
--    Just 500000
--    (0.40 secs, 284,605,936 bytes)
--    λ> last <$> (divideSiTodosMultiplos2 2 [2,4..10^6])
--    Just 500000
--    (0.32 secs, 212,605,792 bytes)
--    λ> last <$> (divideSiTodosMultiplos3 2 [2,4..10^6])
--    Just 500000
--    (1.30 secs, 595,295,824 bytes)
--    λ> last <$> (divideSiTodosMultiplos4 2 [2,4..10^6])
--    Just 500000
--    (0.98 secs, 462,908,920 bytes)
--    λ> last <$> (divideSiTodosMultiplos5 2 [2,4..10^6])
--    Just 500000
--    (0.73 secs, 405,609,672 bytes)
--    λ> last <$> (divideSiTodosMultiplos6 2 [2,4..10^6])
--    Just 500000
--    (0.81 secs, 405,609,800 bytes)
--    λ> last <$> (divideSiTodosMultiplos7 2 [2,4..10^6])
--    Just 500000
--    (0.72 secs, 377,609,624 bytes)