Data.Vector

De Wikihaskell
Saltar a: navegación, buscar
Data.Vector
Vector es una biblioteca de Haskell para trabajar con arrays
Lenguaje Haskell
Biblioteca Data.Vector
Autores Rubén Díaz Sánchez
Leví Orta Caro
Antonio Balderas Alberico


Data.Vector es una biblioteca de Haskell para trabajar con arrays. Esta biblioteca proporciona un buen rendimiento con una una interfaz potente. Los tipos de datos principales que puede manejar son los arrays boxed y unboxed, que a su vez podrán ser inmutables o mutables. Además, los arrays pueden almacenar estructuras, son adecuados para la portabilidad desde y hacia C, y se indexan por valores enteros no negativos.

La biblioteca Data.Vector tiene una API muy similar a la famosa biblioteca Data.List de Haskell, conservando muchos de los mismos nombres.

Contenido

Instalación

Antes de instalar nada sobre nuestra biblioteca debemos de instalar tanto cabal como la Biblioteca de empaquetamiento Cabal dado que la bilbioteca se encuentra empaquetada con éste. Para ello procedemos a su instalación.

A continuación dispone de una breve descripción del proceso de instalación, si desea ampliar información puede consultar el enlace superior.

Instalando Cabal

1- Instalamos las dependencias que seguramente necesitaremos:

     sudo apt-get install ghc6 libghc6-parsec3-dev libghc6-network-dev libghc6-http-dev libghc6-mtl-dev zlib1g-dev

2- Descargamos el intérprete de [1], lo descomprimimos, y desde dentro de la carpeta ejecutamos

     ./bootstrap.sh

Quizás tengamos que cambiar los permisos para que tenga permiso de ejecución.

3- Una vez hecho esto, copiamos la biblioteca:

      $ sudo cp ~/.cabal/bin/cabal /usr/local/bin/

4- Si todo ha ido bien, solo tenemos que actualizar

     cabal update

5- A continuación instalamos el paquete Cabal, para ello descargamos [2], descomprimimos el fichero y desde dentro ejecutamos las siguientes ordenes:

  a- Si no somos administradores:
ghc --make Setup
./Setup configure --user
./Setup build
./Setup install
  b- Si somos administradores
ghc --make Setup
./Setup configure
./Setup build
sudo ./Setup install

Instalando Data.Vector

6- Una vez instalado el cabal hay que instalar la biblioteca vector:

    $ cabal install vector

Para empezar

¿Cómo cargar la biblioteca?

Una vez dentro de GHCi hacemos:

     Prelude> :m + Data.Vector

y ya podemos usarla. Podemos observar que ahora cambia el prompt a:

     Prelude Data.Vector>

Los siguientes puntos nos muestran de un vistazo rápido qué se puede hacer con esta biblioteca:

