Ir al contenido principal

Mínimo producto escalar

El producto escalar de los vectores \([a_1,a_2,...,a_n]\) y \([b_1,b_2,..., b_n]\) es \[ a_1·b_1 + a_2·b_2 + ··· + a_n·b_n \]

Definir la función

   menorProductoEscalar :: (Ord a, Num a) => [a] -> [a] -> a

tal que (menorProductoEscalar xs ys) es el mínimo de los productos escalares de las permutaciones de xs y de las permutaciones de ys. Por ejemplo,

   menorProductoEscalar [3,2,5]  [1,4,6]    == 29
   menorProductoEscalar [3,2,5]  [1,4,-6]   == -19
   menorProductoEscalar [1..10^2] [1..10^2] == 171700
   menorProductoEscalar [1..10^3] [1..10^3] == 167167000
   menorProductoEscalar [1..10^4] [1..10^4] == 166716670000
   menorProductoEscalar [1..10^5] [1..10^5] == 166671666700000
   menorProductoEscalar [1..10^6] [1..10^6] == 166667166667000000

1. Soluciones en Haskell

import Data.List (permutations, sort, sortOn)
import Test.QuickCheck (quickCheck)
import Test.Hspec (Spec, describe, hspec, it, shouldBe)

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

menorProductoEscalar1 :: (Ord a, Num a) => [a] -> [a] -> a
menorProductoEscalar1 xs ys =
  minimum [sum (zipWith (*) pxs pys) | pxs <- permutations xs,
                                       pys <- permutations ys]

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

menorProductoEscalar2 :: (Ord a, Num a) => [a] -> [a] -> a
menorProductoEscalar2 xs ys =
  minimum [sum (zipWith (*) pxs ys) | pxs <- permutations xs]

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

menorProductoEscalar3 :: (Ord a, Num a) => [a] -> [a] -> a
menorProductoEscalar3 xs ys =
  sum (zipWith (*) (sort xs) (reverse (sort ys)))

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

verifica :: IO ()
verifica = hspec spec

specG :: ([Integer] -> [Integer] -> Integer) -> Spec
specG menorProductoEscalar = do
  it "e1" $
    menorProductoEscalar [3,2,5]  [1,4,6]  `shouldBe` 29
  it "e2" $
    menorProductoEscalar [3,2,5]  [1,4,-6] `shouldBe` -19

spec :: Spec
spec = do
  describe "def. 1" $ specG menorProductoEscalar1
  describe "def. 2" $ specG menorProductoEscalar2
  describe "def. 3" $ specG menorProductoEscalar3

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

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

-- La propiedad es
prop_menorProductoEscalar :: [Integer] -> [Integer] -> Bool
prop_menorProductoEscalar xs ys =
  all (== menorProductoEscalar1 xs' ys')
      [menorProductoEscalar2 xs' ys',
       menorProductoEscalar3 xs' ys']
  where n   = min (length xs) (length ys)
        xs' = take n xs
        ys' = take n ys

-- La comprobación es
--    λ> quickCheckWith (stdArgs {maxSize=7}) prop_menorProductoEscalar
--    +++ OK, passed 100 tests.

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

-- La comparación es
--    λ> menorProductoEscalar1 [0..5] [0..5]
--    20
--    (3.24 secs, 977385528 bytes)
--    λ> menorProductoEscalar2 [0..5] [0..5]
--    20
--    (0.01 secs, 4185776 bytes)
--
--    λ> menorProductoEscalar2 [0..9] [0..9]
--    120
--    (23.86 secs, 9342872784 bytes)
--    λ> menorProductoEscalar3 [0..9] [0..9]
--    120
--    (0.01 secs, 2580824 bytes)
--
--    λ> menorProductoEscalar3 [0..10^6] [0..10^6]
--    166666666666500000
--    (2.46 secs, 473,338,912 bytes)

2. Soluciones en Python

from itertools import permutations
from timeit import Timer, default_timer

from hypothesis import given
from hypothesis import strategies as st

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

def menorProductoEscalar1(xs: list[int], ys: list[int]) -> int:
    return min(sum(x * y for x, y in zip(pxs, pys))
               for pxs in permutations(xs)
               for pys in permutations(ys))

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

def menorProductoEscalar2(xs: list[int], ys: list[int]) -> int:
    return min(sum(x * y for x, y in zip(pxs, ys))
               for pxs in permutations(xs))

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

def menorProductoEscalar3(xs: list[int], ys: list[int]) -> int:
    return sum(x * y for x, y in zip(sorted(xs),
                                     sorted(ys, reverse=True)))

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

def test_menorProductoEscalar() -> None:
    for menorProductoEscalar in [menorProductoEscalar1,
                                 menorProductoEscalar2,
                                 menorProductoEscalar3]:
        assert menorProductoEscalar([3,2,5], [1,4,6])  == 29
        assert menorProductoEscalar([3,2,5], [1,4,-6]) == -19
    print("Verificado")

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

# Comprobación de equivalencia
# ============================

# La propiedad es
@given(st.lists(st.integers(min_value=1, max_value=1000), max_size = 6),
       st.lists(st.integers(min_value=1, max_value=1000)))
def test_menorProductoEscalar_equiv(xs: list[int], ys: list[int]) -> None:
    n = min(len(xs), len(ys))
    xs_ = xs[:n]
    ys_ = ys[:n]
    r = menorProductoEscalar1(xs_, ys_)
    assert menorProductoEscalar2(xs_, ys_) == r
    assert menorProductoEscalar3(xs_, ys_) == r

# La comprobación es
#    >>> test_menorProductoEscalar_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('menorProductoEscalar1(range(7), range(7))')
#    23.11 segundos
#    >>> tiempo('menorProductoEscalar2(range(7), range(7))')
#    0.01 segundos
#    >>> tiempo('menorProductoEscalar3(range(7), range(7))')
#    0.00 segundos
#
#    >>> tiempo('menorProductoEscalar2(range(10), range(10))')
#    3.93 segundos
#    >>> tiempo('menorProductoEscalar3(range(10), range(10))')
#    0.00 segundos