Ir al contenido principal

Iguales al siguiente


Definir la función

igualesAlSiguiente :: Eq a => [a] -> [a]

tal que (igualesAlSiguiente xs) es la lista de los elementos de xs que son iguales a su siguiente. Por ejemplo,

igualesAlSiguiente [1,2,2,2,3,3,4]  ==  [2,2,3]
igualesAlSiguiente [1..10]          ==  []

Nota: Escribir las soluciones en Haskell y en Python.


Soluciones

A continuación se muestran las soluciones en Haskell y las soluciones en Python.

1. Soluciones en Haskell

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

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

igualesAlSiguiente1 :: Eq a => [a] -> [a]
igualesAlSiguiente1 xs =
  [x | (x, y) <- consecutivos1 xs, x == y]

-- (consecutivos1 xs) es la lista de pares de elementos consecutivos en
-- xs. Por ejemplo,
--    consecutivos1 [3,5,2,7]  ==  [(3,5),(5,2),(2,7)]
consecutivos1 :: [a] -> [(a, a)]
consecutivos1 xs = zip xs (tail xs)

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

igualesAlSiguiente2 :: Eq a => [a] -> [a]
igualesAlSiguiente2 xs =
  [x | (x,y) <- consecutivos2 xs, x == y]

-- (consecutivos2 xs) es la lista de pares de elementos consecutivos en
-- xs. Por ejemplo,
--    consecutivos2 [3,5,2,7]  ==  [(3,5),(5,2),(2,7)]
consecutivos2 :: [a] -> [(a, a)]
consecutivos2 (x:y:zs) = (x,y) : consecutivos2 (y:zs)
consecutivos2 _        = []

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

igualesAlSiguiente3 :: Eq a => [a] -> [a]
igualesAlSiguiente3 (x:y:zs) | x == y    = x : igualesAlSiguiente3 (y:zs)
                             | otherwise = igualesAlSiguiente3 (y:zs)
igualesAlSiguiente3 _                    = []

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

igualesAlSiguiente4 :: Eq a => [a] -> [a]
igualesAlSiguiente4 xs = concat [ys | (_:ys) <- group xs]

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

igualesAlSiguiente5 :: Eq a => [a] -> [a]
igualesAlSiguiente5 xs = concat (map tail (group xs))

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

igualesAlSiguiente6 :: Eq a => [a] -> [a]
igualesAlSiguiente6 xs = concatMap tail (group xs)

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

igualesAlSiguiente7 :: Eq a => [a] -> [a]
igualesAlSiguiente7 = concatMap tail . group

-- 8ª solución
-- ===========

igualesAlSiguiente8 :: Eq a => [a] -> [a]
igualesAlSiguiente8 xs = tail =<< group xs

-- 9ª solución
-- ===========

igualesAlSiguiente9 :: Eq a => [a] -> [a]
igualesAlSiguiente9 = (tail =<<) . group

-- 10ª solución
-- ===========

igualesAlSiguiente10 :: Eq a => [a] -> [a]
igualesAlSiguiente10 xs = aux xs (tail xs)
  where aux (u:us) (v:vs) | u == v    = u : aux us vs
                          | otherwise = aux us vs
        aux _ _ = []

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

verifica :: IO ()
verifica = hspec spec

specG :: ([Int] -> [Int]) -> Spec
specG igualesAlSiguiente = do
  it "e1" $
    igualesAlSiguiente [1,2,2,2,3,3,4] `shouldBe` [2,2,3]
  it "e2" $
    igualesAlSiguiente [1..10] `shouldBe` []


spec :: Spec
spec = do
  describe "def. 1" $ specG igualesAlSiguiente1
  describe "def. 2" $ specG igualesAlSiguiente2
  describe "def. 3" $ specG igualesAlSiguiente3
  describe "def. 4" $ specG igualesAlSiguiente4
  describe "def. 5" $ specG igualesAlSiguiente5
  describe "def. 6" $ specG igualesAlSiguiente6
  describe "def. 7" $ specG igualesAlSiguiente7
  describe "def. 8" $ specG igualesAlSiguiente8
  describe "def. 9" $ specG igualesAlSiguiente9
  describe "def. 10" $ specG igualesAlSiguiente10

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

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

-- La propiedad es
prop_igualesAlSiguiente :: [Int] -> Bool
prop_igualesAlSiguiente xs =
  all (== igualesAlSiguiente1 xs)
      [igualesAlSiguiente2 xs,
       igualesAlSiguiente3 xs,
       igualesAlSiguiente4 xs,
       igualesAlSiguiente5 xs,
       igualesAlSiguiente6 xs,
       igualesAlSiguiente7 xs,
       igualesAlSiguiente8 xs,
       igualesAlSiguiente9 xs,
       igualesAlSiguiente10 xs]

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

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

