Translate
martes, 1 de octubre de 2019
Diagramas de Chebotko
Con frecuencia es útil presentar diseños de modelos de datos lógicos y físicos visualmente. Para lograr esto en Cassandra, podemos utilizar Diagrama de Chebotko, que presenta un diseño de esquema de base de datos como una combinación de esquemas de tabla individuales y transiciones de flujo de trabajo de aplicaciones basadas en consultas. Algunas de las ventajas de los Diagramas de Chebotko, en comparación con los scripts de definición de esquemas de CQL regulares, incluyen una legibilidad general mejorada, una inteligibilidad superior para modelos de datos complejos y una mejor expresividad con esquemas de tabla y sus consultas de aplicaciones compatibles. Los diagramas de nivel físico contienen información suficiente para generar un script CQL que instancia un esquema de base de datos y puede servir como documentos de referencia para desarrolladores y arquitectos que diseñan y mantienen una solución basada en datos.
Dentro de la comunidad de Cassandra han propuesto anotaciones para capturar modelos de datos en forma de diagrama. Y se popularizo el diagrama creado por Artem Chebotko que proporciona una forma simple e informativa de visualizar las relaciones entre consultas y tablas en nuestros diseños.
Cada tabla se muestra con su título y una lista de columnas. Las columnas de clave principal se identifican mediante símbolos como K para columnas de clave de partición y C ↑ o C ↓ para representar columnas de agrupamiento. Las líneas se muestran entrando en tablas o entre tablas para indicar las consultas que cada tabla está diseñada para admitir.
Modelado de datos en Cassandra
Nosotros en casandra podemos guradar algun id y verlo como una foreign key pero este concepto no existe en realidad, el motor de base de datos no realiza ningun checkeo.
En el diseño de bases de datos relacionales, a menudo se nos enseña la importancia de la normalización. Esto no es una ventaja cuando se trabaja con Cassandra porque funciona mejor cuando el modelo de datos está desnormalizado. A menudo ocurre que las empresas también terminan desnormalizando datos en bases de datos relacionales.
Hay dos razones comunes para esto:
Uno es el rendimiento. Las empresas simplemente no pueden obtener el rendimiento que necesitan cuando tienen que hacer tantas uniones en datos, por lo que desnormalizar puede ser una solución para la performance. Esto termina funcionando, pero va en contra de la forma en que se pretende diseñar las bases de datos relacionales por lo tanto no tiene sentido usar base de datos relacionales.
Una segunda razón es conservar un historial. El ejemplo común aquí es con las facturas. Ya tiene tablas de clientes y productos, y pensaría que podría hacer una factura que referencie esas tablas. Pero esto nunca debe hacerse en la práctica. La información del cliente o del precio podría cambiar, y luego perdería la integridad del documento tal como estaba en la fecha de la factura, lo que podría violar auditorías, informes o leyes, y causar otros problemas.
En el mundo relacional, la desnormalización viola las formas normales de Codd y tratamos de evitarla. Pero en Cassandra, la desnormalización es, bueno, perfectamente normal.
En cassandra existen una tecnicas para modelar nuestros datos :
Query-first design o diseño orientado a consultas: En cassandra podemos comenzar modelando de los datos desde las consultas, la información se debe organizar según las consultas que necesitamos. De esta manera vamos a tener un monton de datos repetidos pero esto no es significativo, dado que con cassandra el hardware es relativamente barato y la escritura muy rapida.
En las base de datos relacionales muchas veces es transparente como se organiza la base a como se guardan los datos en disco. En cambio en cassandra cada tabla es guardada en un archivo diferente en el disco. Por lo tanto es importante mantener columnas relacionadas en la misma tabla. Un objetivo de cassandra es minimizar el numero de particiones donde hay que buscar con el objetivo de satisfacer una query. Pensemos que buscar los datos en una partición puede ser sumamente más performate que buscarlo en nodos separados.
En las base de datos relacionales es muy facil cambiar el orden en una consulta con la instrucción ORDER BY. Y no se puede especificar el orden con los que se guardan los datos y por defecto los datos son recuperados según fueron grabados. En cassandra esto cambia un poco dado que esta es una decisión de diseño. El order es fijo y esta determinado por las clustering columns. Es decir el select permite utilizar el order by pero solo en las clustering columns.
En el diseño de bases de datos relacionales, a menudo se nos enseña la importancia de la normalización. Esto no es una ventaja cuando se trabaja con Cassandra porque funciona mejor cuando el modelo de datos está desnormalizado. A menudo ocurre que las empresas también terminan desnormalizando datos en bases de datos relacionales.
Hay dos razones comunes para esto:
Uno es el rendimiento. Las empresas simplemente no pueden obtener el rendimiento que necesitan cuando tienen que hacer tantas uniones en datos, por lo que desnormalizar puede ser una solución para la performance. Esto termina funcionando, pero va en contra de la forma en que se pretende diseñar las bases de datos relacionales por lo tanto no tiene sentido usar base de datos relacionales.
Una segunda razón es conservar un historial. El ejemplo común aquí es con las facturas. Ya tiene tablas de clientes y productos, y pensaría que podría hacer una factura que referencie esas tablas. Pero esto nunca debe hacerse en la práctica. La información del cliente o del precio podría cambiar, y luego perdería la integridad del documento tal como estaba en la fecha de la factura, lo que podría violar auditorías, informes o leyes, y causar otros problemas.
En el mundo relacional, la desnormalización viola las formas normales de Codd y tratamos de evitarla. Pero en Cassandra, la desnormalización es, bueno, perfectamente normal.
En cassandra existen una tecnicas para modelar nuestros datos :
Query-first design o diseño orientado a consultas: En cassandra podemos comenzar modelando de los datos desde las consultas, la información se debe organizar según las consultas que necesitamos. De esta manera vamos a tener un monton de datos repetidos pero esto no es significativo, dado que con cassandra el hardware es relativamente barato y la escritura muy rapida.
En las base de datos relacionales muchas veces es transparente como se organiza la base a como se guardan los datos en disco. En cambio en cassandra cada tabla es guardada en un archivo diferente en el disco. Por lo tanto es importante mantener columnas relacionadas en la misma tabla. Un objetivo de cassandra es minimizar el numero de particiones donde hay que buscar con el objetivo de satisfacer una query. Pensemos que buscar los datos en una partición puede ser sumamente más performate que buscarlo en nodos separados.
En las base de datos relacionales es muy facil cambiar el orden en una consulta con la instrucción ORDER BY. Y no se puede especificar el orden con los que se guardan los datos y por defecto los datos son recuperados según fueron grabados. En cassandra esto cambia un poco dado que esta es una decisión de diseño. El order es fijo y esta determinado por las clustering columns. Es decir el select permite utilizar el order by pero solo en las clustering columns.
sábado, 28 de septiembre de 2019
Optimice y mejore el rendimiento de PostgreSQL con VACUUM, ANALYZE y REINDEX
Si tenes una base de datos PostgreSQL y te anda lenta, podes usar unos comandos para mejorar y optimizar el rendimiento: VACUUM, ANALYZE y REINDEX
Para evitar problemas es preferible ejecutar estos comandos durante el periodo de mantenimiento cuando nadie usa la base.
En la configuración por defecto de PostgreSQL, el demonio AUTOVACUUM debe estar habilitado y todos los parámetros de configuración necesarios se configuran según sea necesario. El demonio ejecutará VACUUM y ANALYZE a intervalos regulares. Para confirmar si el daemon de autovacuum se está ejecutando en UNIX o Linux, podes hacer :
$ ps aux | grep autovacuum | grep -v grep
postgres 334 0.0 0.0 2654128 1232 ?? Ss 16Mar17 0:05.63 postgres: autovacuum launcher process
En la base, se puede verificar el estado de autovacuum en pg_settings con la siguiente consulta:
select name, setting from pg_settings where name = 'autovacuum' ;
El comando VACUUM recuperará el espacio que todavía utilizan los datos que se han actualizado.
En PostgreSQL, las tuplas de clave-valor actualizadas no se eliminan de las tablas cuando se cambian las filas, por lo que el comando VACUUM debe ejecutarse ocasionalmente para hacer esto.
VACUUM se puede ejecutar solo o con ANALYZE.
Veamos algunos ejemplos, ojo el nombre de la tabla es opcional, por eso se escribe así [nombre de tabla]. Sin una tabla especificada, VACUUM se ejecutará en las tablas disponibles en el esquema actual al que el usuario tiene acceso.
VACUUM simple: libera espacio para reutilizar
VACUUM [tablename]
VACUUM FULL: bloquea la tabla de la base de datos y reclama más espacio que un VACUUM simple
/* Before Postgres 9.0: */
VACUUM FULL
/* Postgres 9.0+: */
VACUUM(FULL) [tablename]
VACUUM completo y ANALYZE: realiza un VACUUM completo y recopila nuevas estadísticas sobre las rutas de ejecución de consultas utilizando ANALIZAR
/* Before Postgres 9.0: */
VACUUM FULL ANALYZE [tablename]
/* Postgres 9.0+: */
VACUUM(FULL, ANALYZE) [tablename]
Verbose Full VACUUM and ANALYZE: Igual que el anterior, pero con salida de progreso detallado
/* Before Postgres 9.0: */
VACUUM FULL VERBOSE ANALYZE [tablename]
/* Postgres 9.0+: */
VACUUM(FULL, ANALYZE, VERBOSE) [tablename]
ANALYZE recopila estadísticas para el planificador de consultas para crear las rutas de ejecución de consultas más eficientes. Según la documentación de PostgreSQL, las estadísticas precisas ayudarán al planificador a elegir el plan de ejecución más apropiado y, por lo tanto, a mejorar la velocidad del procesamiento de la consulta.
En el siguiente ejemplo, [nombre de tabla] es opcional. Sin una tabla especificada, ANALYZE se ejecutará en las tablas disponibles en el esquema actual al que el usuario tiene acceso.
ANALYZE VERBOSE [tablename]
El comando REINDEX reconstruye uno o más índices, reemplazando la versión anterior del índice. REINDEX se puede usar en muchos escenarios, incluidos los siguientes:
- Un índice se ha dañado y ya no contiene datos válidos. Aunque en teoría esto nunca debería suceder, en la práctica los índices pueden corromperse debido a errores de software o fallas de hardware. REINDEX proporciona un método de recuperación.
- Un índice se ha "hinchado", es decir, contiene muchas páginas vacías o casi vacías. Esto puede ocurrir con índices de árbol B en PostgreSQL bajo ciertos patrones de acceso poco comunes. REINDEX proporciona una forma de reducir el consumo de espacio del índice escribiendo una nueva versión del índice sin las páginas muertas.
- Ha modificado un parámetro de almacenamiento (como el factor de relleno) para un índice y desea asegurarse de que el cambio haya tenido pleno efecto.
- Una construcción de índice con la opción CONCURRENTEMENTE falló, dejando un índice "inválido". Dichos índices son inútiles, pero puede ser conveniente usar REINDEX para reconstruirlos. Tenga en cuenta que REINDEX no realizará una compilación concurrente. Para construir el índice sin interferir con la producción, debe borrar el índice y volver a crearlo con el comando CREATE INDEX CONCURRENTLY.
Veamos unos ejemplos, cualquiera de estos puede forzarse agregando la palabra clave FORCE después del comando:
Recree un solo índice, myindex:
REINDEX INDEX myindex
Recrea todos los índices en una tabla, mytable:
REINDEX TABLE mytable
Recree todos los índices en el esquema público:
REINDEX SCHEMA public
Recree todos los índices en la base de datos postgres:
REINDEX DATABASE postgres
Recree todos los índices en los catálogos del sistema en bases de datos de postgres:
REINDEX SYSTEM postgres
jueves, 26 de septiembre de 2019
Libros Gratuitos desde Java Geek!!
martes, 24 de septiembre de 2019
44 vídeos para aprender a programar con Python lanzado por Microsoft
Microsoft Developer nos regala un set de 44 vídeos sobre Python, no sé que más decir!!
Eso es todo, a disfrutar!!!
Dejo link: https://www.youtube.com/watch?v=jFCNu1-Xdsw
Y a la lista de reproducción: https://www.youtube.com/playlist?list=PLlrxD0HtieHhS8VzuMCfQD4uJ9yne1mE6
Eso es todo, a disfrutar!!!
Dejo link: https://www.youtube.com/watch?v=jFCNu1-Xdsw
Y a la lista de reproducción: https://www.youtube.com/playlist?list=PLlrxD0HtieHhS8VzuMCfQD4uJ9yne1mE6
Tuneando base de datos postgresql con pgtune
Tenes una base de datos postgresql que anda lento? no modificaste postgresql.conf?
Bueno esta aplicación te permite tunear la base postgreslq con parámetros del hardware que estas utilizando. Lo importante es que toma solo el hardware por lo tanto es solo el comienzo, luego tenemos que seguir tuneando según el uso.
Sin más dejo el link: https://pgtune.leopard.in.ua/#/
sábado, 21 de septiembre de 2019
Charla sobre Apache Cassandra
El día martes 24 de Septiembre Hexacta dictará a través de la plataforma de streaming provista por la Universidad Tecnológica Nacional Facultad Regional Paraná una charla sobre Apache Cassandra. Los esperamos!
Formulario de inscripción: https://bit.ly/2m3ZLsE
jueves, 19 de septiembre de 2019
Funciones con funciones como parámetros en Scala
En Scala una función puede tomar otra función como parámetro. Veamos un ejemplo:
def valueAtOneQuarter(f: (Double) => Double) = f(0.25)
Como vemos el parámetro puede ser cualquier función que reciba y devuelva un Doble. La función valueAtOneQuarter calcula el valor de esa función en 0.25.
Por ejemplo,
valueAtOneQuarter(ceil _) // 1.0
valueAtOneQuarter(sqrt _) // 0.5 (because 0.5 × 0.5 = 0.25)
¿Cuál es el tipo de valueAtOneQuarter? Es una función con un parámetro, por lo que su tipo se escribe como :
(parameterType) => resultType
El resultType es claramente Double, y el parameterType ya se encuentra en el encabezado de la función como (Double) => Double. Por lo tanto, el tipo de valueAtOneQuarter es :
((Double) => Double) => Double
Como valueAtOneQuarter es una función que recibe una función, se denomina función de orden superior.
Una función de orden superior también puede producir una función, es decir el resultado es una función. Veamos un ejemplo :
def mulBy(factor : Double) = (x : Double) => factor * x
Esta función nos permite obtener otra función que multiplica un número por el factor, veamos un ejemplo de uso :
val quintuple = mulBy(5)
quintuple(20) // 100
La función mulBy tiene un parámetro de tipo Double, y devuelve una función de tipo (Double) => Double. Por lo tanto, su tipo es
(Double) => ((Double) => Double)
Esta es una de las características que me gustan mucho de scala que contamos con funciones de orden superior de tipado estático y nos ayuda la inferencia de tipo.
def valueAtOneQuarter(f: (Double) => Double) = f(0.25)
Como vemos el parámetro puede ser cualquier función que reciba y devuelva un Doble. La función valueAtOneQuarter calcula el valor de esa función en 0.25.
Por ejemplo,
valueAtOneQuarter(ceil _) // 1.0
valueAtOneQuarter(sqrt _) // 0.5 (because 0.5 × 0.5 = 0.25)
¿Cuál es el tipo de valueAtOneQuarter? Es una función con un parámetro, por lo que su tipo se escribe como :
(parameterType) => resultType
El resultType es claramente Double, y el parameterType ya se encuentra en el encabezado de la función como (Double) => Double. Por lo tanto, el tipo de valueAtOneQuarter es :
((Double) => Double) => Double
Como valueAtOneQuarter es una función que recibe una función, se denomina función de orden superior.
Una función de orden superior también puede producir una función, es decir el resultado es una función. Veamos un ejemplo :
def mulBy(factor : Double) = (x : Double) => factor * x
Esta función nos permite obtener otra función que multiplica un número por el factor, veamos un ejemplo de uso :
val quintuple = mulBy(5)
quintuple(20) // 100
La función mulBy tiene un parámetro de tipo Double, y devuelve una función de tipo (Double) => Double. Por lo tanto, su tipo es
(Double) => ((Double) => Double)
Esta es una de las características que me gustan mucho de scala que contamos con funciones de orden superior de tipado estático y nos ayuda la inferencia de tipo.
domingo, 15 de septiembre de 2019
Slice en Go parte 2
Continuamos con Slice en go
Es común agregar nuevos elementos a un Slice, por lo que Go proporciona una función de adición:
func append (s [] T, vs ... T) [] T
El primer parámetro s de append es un slice de tipo T, y el resto son valores T para agregar al slice.
El valor resultante de append es un segmento que contiene todos los elementos del segmento original más los valores proporcionados.
Si la capacidad es menor a los elementos a agregar esta se ajustara automáticamente. El Slice devuelto apuntará a la matriz recién asignada.
Como podemos iterar por nuestro slice? para eso esta el for que itera sobre un slice o un mapa. Para esto tambien debemos utilizar la función range, veamos un ejemplo:
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
Cuando se itera por un slice, se devuelven dos valores para cada iteración. El primero es el índice, y el segundo es una copia del elemento en ese índice.
Puede omitir el índice o el valor asignándole a _.
for i, _ := range pow
for _, value := range pow
Si solo desea el índice, puede omitir la segunda variable.
for i: = rango pow
veamos un ejemplo:
package main
import "fmt"
func main() {
pow := make([]int, 10)
for i := range pow {
pow[i] = 1 << uint(i) // == 2**i
}
for _, value := range pow {
fmt.Printf("%d\n", value)
}
}
Es común agregar nuevos elementos a un Slice, por lo que Go proporciona una función de adición:
func append (s [] T, vs ... T) [] T
El primer parámetro s de append es un slice de tipo T, y el resto son valores T para agregar al slice.
El valor resultante de append es un segmento que contiene todos los elementos del segmento original más los valores proporcionados.
Si la capacidad es menor a los elementos a agregar esta se ajustara automáticamente. El Slice devuelto apuntará a la matriz recién asignada.
Como podemos iterar por nuestro slice? para eso esta el for que itera sobre un slice o un mapa. Para esto tambien debemos utilizar la función range, veamos un ejemplo:
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
Cuando se itera por un slice, se devuelven dos valores para cada iteración. El primero es el índice, y el segundo es una copia del elemento en ese índice.
Puede omitir el índice o el valor asignándole a _.
for i, _ := range pow
for _, value := range pow
Si solo desea el índice, puede omitir la segunda variable.
for i: = rango pow
veamos un ejemplo:
package main
import "fmt"
func main() {
pow := make([]int, 10)
for i := range pow {
pow[i] = 1 << uint(i) // == 2**i
}
for _, value := range pow {
fmt.Printf("%d\n", value)
}
}
Slice en Go
No voy a escribir un conjunto de post de go que permitan iniciarse en el lenguaje por que para ser sincero, no me gusta. A mi forma de ver las cosas, es un c mejorado, es decir es bastante de bajo nivel, que esta bien para hacer un montón de cosas, salvo las que hago. Por lo tanto, voy a hablar de las características del lenguaje que me llamaron la atención. Ahora voy a ver Slice.
Slice es como vectores dinámicos de c pero con muchas más utilidades.
Slice es flexible y de tamaño dinámico. En la práctica, Slice son mucho más comunes que los vectores, dado que no necesitan una cantidad fija de elementos.
El tipo [] T es un Slice con elementos del tipo T.
Se puede definir un Slice con dos índices, el inicio y el final, separados por dos puntos:
a [bajo: alto]
Esto selecciona un rango que incluye el primer elemento, pero excluye el último.
La siguiente expresión crea un Slice que incluye los elementos 1 a 3 :
a [1: 4]
Veamos un ejemplo:
package main
import "fmt"
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
}
Lo que esta en negrita es la declaración del Slice.
Un Slice no almacena ningún dato, solo describe una sección de un vector subyacente. Se puede ver como un puntero o referencia.
Cambiar los elementos de un segmento modifica los elementos correspondientes del vector subyacente. Otros Slice que comparten el mismo vector subyacente verán esos cambios.
Veamos un ejemplo:
package main
import "fmt"
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}
También podemos crear nuestros Slice por defecto usando { }, por ejemplo:
[] bool {verdadero, verdadero, falso}
Al cortar, puede omitir los límites superior o inferior para utilizar sus valores predeterminados.
El valor predeterminado es cero para el límite inferior y la longitud del segmento para el límite superior.
Para vector
var a [10] int
Estas expresiones de corte son equivalentes:
a [0:10]
a [: 10]
a [0:]
una[:]
El valor predeterminado es cero para el límite inferior y la longitud del segmento para el límite superior.
Para vector
var a [10] int
Estas expresiones de corte son equivalentes:
a [0:10]
a [: 10]
a [0:]
una[:]
Un Slice tiene tanto una longitud como una capacidad.
La longitud de un Slice es la cantidad de elementos que contiene.
La capacidad es el número de elementos en el vector subyacente (como tener un vector de respaldo para no tener que pedir memoria cada vez que necesitamos agregar un elemento) , contando desde el primer elemento del Slice.
La longitud y la capacidad de un Slice se pueden obtener utilizando las expresiones len y cap.
El valor cero de un slice es nulo. Y un slice nulo tiene una longitud y capacidad de 0 y no tiene un vector subyacente.
Se pueden crear Slices con una función de creación llamada make; así es como se crean vectores de tamaño dinámico.
La función make crea un Slice con valores 0:
a: = make ([] int, 5) // len (a) = 5
Para especificar una capacidad, pase un tercer argumento para hacer:
b: = make ([] int, 0, 5) // len (b) = 0, cap (b) = 5
b = b [: cap (b)] // len (b) = 5, cap (b) = 5
b = b [1:] // len (b) = 4, cap (b) = 4
Y por ahora eso es todo, continuará....
martes, 10 de septiembre de 2019
Libros de Java code geeks
|
Reforzando el aprendizaje con MATLAB: Entendiendo los conceptos básicos y configurando el entorno
Me llego un libro de Matlab sobre los conceptos básicos y la configuración del entorno, y como soy tan bueno lo comparto con ustedes :
|
Guía definitiva para Plataforma de contenedores empresariales o Guía de Docker para empresas
Me llego por mail la Guía definitiva para Plataforma de contenedores empresariales o en criollo Docker para empresas. Y lo comparto con ustedes :
| ||||||||||
| ||||||||||
| ||||||||||
|
domingo, 8 de septiembre de 2019
ENCUESTA DE SUELDOS 2019
La empresa openqube ha hecho una encuesta de sueldo de 2019 en argentina, hay muy buenos datos.
Dejo link: https://openqube.io/encuesta-sueldos-2019.02
viernes, 6 de septiembre de 2019
Scala mixea el mundo orientado a objeto y el mundo funcional.
Scala mixea el mundo orientado a objeto y el mundo funcional. Por esa razón en scala las funciones son ciudadanos de primera clase, es decir que una función es solo otro tipo de dato.
En scala podemos guardar una función en una variable :
import scala.math._
val num = 3.14
val fun = ceil _
Como vemos num vale 3.14 y fun vale ceil _ que es una función.
El _ indica que puede recibir cualquier parámetro, es decir que podemos llamarlo como queramos.
Si ejecutamos este ejemplo con el RELP num va ser de tipo Double y fun (Double) => Double, lo que significa que es una función que recibe un Double y retorna un Double.
Si utilizamos la sintaxis anterior podemos definir la función charAt de la siguiente forma :
val f = (_: String).charAt(_: Int) // (String, Int) => Char
Otra posible definición puede ser :
val f: (String, Int) => Char = _.charAt(_)
Que puedo hacer con mi variable fun?
Llamar la función :
fun(num) // 4.0
Pasarla por parámetros o retornarla desde una función.
Array(3.14, 1.42, 2.0).map(fun) // Array(4.0, 2.0, 2.0)
Funciones Anónimas: En scala, tambien tenemos funciones anónimas y estas se pueden definir de este modo:
(x: Double) => 3 * x
Por supuesto se puede guardar en una variable :
val triple = (x: Double) => 3 * x
O lo podemos definir así :
def triple(x: Double) = 3 * x
Pero si queremos utilizarla sin nombre podemos hacer :
Array(3.14, 1.42, 2.0).map((x: Double) => 3 * x)
// Array(9.42, 4.26, 6.0)
También podemos utilizar las llaves :
Array(3.14, 1.42, 2.0).map{ (x: Double) => 3 * x }
Otra forma de escribirlo de forma infija y sin utilizar el punto :
Array(3.14, 1.42, 2.0) map { (x: Double) => 3 * x }
Por ahora eso es totototodo amigos!!
Suscribirse a:
Entradas (Atom)