Ir al contenido principal

Suma si todos los valores son justos


Definir la función

sumaSiTodosJustos :: (Num a, Eq a) => [Maybe a] -> Maybe a

tal que (sumaSiTodosJustos xs) es justo la suma de todos los elementos de xs si todos son justos (es decir, si Nothing no pertenece a xs) y Nothing en caso contrario. Por ejemplo,

sumaSiTodosJustos [Just 2, Just 5]           == Just 7
sumaSiTodosJustos [Just 2, Just 5, Nothing]  == Nothing

Soluciones

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

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

sumaSiTodosJustos1 :: (Num a, Eq a) => [Maybe a] -> Maybe a
sumaSiTodosJustos1 xs
  | todosJustos xs = Just (sum [x | (Just x) <- xs])
  | otherwise      = Nothing

-- (todosJustos xs) se verifica si todos los elementos de xs son justos
-- (es decir, si Nothing no pertenece a xs) y Nothing en caso
-- contrario. Por ejemplo,
--    todosJustos [Just 2, Just 5]           == True
--    todosJustos [Just 2, Just 5, Nothing]  == False

-- 1ª definición de todosJustos:
todosJustos1 :: Eq a => [Maybe a] -> Bool
todosJustos1 = notElem Nothing

-- 2ª definición de todosJustos:
todosJustos :: Eq a => [Maybe a] -> Bool
todosJustos = all isJust

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

sumaSiTodosJustos2 :: (Num a, Eq a) => [Maybe a] -> Maybe a
sumaSiTodosJustos2 xs
  | todosJustos xs = Just (sum [fromJust x | x <- xs])
  | otherwise      = Nothing

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

sumaSiTodosJustos3 :: (Num a, Eq a) => [Maybe a] -> Maybe a
sumaSiTodosJustos3 xs
  | todosJustos xs = Just (sum (map fromJust xs))
  | otherwise      = Nothing

-- 4ª solución

sumaSiTodosJustos4 :: (Num a, Eq a) => [Maybe a] -> Maybe a
sumaSiTodosJustos4 xs
  | todosJustos xs = Just (sum (catMaybes xs))
  | otherwise      = Nothing

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

sumaSiTodosJustos5 :: (Num a, Eq a) => [Maybe a] -> Maybe a
sumaSiTodosJustos5 xs = suma (sequence xs)
  where suma Nothing   = Nothing
        suma (Just ys) = Just (sum ys)

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

sumaSiTodosJustos6 :: (Num a, Eq a) => [Maybe a] -> Maybe a
sumaSiTodosJustos6 xs = fmap sum (sequence xs)

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

sumaSiTodosJustos7 :: (Num a, Eq a) => [Maybe a] -> Maybe a
sumaSiTodosJustos7 = fmap sum . sequence

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

verifica :: IO ()
verifica = hspec spec

specG :: ([Maybe Integer] -> Maybe Integer) -> Spec
specG sumaSiTodosJustos = do
  it "e1" $
    sumaSiTodosJustos [Just 2, Just 5]           `shouldBe` Just 7
  it "e2" $
    sumaSiTodosJustos [Just 2, Just 5, Nothing]  `shouldBe` Nothing

spec :: Spec
spec = do
  describe "def. 1" $ specG sumaSiTodosJustos1
  describe "def. 2" $ specG sumaSiTodosJustos2
  describe "def. 3" $ specG sumaSiTodosJustos3
  describe "def. 4" $ specG sumaSiTodosJustos4
  describe "def. 5" $ specG sumaSiTodosJustos5
  describe "def. 6" $ specG sumaSiTodosJustos6
  describe "def. 7" $ specG sumaSiTodosJustos7

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

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

-- La propiedad es
prop_sumaSiTodosJustos :: [Maybe Integer] -> Bool
prop_sumaSiTodosJustos xs =
  all (== sumaSiTodosJustos1 xs)
      [sumaSiTodosJustos2 xs,
       sumaSiTodosJustos3 xs,
       sumaSiTodosJustos4 xs,
       sumaSiTodosJustos5 xs,
       sumaSiTodosJustos6 xs,
       sumaSiTodosJustos7 xs]

verifica_sumaSiTodosJustos :: IO ()
verifica_sumaSiTodosJustos =
  quickCheck prop_sumaSiTodosJustos

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

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

ejemplo1 :: Integer -> [Maybe Integer]
ejemplo1 n = map Just [1..n]

ejemplo2 :: Integer -> [Maybe Integer]
ejemplo2 n = map Just [1..n] ++ [Nothing]

-- La comparación es
--    λ> sumaSiTodosJustos1 (ejemplo1 10000000)
--    Just 50000005000000
--    (4.08 secs, 3,520,610,288 bytes)
--    λ> sumaSiTodosJustos2 (ejemplo1 10000000)
--    Just 50000005000000
--    (4.75 secs, 4,000,610,376 bytes)
--    λ> sumaSiTodosJustos3 (ejemplo1 10000000)
--    Just 50000005000000
--    (2.55 secs, 3,680,610,368 bytes)
--    λ> sumaSiTodosJustos4 (ejemplo1 10000000)
--    Just 50000005000000
--    (2.87 secs, 3,360,610,184 bytes)
--    λ> sumaSiTodosJustos5 (ejemplo1 10000000)
--    Just 50000005000000
--    (4.96 secs, 3,461,220,272 bytes)
--    λ> sumaSiTodosJustos6 (ejemplo1 10000000)
--    Just 50000005000000
--    (3.30 secs, 3,461,220,152 bytes)
--    λ> sumaSiTodosJustos7 (ejemplo1 10000000)
--    Just 50000005000000
--    (4.10 secs, 3,461,220,304 bytes)
--
--    λ> sumaSiTodosJustos1 (ejemplo2 10000000)
--    Nothing
--    (3.22 secs, 3,200,600,632 bytes)
--    λ> sumaSiTodosJustos2 (ejemplo2 10000000)
--    Nothing
--    (2.12 secs, 3,200,600,688 bytes)
--    λ> sumaSiTodosJustos3 (ejemplo2 10000000)
--    Nothing
--    (3.22 secs, 3,200,600,704 bytes)
--    λ> sumaSiTodosJustos4 (ejemplo2 10000000)
--    Nothing
--    (2.34 secs, 3,200,600,616 bytes)
--    λ> sumaSiTodosJustos5 (ejemplo2 10000000)
--    Nothing
--    (4.02 secs, 3,061,210,672 bytes)
--    λ> sumaSiTodosJustos6 (ejemplo2 10000000)
--    Nothing
--    (2.81 secs, 3,061,210,568 bytes)
--    λ> sumaSiTodosJustos7 (ejemplo2 10000000)
--    Nothing
--    (2.61 secs, 3,061,210,720 bytes)