-- La comparación es
--    > ej = concatMap show [1..10^6]
--    (0.01 secs, 446,752 bytes)
--    λ> length ej
--    5888896
--    (0.16 secs, 669,787,856 bytes)
--    λ> length (show (igualesAlSiguiente1 ej))
--    588895
--    (1.30 secs, 876,867,992 bytes)
--    λ> length (show (igualesAlSiguiente2 ej))
--    588895
--    (1.91 secs, 1,724,868,880 bytes)
--    λ> length (show (igualesAlSiguiente3 ej))
--    588895
--    (1.34 secs, 1,168,957,152 bytes)
--    λ> length (show (igualesAlSiguiente4 ej))
--    588895
--    (1.22 secs, 1,922,735,352 bytes)
--    λ> length (show (igualesAlSiguiente5 ej))
--    588895
--    (0.45 secs, 2,007,535,504 bytes)
--    λ> length (show (igualesAlSiguiente6 ej))
--    588895
--    (0.43 secs, 1,541,135,200 bytes)
--    λ> length (show (igualesAlSiguiente7 ej))
--    588895
--    (0.39 secs, 1,541,135,384 bytes)
--    λ> length (show (igualesAlSiguiente8 ej))
--    588895
--    (0.39 secs, 1,541,135,160 bytes)
--    λ> length (show (igualesAlSiguiente9 ej))
--    588895
--    (0.37 secs, 1,541,135,416 bytes)
--    λ> length (show (igualesAlSiguiente10 ej))
--    588895
--    (1.16 secs, 744,956,960 bytes)

2. Soluciones en Python

from itertools import chain, groupby
from sys import setrecursionlimit
from timeit import Timer, default_timer
from typing import TypeVar

from hypothesis import given
from hypothesis import strategies as st

setrecursionlimit(10**9)
A = TypeVar('A')

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

# consecutivos1(xs) es la lista de pares de elementos consecutivos en
# xs. Por ejemplo,
#    >>> consecutivos1([3,5,2,7])
#    [(3, 5), (5, 2), (2, 7)]
def consecutivos1(xs: list[A]) -> list[tuple[A, A]]:
    return list(zip(xs, xs[1:]))

def igualesAlSiguiente1(xs: list[A]) -> list[A]:
    return [x for x, y in consecutivos1(xs) if x == y]

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

# consecutivos2(xs) es la lista de pares de elementos consecutivos en
# xs. Por ejemplo,
#    >>> consecutivos2([3, 5, 2, 7])
#    [(3, 5), (5, 2), (2, 7)]
def consecutivos2(xs: list[A]) -> list[tuple[A, A]]:
    ys = []
    for i in range(len(xs) - 1):
        ys.append((xs[i], xs[i+1]))
    return ys

def igualesAlSiguiente2(xs: list[A]) -> list[A]:
    return [x for x, y in consecutivos2(xs) if x == y]

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

def igualesAlSiguiente3(xs: list[A]) -> list[A]:
    ys = []
    i = 0
    while i < len(xs) - 1:
        if xs[i] == xs[i+1]:
            ys.append(xs[i])
        i += 1
    return ys

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

def igualesAlSiguiente4(xs: list[A]) -> list[A]:
    return list(chain.from_iterable(list(ys)[1:] for _, ys in groupby(xs)))

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

def igualesAlSiguiente5(xs: list[A]) -> list[A]:
    def aux(us: list[A], vs: list[A]) -> list[A]:
        if us and vs:
            if us[0] == vs[0]:
                return [us[0]] + aux(us[1:], vs[1:])
            return aux(us[1:], vs[1:])
        return []
    return aux(xs, xs[1:])

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

def test_igualesAlSiguiente() -> None:
    for igualesAlSiguiente in [igualesAlSiguiente1, igualesAlSiguiente2,
                               igualesAlSiguiente3, igualesAlSiguiente4,
                               igualesAlSiguiente5]:
        assert igualesAlSiguiente([1, 2, 2, 2, 3, 3, 4]) == [2, 2, 3]
        assert igualesAlSiguiente(list(range(10))) == []
        print(f"Verificado {igualesAlSiguiente.__name__}")

# La verificación es
#    >>> test_igualesAlSiguiente()
#    Verificado igualesAlSiguiente1
#    Verificado igualesAlSiguiente2
#    Verificado igualesAlSiguiente3
#    Verificado igualesAlSiguiente4
#    Verificado igualesAlSiguiente5

# Equivalencia de las definiciones
# ================================

# La propiedad es
@given(st.lists(st.integers()))
def test_igualesAlSiguiente_equiv(xs: list[int]) -> None:
    r = igualesAlSiguiente1(xs)
    assert igualesAlSiguiente2(xs) == r
    assert igualesAlSiguiente3(xs) == r
    assert igualesAlSiguiente4(xs) == r
    assert igualesAlSiguiente5(xs) == r

# La comprobación es
#    >>> test_igualesAlSiguiente_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
#    > ej = ''.join(map(str, range(10**6)))
#    >>> ej = ''.join(map(str, range(10**6)))
#    >>> tiempo('igualesAlSiguiente1(ej)')
#    0.69 segundos
#    >>> tiempo('igualesAlSiguiente2(ej)')
#    1.23 segundos
#    >>> tiempo('igualesAlSiguiente3(ej)')
#    1.09 segundos
#    >>> tiempo('igualesAlSiguiente4(ej)')
#    1.49 segundos