Ir al contenido principal

Inversa a trozos


Definir la función

inversa :: Int -> [a] -> [a]

tal que (inversa k xs) es la lista obtenida invirtiendo elementos de xs, k elementos cada vez. Si el número de elementos de xs no es un múltiplo de k, entonces los finales elementos de xs se dejen sin invertir. Por ejemplo,

inversa 3 [1..11]  ==  [3,2,1,6,5,4,9,8,7,10,11]
inversa 4 [1..11]  ==  [4,3,2,1,8,7,6,5,9,10,11]

Comprobar con QuickCheck que la función inversa es involutiva; es decir, para todo número k > 0 y toda lista xs, se tiene que (inversa k (inversa k xs)) es igual a xs.


Soluciones

import Data.List.Split (chunksOf)
import Test.Hspec (Spec, describe, hspec, it, shouldBe)
import Test.QuickCheck

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

inversa1 :: Int -> [a] -> [a]
inversa1 k xs
  | length xs < k = xs
  | otherwise     = reverse (take k xs) ++ inversa1 k (drop k xs)

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

inversa2 :: Int -> [a] -> [a]
inversa2 k xs = aux xs (length xs)
  where
    aux ys n
      | n < k     = ys
      | otherwise = reverse (take k ys) ++ aux (drop k ys) (n-k)

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

inversa3 :: Int -> [a] -> [a]
inversa3 k xs =
  concatMap invierteSiCompleto (divideEnGrupos k xs)
  where
    invierteSiCompleto ys | length ys == k = reverse ys
                          | otherwise      = ys

divideEnGrupos :: Int -> [a] -> [[a]]
divideEnGrupos _ [] = []
divideEnGrupos n xs =
  ys : divideEnGrupos n zs
  where (ys, zs) = splitAt n xs

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

inversa4 :: Int -> [a] -> [a]
inversa4 k xs =
  concatMap invierteSiCompleto (chunksOf k xs)
  where
    invierteSiCompleto ys | length ys == k = reverse ys
                          | otherwise      = ys

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

inversa5 :: Int -> [a] -> [a]
inversa5 k xs =
  aux (chunksOf k xs)
  where
    aux [] = []
    aux [ys] | length ys == k = reverse ys
             | otherwise      = ys
    aux (ys:yss) = reverse ys ++ aux yss

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

verifica :: IO ()
verifica = hspec spec

specG :: (Int -> [Int] -> [Int]) -> Spec
specG inversa = do
  it "e1" $
    inversa 3 [1..11]  `shouldBe`  [3,2,1,6,5,4,9,8,7,10,11]
  it "e2" $
    inversa 4 [1..11]  `shouldBe`  [4,3,2,1,8,7,6,5,9,10,11]

spec :: Spec
spec = do
  describe "def. 1" $ specG inversa1
  describe "def. 2" $ specG inversa2
  describe "def. 3" $ specG inversa3
  describe "def. 4" $ specG inversa4
  describe "def. 5" $ specG inversa5

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

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

-- La propiedad es
prop_equivalencia :: Positive Int -> [Int] -> Bool
prop_equivalencia (Positive k) xs =
  all (== inversa1 k xs)
      [inversa2 k xs,
       inversa3 k xs,
       inversa4 k xs,
       inversa5 k xs]

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

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

-- La comparación es
--    λ> last (inversa1 3 [1..100000])
--    100000
--    (7.10 secs, 31,799,800 bytes)
--    λ> last (inversa2 3 [1..100000])
--    100000
--    (0.07 secs, 33,133,192 bytes)
--    λ> last (inversa3 3 [1..100000])
--    100000
--    (0.08 secs, 40,600,424 bytes)
--    λ> last (inversa4 3 [1..100000])
--    100000
--    (0.06 secs, 32,067,024 bytes)
--    λ> last (inversa5 3 [1..100000])
--    100000
--    (0.05 secs, 31,533,576 bytes)
--
--    λ> last (inversa2 3 [1..5*10^6])
--    5000000
--    (1.97 secs, 1,627,268,040 bytes)
--    λ> last (inversa3 3 [1..5*10^6])
--    5000000
--    (2.01 secs, 2,000,602,008 bytes)
--    λ> last (inversa4 3 [1..5*10^6])
--    5000000
--    (0.93 secs, 1,573,935,312 bytes)
--    λ> last (inversa5 3 [1..5*10^6])
--    5000000
--    (0.90 secs, 1,547,268,440 bytes)

-- Propiedad
-- =========

-- La propiedad es
prop_inversa :: Positive Int -> [Int] -> Bool
prop_inversa (Positive k) xs =
  inversa4 k (inversa4 k xs) == xs

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