Ir al contenido principal

Determinación de los elementos minimales


Definir la función

minimales :: Ord a => [[a]] -> [[a]]

tal que (minimales xss) es la lista de los elementos de xss que no están contenidos en otros elementos de xss. Por ejemplo,

minimales [[1,3],[2,3,1],[3,2,5]]        ==  [[2,3,1],[3,2,5]]
minimales [[1,3],[2,3,1],[3,2,5],[3,1]]  ==  [[2,3,1],[3,2,5]]
map sum (minimales [[1..n] | n <- [1..300]])  ==  [45150]

Nota: Escribir las soluciones en Haskell y en Python.


1. Soluciones en Haskell

import Data.List (delete, nub)
import Data.Set (fromList, isProperSubsetOf)
import Test.Hspec (Spec, describe, hspec, it, shouldBe)
import Test.QuickCheck

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

minimales1 :: Ord a => [[a]] -> [[a]]
minimales1 xss =
  [xs | xs <- xss,
        null [ys | ys <- xss, subconjuntoPropio xs ys]]

-- (subconjuntoPropio xs ys) se verifica si xs es un subconjunto propio
-- de ys. Por ejemplo,
--    subconjuntoPropio [1,3] [3,1,3]    ==  False
--    subconjuntoPropio [1,3,1] [3,1,2]  ==  True
subconjuntoPropio :: Ord a => [a] -> [a] -> Bool
subconjuntoPropio xs ys = aux (nub xs) (nub ys)
  where
    aux _       []  = False
    aux []      _   = True
    aux (u:us) vs = u `elem` vs && aux us (delete u vs)

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

minimales2 :: Ord a => [[a]] -> [[a]]
minimales2 xss =
  [xs | xs <- xss,
        null [ys | ys <- xss, subconjuntoPropio2 xs ys]]

subconjuntoPropio2 :: Ord a => [a] -> [a] -> Bool
subconjuntoPropio2 xs ys =
  subconjunto xs ys && not (subconjunto ys xs)

-- (subconjunto xs ys) se verifica si xs es un subconjunto de ys. Por
-- ejemplo,
--    subconjunto [1,3] [3,1,3]        ==  True
--    subconjunto [1,3,1,3] [3,1,3]    ==  True
--    subconjunto [1,3,2,3] [3,1,3]    ==  False
--    subconjunto [1,3,1,3] [3,1,3,2]  ==  True
subconjunto :: Ord a => [a] -> [a] -> Bool
subconjunto xs ys =
  all (`elem` ys) xs

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

minimales3 :: Ord a => [[a]] -> [[a]]
minimales3 xss =
  [xs | xs <- xss,
        null [ys | ys <- xss, subconjuntoPropio3 xs ys]]

subconjuntoPropio3 :: Ord a => [a] -> [a] -> Bool
subconjuntoPropio3 xs ys =
  isProperSubsetOf (fromList xs) (fromList ys)

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

verifica :: IO ()
verifica = hspec spec

specG :: ([[Int]] -> [[Int]]) -> Spec
specG minimales = do
  it "e1" $
    minimales [[1,3],[2,3,1],[3,2,5]]        `shouldBe`  [[2,3,1],[3,2,5]]
  it "e2" $
    minimales [[1,3],[2,3,1],[3,2,5],[3,1]]  `shouldBe`  [[2,3,1],[3,2,5]]

spec :: Spec
spec = do
  describe "def. 1" $ specG minimales1
  describe "def. 2" $ specG minimales2
  describe "def. 3" $ specG minimales3

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

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

-- La propiedad es
prop_minimales :: [[Int]] -> Bool
prop_minimales xss =
  all (== minimales1 xss)
      [minimales2 xss,
       minimales3 xss]

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

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