Generando vectores

  • Podemos generar un vector a partir de una lista:
    Prelude Data.Vector> let a = fromList [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  • Podemos rellenar el vector a partir de una secuencia, por ejemplo, con la función enumFromStepN n x y, que a partir de un número n, va añadiendo elementos a la lista añadiéndole x tantas veces (y) como le digamos:
    Prelude Data.Vector> enumFromStepN 10 2 4
   fromList [10,12,14,16] :: Data.Vector.Vector
  • Podemos crear un vector a partir de una serie de valores consecutivos:
    Prelude Data.Vector> enumFromN 10 4
   fromList [10,11,12,13] :: Data.Vector.Vector
  • Podemos crear un vector vacío:
    Prelude Data.Vector> empty
   fromList [] :: Data.Vector.Vector
  • Crear un vector de un único elemento:
    Prelude Data.Vector> singleton 10
   fromList [10] :: Data.Vector.Vector

  • Crear un vector de longitud 6 rellena del valor 10
    Prelude Data.Vector> Data.Vector.replicate 6 10
   fromList [10,10,10,10,10,10] :: Data.Vector.Vector

Tened en cuenta que en caso de utilizar funciones que tienen el mismo nombre en el prelude es necesario utilizar el path completo de Data.Vector.

  • Se pueden crear vectores aplicando una función a los elementos:
    Prelude Data.Vector> generate 10 (^2)
   fromList [0,1,4,9,16,25,36,49,64,81] :: Data.Vector.Vector
  • Los vectores pueden tener más de una dimensión:
    Prelude Data.Vector> let x = generate 10 (\n -> Data.Vector.replicate 10 n)
  • El tipo creado es un vector de vectores de enteros:
    Prelude Data.Vector> :t x
   x :: Vector (Vector Int)
  • Crea lista desde x hasta y:
    Prelude Data.Vector> let y = Data.Vector.enumFromTo 0 11
   Prelude Data.Vector> y
   fromList [0,1,2,3,4,5,6,7,8,9,10,11] :: Data.Vector.Vector
  • Pone los tres primeros elementos en un nuevo vector:
    Prelude Data.Vector> Data.Vector.take 3 y
   fromList [0,1,2] :: Data.Vector.Vector
  • Duplica y une los vectores:
    Prelude Data.Vector> y Data.Vector.++ y
   fromList [0,1,2,3,4,5,6,7,8,9,10,11,0,1,2,3,4,5,6,7,8,9,10,11] :: Data.Vector.Vector

Modificando vectores

Se pueden tratar como listas (iterar sobre ellos, reducirlos, filtrar...):

  • Mapear una función sobre los elementos de un vector
    Prelude Data.Vector> Data.Vector.map (^2) y
fromList [0,1,4,9,16,25,36,49,64,81,100,121] :: Data.Vector.Vector
  • Extraer números impares de un vector
    Prelude Data.Vector> Data.Vector.filter odd y
fromList [1,3,5,7,9,11] :: Data.Vector.Vector
  • Reducir un vector
    Prelude Data.Vector> Data.Vector.foldl (+) 0 y
66
  • Meter dos vectores en un vector de pares
    Prelude Data.Vector> Data.Vector.zip y y
fromList [(0,0),(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10),(11,11)] :: Data.Vector.Vector

Indexando vectores

Y como en todos los buenos arrays, podemos indexarlos de varias formas:

  • Devolver el primer elemento
    Prelude Data.Vector> Data.Vector.head y
0

  • Devolver el último elemento
    Prelude Data.Vector> Data.Vector.tail y
fromList [1,2,3,4,5,6,7,8,9,10,11] :: Data.Vector.Vector

  • Devolver el elemento que se encuentra indexado en esa posición
    Prelude Data.Vector> y ! 4
4

Extrayendo subvectores

También podemos dividir un vector en otros subvectores más pequeños:

  • Devuelve un porción del vector sin copiarlo. El vector debe contener al menos los elementos que hay en el índice y tantos elementos como longitud se quiera.
    slice :: Int   --índice de inicio
         -> Int   --longitud
         -> Vector a
         -> Vector a
    Prelude Data.Vector> let a = fromList [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
fromList [1,2,3,4,5,6,7,8,9,10] :: Data.Vector.Vector
Prelude Data.Vector> slice 0 4 a
fromList [1,2,3,4] :: Data.Vector.Vector


El Tutorial

El paquete vector proporciona varios tipos de matrices. La interfaz más general es Data.Vector, que ofrece a los arrays de caja (boxed array), poder trabajar con cualquier tipo. La denominación boxed indica que son valores de Haskell (perezosos), que serán evaluados bajo petición y que pueden incluso estar vacíos.

También hay tipos de matriz más especializadas:

  • Unboxed arrays.
  • Almacenables.


En todos los casos, las operaciones están sujetas a lo que se conoce en inglés como loop fusion, es decir, que al componer dos funciones:

map f . map g

Será transformado por el compilador en:

map (f . g)

Ahorrando tiempo y espacio.


Ejemplos

Se pueden crear arrays de muchas maneras, por ejemplo, desde una lista regular típica de Haskell:

let a = fromList [2,4,6]
 
Prelude Data.Vector> a
fromList [2,4,6] :: Data.Vector.Vector
 
Prelude Data.Vector> :t a
a :: Vector Integer

Para crear matrices se pueden utilizar arrays anidados:

Prelude Data.Vector> let x = fromList [ fromList [1 .. x] | x <- [1..10] ]
 
Prelude Data.Vector> :t x
x :: Vector (Vector Integer)

Otro ejemplo sería rellenar un vector de ceros:

Prelude Data.Vector> Data.Vector.replicate 10 0
fromList [0,0,0,0,0,0,0,0,0,0] :: Data.Vector.Vector


Tipos de arrays

El paquete vector proporciona varios tipos de arrays, todos con idéntica interfaz, pero comportándose de una u otra manera, adaptándose a los valores y características de los elementos que almacenan.

En resumen:

  • Los usuarios finales suelen utilizar el tipo Data.Vector.Uboxed para la mayoría de los casos.
  • Si necesitan almacenar estructuras más complejas utilizan Data.Vector.
  • Si necesitan pasar datos a C usan Data.Vector.Storable.


Los creadores de bibliotecas utilizan la interfaz genérica (Data.Vector.Generic) para maximizar la flexibildad de su biblioteca.

Boxed Arrays: Data.Vector

Este es el tipo de array más flexible, que proporciona 'boxed' arrays, es decir, arrays de punteros a valores Haskell.

Son absolutamente polimórficos, ya que pueden contener cualquier tipo Haskell.

Estos arrays son apropiados para almacenar complejos tipos en Haskell, pero una elección más acertada para tipos de datos simple es Data.Vector.Unboxed.

Unboxed Arrays: Data.Vector.Unboxed

Tipos simples, ya sea un individuo o un par pueden ser almacenados de una manera más eficiente: posiciones consecutivas de memoria sin punteros. El tipo Data.Vector.Unboxed proporciona arrays cuyos tipos son elementos primitivos sin referenciar, tales cómo:

  • Bool
  • ()
  • Char
  • Double
  • Float
  • Int
  • Int8, 16, 32, 64
  • Word
  • Word8, 16, 32, 64
  • Tipos complejos formados por tipos primitivos sin referenciar.
  • Tuplas de tipos formadas por alguno de los tipos anteriores.


Arrays de tipo unbox (primitivos sin referenciar) se suelen preferir cuando tenemos elementos de dicho tipo, ya que generalmente son más eficientes.

Storable Arrays: pasando datos a C

Arrays almacenables (Data.Vector.Storable.Vector) son vectores de cualquier tipo de la clase almacenable.

Estos arrays son inamovibles, y se convierten desde y hacia punteros, ya que son pasados a funciones de C.

unsafeFromForeignPtr
    :: Storable a
    => ForeignPtr a
    -> Int
    -> Int	
    -> Vector a	
 
-- Crea un vector desde un puntero con un offset y una longitud.
-- El dato no debería ser modificado a través del puntero después.
 
unsafeToForeignPtr
    :: Storable a
    => Vector a
    -> (ForeignPtr a, Int, Int)
 
-- Cede el puntero subyacente junto con el offset y su longitud al dato.
-- El dato no será modificado a través del puntero.
 
unsafeWith
    :: Storable a
    => Vector a
    -> (Ptr a -> IO b)
    -> IO b
 
-- Pasa un puntero al vector de datos en la acción del IO.
-- El dato no será modificado a través del puntero.

Almacenando tus propios tipos en vectores almacenables

Puedes almacenar nuevos tipos de datos en vectores almacenables escribiendo una instancia para el nuevo tipo.

En el ejemplo almacenamos un vector de 4 elementos de tipo double:

{-# LANGUAGE BangPatterns #-}
 
import Data.Vector.Storable
import qualified Data.Vector.Storable as V
import Foreign
import Foreign.C.Types
 
-- Define un tipo vector de 4 elementos
data Vec4 = Vec4 {-# UNPACK #-} !CFloat
                 {-# UNPACK #-} !CFloat
                 {-# UNPACK #-} !CFloat
                 {-# UNPACK #-} !CFloat

Nos aseguramos de que podemos almacenar un array con el nuevo tipo:

instance Storable Vec4 where
  sizeOf _ = sizeOf (undefined :: CFloat) * 4
  alignment _ = alignment (undefined :: CFloat)
 
  {-# INLINE peek #-}
  peek p = do
             a <- peekElemOff q 0
             b <- peekElemOff q 1
             c <- peekElemOff q 2
             d <- peekElemOff q 3
             return (Vec4 a b c d)
    where
      q = castPtr p
  {-# INLINE poke #-}
  poke p (Vec4 a b c d) = do
             pokeElemOff q 0 a
             pokeElemOff q 1 b
             pokeElemOff q 2 c
             pokeElemOff q 3 d
    where
      q = castPtr p

Ahora podemos escribir operaciones para el nuevo vector con muy buen rendimiento:

a = Vec4 0.2 0.1 0.6 1.0
m = Vec4 0.99 0.7 0.8 0.6
 
add :: Vec4 -> Vec4 -> Vec4
{-# INLINE add #-}
add (Vec4 a b c d) (Vec4 a' b' c' d') = Vec4 (a+a') (b+b') (c+c') (d+d')
 
mult :: Vec4 -> Vec4 -> Vec4
{-# INLINE mult #-}
mult (Vec4 a b c d) (Vec4 a' b' c' d') = Vec4 (a*a') (b*b') (c*c') (d*d')
 
vsum :: Vec4 -> CFloat
{-# INLINE vsum #-}
vsum (Vec4 a b c d) = a+b+c+d
 
multList :: Int -> Vector Vec4 -> Vector Vec4
multList !count !src
    | count <= 0    = src
    | otherwise     = multList (count-1) $ V.map (\v -> add (mult v m) a) src
 
main = do
    print $ Data.Vector.Storable.sum
          $ Data.Vector.Storable.map vsum
          $ multList repCount
          $ Data.Vector.Storable.replicate arraySize (Vec4 0 0 0 0)
 
repCount, arraySize :: Int
repCount = 10000
arraySize = 20000


Arrays impuros

Los arrays pueden ser creados y se puede operar con ellos de muchas maneras: utilizando actualizaciones destructivas, como si fuera un lenguaje imperativo, .... Una vez que todas las operaciones se han completado, el array modificable se transforma en un array puro.

Los arrays modificables son más amigables a la hora de inicializar arrays con datos del "mundo exterior".


Por ejemplo, para rellenar un array genérico, nosotros primero:

  • Creamos un vector vacío de tamaño n.
  • Actualizamos destructivamente las celdas usando alguna función.
  • Salvamos el array y lo devolvemos con un valor puro.
import qualified System.Random.Mersenne     as R
 
import qualified Data.Vector.Generic         as G
import qualified Data.Vector.Generic.Mutable as GM
 
random :: (R.MTRandom a, G.Vector v a) => R.MTGen -> Int -> IO (v a)
random g n = do
    v  <- GM.new n
    fill v 0
    G.unsafeFreeze v
  where
    fill v i
        | i < n = do
            x <- R.random g
            GM.unsafeWrite v i x
            fill v (i+1)
        | otherwise = return ()

Utilizamos el Data.Vector.Generic.Mutable.new para crear un nuevo y no inicializado array en el que entonces podremos guardar elementos (utilizando unsafeWrite).

Utilizando el interfaz genérico, podemos contruir arrays boxed, almacenables y unboxed a partir del mismo código.

Algunos ejemplos

La características más importante de los arrays es que son de orden temporal lineal (O(1)), igual que en el aspecto espacial.

-- Tamaño del array
Prelude Data.Vector> let a = fromList [1,2,3,4,5,6,7,8,9,10]
Prelude Data.Vector> Data.Vector.length a
10
 
-- ¿Está el array vacío?
Prelude Data.Vector> Data.Vector.null a
False


Creación de arrays

Enumeradores

La forma más común de generar un vector es usando una función con enumeradores.

  • Podemos rellenar el vector a partir de una secuencia, por ejemplo, con la función enumFromTo n x, que a partir de un elemento n, va añadiendo elementos al vector de 1 en 1 hasta llegar a x:
    Prelude> Data.Vector.enumFromTo 1 10
fromList [1,2,3,4,5,6,7,8,9,10] :: Data.Vector.Vector
  • Podemos rellenar el vector a partir de una secuencia, por ejemplo, con la función enumFromThenTo n x y, que a partir de elemento n, va añadiendo elementos al vector, siguiendo la secuencia n x, hasta llegar a y.
    Prelude> Data.Vector.enumFromThenTo 10 9 1
fromList [10,9,8,7,6,5,4,3,2,1] :: Data.Vector.Vector

Las funciones enumFromTo y enumFromThenTo están optimizadas para cualquier tipo. La función enumFromTo podría devolver una lista si no existies especialización para el tipo pasado. Actualmente, están especializadas para la mayoría de constructores de Int/Word/Double/Float.

Un ejemplo: insertar elementos en un vector desde un fichero

A veces, queremos rellenar un vector usando un fichero externo. La forma más fácil de conseguirlo es con IO y con la función Data.Vector.unfoldr

Usando enteros

La forma más sencilla de pasar un fichero de tipos Int o Integer es con un byteString estricto o perezoso, y la función readInt o readInteger. Para hacerlo, escribimos en un fichero el siguiente código y lo guradamos con el nombre vector.hs:

 {-# LANGUAGE BangPatterns #-}

import qualified Data.ByteString.Lazy.Char8 as L
import qualified Data.Vector                as U
import System.Environment

main = do
[f] <- getArgs
s   <- L.readFile f
print . U.sum . parse $ s

-- Rellena un vector nuevo desde un fichero que contiene una lista de números.
parse = U.unfoldr step
where
step !s = case L.readInt s of
   Nothing       -> Nothing
   Just (!k, !t) -> Just (k, L.tail t)

Para probar nuestro código, creremos un fichero de datos con un millón de enteros en la misma carpeta donde tenemos nuestro programa. En un terminal escribimos:

 $ seq 1 1000000 >  data

Ahora, complilamos nuestro programa con la opción -Odph, que habilita una optimización especial que ayuda con la fusión:

 $ ghc -Odph --make vector.hs

Por último, ejecutamos nuestro programa:

 $ time ./vector data   
500000500000
./vector data  0.08s user 0.01s system 98% cpu 0.088 total

Usando valores en punto flotante

Para cargar un fichero de valores en punto flotante en un vector, usaremos un byteString y el paquete bytestring-lexing, el cual nos proporciona funciones para leer valores float y double:

 {-# LANGUAGE BangPatterns #-}

import qualified Data.ByteString.Lazy.Char8       as L
import qualified Data.ByteString.Lex.Lazy.Double  as L
import qualified Data.Vector                      as U
import System.Environment

main = do
[f] <- getArgs
s   <- L.readFile f
print . U.sum . parse $ s

-- Rellena un vector nuevo desde un fichero que contiene una lista de números.
parse = U.unfoldr step
where
step !s = case L.readDouble s of
   Nothing       -> Nothing
   Just (!k, !t) -> Just (k, L.tail t)

Usando datos binarios

La mejor manera de pasar datos binarios es usando un byteString y el paquete Data.Binary

Hay una instancia de vectores binarios y serializables aqui [3]

Un ejemplo: el análisis de una lista de enteros en forma de texto, la serialización de nuevo en formato binario, a continuación, cargar el archivo binario:

 {-# LANGUAGE BangPatterns #-}

import Data.Vector.Binary
import Data.Binary
import qualified Data.ByteString.Lazy.Char8  as L
import qualified Data.Vector.Unboxed         as V

main = do
s <- L.readFile "dat"
let v = parse s :: V.Vector Int
encodeFile "dat2" v
v' <- decodeFile "dat2" :: IO (V.Vector Int)
print (v == v')

-- Rellena un vector nuevo desde un fichero que contiene una lista de números.
parse = V.unfoldr step
where
step !s = case L.readInt s of
   Nothing       -> Nothing
   Just (!k, !t) -> Just (k, L.tail t)

Números aleatorios

Si podemos rellenar un vector desde un archivo, también podríamos rellenar un vector con números aleatorios. Para ello vamos a utilizar el paquete mersenne-random

    $ cabal install mersenne-random

A continuación, puedemos utilizar la clase MTRandom para generar vectores aleatorios de diferentes tipos:

 import qualified Data.Vector.Unboxed as U
import System.Random.Mersenne
import Control.Monad

main = do
-- crear una nueva fuente de números aleatorios
-- y una lista infinita que contendrá números aleatorios
g   <- newMTGen Nothing
rs  <- randoms g

-- rellenamos el vector con los 10 primero números aleatorios
let a = U.fromList (take 10 rs) :: U.Vector Int

-- imprimir la suma
print (U.sum a)

-- imprimir cada elemento
forM_ (U.toList a) print

Una vez lo tenemos listo, sólo tenemos que ejecutarlo:

    $ runhaskell B.hs
63478231
-476069302
-803865425
1309255504
133714624
-1414864908
152148365
2072440988
-705525863
510608577
-714364329

También podemos utilizar el paquete vector-random para generar nuevos vectores inicializados con el generador de Mersenne.

Por ejemplo, para generar 100 millones de dobles al azar al azar y la suma de ellos:

 import qualified Data.Vector.Unboxed as U
import System.Random.Mersenne
import qualified Data.Vector.Random.Mersenne as G

main = do
g <- newMTGen Nothing
a <- G.random g 10000000 :: IO (U.Vector Double) -- 100 M
print (U.sum a)

Trabajando con vectores

Para empezar podemos ver las clases de operaciones Zip sobre vectores, en estas aplicamos un operador sobre dos vectores. En el próximo ejemplo se ve claramente como resta un vector a otro mediante la función zipWith.

> let a = fromList [20,30,40,50]

> let b = enumFromN 0 4

> a
fromList [20,30,40,50]

> b
fromList [0,1,2,3]

> Data.Vector.zipWith (-) a b
fromList [20,29,38,47]


Este es un caso sencillo pero podemos completarlo un poco más, haciendo que aplique una función a cada elemento del vector con la función map. Por ejemplo elevar todos los elementos al cuadrado:

> Data.Vector.map (^2) b
fromList [0,1,4,9]


Funciones Reducidas: Sumas, Productos, Mínimo y Máximo

Existen funciones, conocidas como Funciones Reducidas Funciones de Plegado, que son muy sencillas y se aplican a todo el vector. Por ejemplo la función sum suma todos los elementos del vector, lo mismo que la función product aunque esta los multiplica. Las funciones minimum y maximum devuelven el menor y el mayor elemento respectivamente.

> let a = enumFromN 1 100
 
> Data.Vector.sum a
5050
 
> Data.Vector.product a
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
 
> Data.Vector.minimum a
1
 
> Data.Vector.maximum a
100


Indexado, "Rebanado" (Slicing) e Iteración

Los vectores de una sola dimensión se pueden indexar, rebanar o iterar de igual forma a las listas.


Ésto es porque  los valores en Haskell son immutables por defecto. Todas las operaciones Slice son Zero-copying (Zero-copy describe operaciones de computadora en las cuales la CPU no realiza la tarea de copiar los datos desde un área hacia otra de la memoria).


La operación Slice recibe tres argumentos:

  • El índice inicial desde el que rebanar.
  • El número de elementos a rebanar.
  • El vector sobre el que operar.


> let a = enumFromN 0 10
 
> a
fromList [0,1,2,3,4,5,6,7,8,9]
 
> let b = Data.Vector.map (^3) a
 
> b ! 2
8
 
> slice 2 3 b
fromList [8,27,64]

Aquí se ve como toma, a partir del segundo elemento de b


De la misma forma tenemos otras operaciones

> Data.Vector.init b
fromList [0,1,8,27,64,125,216,343,512]

> Data.Vector.tail b
fromList [1,8,27,64,125,216,343,512,729]

> Data.Vector.take 3 b
fromList [0,1,8] :: Data.Vector.Vector

> Data.Vector.drop 3 b
fromList [27,64,125,216,343,512,729] :: Data.Vector.Vector
  • take toma los primeros elementos.
  • drop toma todos menos los primeros n elementos (el número de elementos se pasa como argumento).
  • init coge todos menos el último.
  • tail todos menos el primero.


Unsafe Slices 

Por motivos de rendimiento es posible que se deseen evitar control de límites en los índices, cuando se puede demostrar que la subcadena o índice está dentro de los límites.


Para esto hay operaciones inseguras que permiten omitir la comprobación de estos límites:

> let a = fromList [1..10]

> unsafeSlice 2 4 a
fromList [3,4,5,6]

> unsafeInit a
fromList [1,2,3,4,5,6,7,8,9]

> unsafeTail a
fromList [2,3,4,5,6,7,8,9,10]


También existen las operaciónes unsafeTake y unsafeDrop.


Ejemplo

Suma de Cuadrados

Recoge la suma de los cuadrados de los elementos de un vector:

sumsq :: U.Vector Int -> Int
sumsq v = U.sum (U.map (\x -> x * x) v)


Enlaces externos

Herramientas personales