Ir al contenido principal

Aplicaciones alternativas


Definir la función

alternativa :: (a -> b) -> (a -> b) -> [a] -> [b]

tal que (alternativa f g xs) es la lista obtenida aplicando alternativamente las funciones f y g a los elementos de xs. Por ejemplo,

alternativa (+1)  (+10) [1,2,3,4]    ==  [2,12,4,14]
alternativa (+10) (*10) [1,2,3,4,5]  ==  [11,20,13,40,15]

Soluciones

import Test.Hspec (Spec, describe, hspec, it, shouldBe)
import Test.QuickCheck.HigherOrder

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

alternativa1 :: (a -> b) -> (a -> b) -> [a] -> [b]
alternativa1 _ _ [] = []
alternativa1 f _ [x] = [f x]
alternativa1 f g (x:y:xs) = f x : g y : alternativa1 f g xs

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

alternativa2 :: (a -> b) -> (a -> b) -> [a] -> [b]
alternativa2 f g = aux True
  where
    aux _ [] = []
    aux True (y:ys)  = f y : aux False ys
    aux False (y:ys) = g y : aux True ys

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

alternativa3 :: (a -> b) -> (a -> b) -> [a] -> [b]
alternativa3 _ _ []     = []
alternativa3 f g (x:xs) = f x : alternativa3 g f xs

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

alternativa4 :: (a -> b) -> (a -> b) -> [a] -> [b]
alternativa4 f g xs =
  [h x | (h,x) <- zip (cycle [f,g]) xs]

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

alternativa5 :: (a -> b) -> (a -> b) -> [a] -> [b]
alternativa5 f g =
  zipWith ($) (cycle [f,g])

-- 6º solución
-- ===========

alternativa6 :: (a -> b) -> (a -> b) -> [a] -> [b]
alternativa6 = ((zipWith id . cycle) .) . (. return) . (:)

-- Nota: Para pasar de la 5ª solución a la 6ª se usa la función
-- return que permite introducir un elemento en una lista. Por ejemplo,
--    > return 3 :: [Int]
--    [3]
-- Se puede formar listas de dos elementos mediante return. Por ejemplo,
--    [2,3]
--    = 2 : [3]
--    = 2 : return 3
--    = (:) 2 (return 3)
--    = ((:) 2) (return 3)
--    = (((:) 2) . return) 3
--    = ((. return) ((:) 2)) 3
--    = (((. return) . (:)) 2) 3
--    = ((. return) . (:)) 2 3
-- En general,
--    [x,y] = ((. return) . (:)) x y

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

verifica :: IO ()
verifica = hspec spec

specG :: ((Int -> Int) -> (Int -> Int) -> [Int] -> [Int]) -> Spec
specG alternativa = do
  it "e1" $
    alternativa (+1)  (+10) [1,2,3,4]    `shouldBe`  [2,12,4,14]
  it "e2" $
    alternativa (+10) (*10) [1,2,3,4,5]  `shouldBe`  [11,20,13,40,15]

spec :: Spec
spec = do
  describe "def. 1" $ specG alternativa1
  describe "def. 2" $ specG alternativa2
  describe "def. 3" $ specG alternativa3
  describe "def. 4" $ specG alternativa4
  describe "def. 5" $ specG alternativa5
  describe "def. 6" $ specG alternativa6

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

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

-- La propiedad es
prop_equivalencia :: (Int -> Int) -> (Int -> Int) -> [Int] -> Bool
prop_equivalencia f g xs =
  all (== alternativa1 f g xs)
      [alternativa2 f g xs,
       alternativa3 f g xs,
       alternativa4 f g xs,
       alternativa5 f g xs,
       alternativa6 f g xs]

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

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

-- La comparación es
--    λ> last (alternativa1 (+1) (+2) [1..10^7])
--    10000002
--    (2.18 secs, 2,400,602,664 bytes)
--    λ> last (alternativa2 (+1) (+2) [1..10^7])
--    10000002
--    (2.33 secs, 2,720,602,752 bytes)
--    λ> last (alternativa3 (+1) (+2) [1..10^7])
--    10000002
--    (2.04 secs, 2,640,602,640 bytes)
--    λ> last (alternativa4 (+1) (+2) [1..10^7])
--    10000002
--    (1.85 secs, 2,720,602,976 bytes)
--    λ> last (alternativa5 (+1) (+2) [1..10^7])
--    10000002
--    (0.26 secs, 1,760,602,888 bytes)
--    λ> last (alternativa6 (+1) (+2) [1..10^7])
--    10000002
--    (0.31 secs, 1,760,603,120 bytes)