-- La comparación es
--    λ> length (minimales1 [[1..n] | n <- [1..200]])
--    1
--    (2.07 secs, 702,257,168 bytes)
--    λ> length (minimales2 [[1..n] | n <- [1..200]])
--    1
--    (0.85 secs, 138,498,288 bytes)
--    λ> length (minimales3 [[1..n] | n <- [1..200]])
--    1
--    (0.15 secs, 293,134,464 bytes)
--
--    λ> length (minimales2 [[1..n] | n <- [1..300]])
--    1
--    (3.61 secs, 442,569,888 bytes)
--    λ> length (minimales3 [[1..n] | n <- [1..300]])
--    1
--    (0.39 secs, 981,990,968 bytes)

2. Soluciones en Python

from collections import Counter
from timeit import Timer, default_timer
from typing import TypeVar

from hypothesis import given
from hypothesis import strategies as st

A = TypeVar('A')

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

# subconjuntoPropio(xs, ys) se verifica si xs es un subconjunto propio
# de ys. Por ejemplo,
#    subconjuntoPropio1([1,3], [3,1,3])    ==  False
#    subconjuntoPropio1([1,3,1], [3,1,2])  ==  True
def subconjuntoPropio1(xs: list[A],
                       ys: list[A]) -> bool:
    def aux(ws: list[A], vs: list[A]) -> bool:
        if not vs:
            return False
        if not ws:
            return True
        u, us = ws[0], ws[1:]
        if u in vs:
            vs.remove(u)
            return aux(us, vs)
        return False
    return aux(list(Counter(xs).keys()), list(Counter(ys).keys()))

def minimales1(xss: list[list[A]]) -> list[list[A]]:
    return [xs for xs in xss
            if not any(subconjuntoPropio1(xs, ys) for ys in xss)]

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

# subconjunto(xs, ys) se verifica si xs es un subconjunto de ys. Por
# ejemplo,
#    subconjunto([1,3], [3,1,3])        ==  True
#    subconjunto([1,3,1,3], [3,1,3])    ==  True
#    subconjunto([1,3,2,3], [3,1,3])    ==  False
#    subconjunto([1,3,1,3], [3,1,3,2])  ==  True
def subconjunto(xs: list[A],
                ys: list[A]) -> bool:
    return all(x in ys for x in xs)

def subconjuntoPropio2(xs: list[A],
                       ys: list[A]) -> bool:
    return subconjunto(xs, ys) and not subconjunto(ys, xs)

def minimales2(xss: list[list[A]]) -> list[list[A]]:
    return [xs for xs in xss
            if not any(subconjuntoPropio2(xs, ys) for ys in xss)]

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

def subconjuntoPropio3(xs: list[A],
                       ys: list[A]) -> bool:
    return set(xs) < set(ys)

def minimales3(xss: list[list[A]]) -> list[list[A]]:
    return [xs for xs in xss
            if not any(subconjuntoPropio3(xs, ys) for ys in xss)]

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

def test_minimales() -> None:
    for minimales in [minimales1, minimales2, minimales3]:
        assert minimales([[1,3],[2,3,1],[3,2,5]]) == [[2,3,1],[3,2,5]]
        assert minimales([[1,3],[2,3,1],[3,2,5],[3,1]]) == [[2,3,1],[3,2,5]]
    print("Verificado")

# La verificación es
#    >>> test_minimales()
#    Verificado

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

# La propiedad es
@given(st.lists(st.lists(st.integers()), min_size=1))
def test_minimales_equiv(xss: list[list[int]]) -> None:
    r = minimales1(xss)
    assert minimales2(xss) == r
    assert minimales3(xss) == r

# La comprobación es
#    >>> test_minimales_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
#    >>> tiempo('minimales1([range(1, n) for n in range(1, 300)])')
#    3.08 segundos
#    >>> tiempo('minimales2([range(1, n) for n in range(1, 300)])')
#    0.46 segundos
#    >>> tiempo('minimales3([range(1, n) for n in range(1, 300)])')
#    0.21 segundos
#
#    >>> tiempo('minimales2([range(1, n) for n in range(1, 500)])')
#    1.92 segundos
#    >>> tiempo('minimales3([range(1, n) for n in range(1, 500)])')
#    0.96 segundos