One place for hosting & domains

      Información

      Información sobre generadores en JavaScript


      El autor seleccionó a Open Internet/Free Speech Fund para recibir una donación como parte del programa Write for DOnations.

      Introducción

      En ECMAScript 2015, se introdujeron generadores en el lenguaje de JavaScript. Un generador es un proceso que puede pausarse, reanudarse y producir varios valores. Un generador en JavaScript consta de una función generadora que muestra un objeto iterable Generator.

      Los generadores pueden mantener el estado, y proporcionar con ello una forma eficiente de crear iteradores, y encargarse de flujos de datos infinitos que se pueden emplear para implementar desplazamiento infinito en una página en el frontend de una aplicación web, para operar con datos de ondas de sonido, y más. Además, cuando se usan con promesas, los generadores pueden imitar la funcionalidad de async/await, lo que nos permite abordar el código asíncrono de una manera más sencilla y legible. Aunque async/await es una alternativa más frecuente para abordar los casos de uso asíncrono más comunes y sencillos, como la obtención de datos de una API, en los generadores se incluyen funciones más avanzadas que hacen que valga la pena aprender a usarlos.

      En este artículo, veremos la forma de crear funciones generadoras e iterar objetos Generator, la diferencia entre yield y return dentro de un generador y otros aspectos vinculados al trabajo con generadores.

      Funciones generadoras

      Una función generadora es una función que muestra un objeto Generator y se define con la palabra clave function seguida de un asterisco (*), como se muestra a continuación:

      // Generator function declaration
      function* generatorFunction() {}
      

      De vez en cuando, verá el asterisco junto al nombre de la función, a diferencia de la palabra clave de la función; por ejemplo, function *generatorFunction(). Esto funciona igual, aunque la de function* es una sintaxis más aceptada.

      Las funciones generadoras también pueden definirse en una expresión, como las funciones regulares:

      // Generator function expression
      const generatorFunction = function*() {}
      

      Los generadores pueden ser, incluso, los métodos de un objeto o una clase:

      // Generator as the method of an object
      const generatorObj = {
        *generatorMethod() {},
      }
      
      // Generator as the method of a class
      class GeneratorClass {
        *generatorMethod() {}
      }
      

      En los ejemplos de este artículo se usará la sintaxis de declaración de las funciones generadoras.

      Nota: A diferencia de las funciones regulares, los generadores no se pueden construir a partir de la palabra clave new ni pueden ser utilizarse junto con las funciones de flecha.

      Ahora que sabe declarar funciones generadoras, veremos los objetos Generator iterables que estas muestran.

      Objetos Generator

      Tradicionalmente, las funciones en JavaScript se ejecutan hasta completarse y la invocación de una muestra un valor cuando se alcanza la palabra clave return. Si se omite la palabra clave return, en una función se mostrará implícitamente undefined.

      En el siguiente código, por ejemplo, declaramos una función sum() que muestra un valor que es la suma de dos argumentos enteros:

      // A regular function that sums two values
      function sum(a, b) {
        return a + b
      }
      

      Al invocar la función se muestra un valor que es la suma de los argumentos:

      const value = sum(5, 6) // 11
      

      Sin embargo, una función generadora no muestra un valor de inmediato y como alternativa se muestra un objeto Generator iterable. En el siguiente ejemplo, se declara una función y se le asigna un valor de devolución único, como en una función estándar:

      // Declare a generator function with a single return value
      function* generatorFunction() {
        return 'Hello, Generator!'
      }
      

      Cuando invoquemos la función generadora, mostrará el objeto Generator, que podemos asignar a una variable:

      // Assign the Generator object to generator
      const generator = generatorFunction()
      

      Si esta fuera una función regular, esperaríamos que generator nos proporcionara la cadena mostrada en la función. Sin embargo, lo que realmente obtenemos es un objeto en estado suspended. Al invocar generator, por lo tanto, obtendremos un resultado similar al siguiente:

      Output

      generatorFunction {<suspended>} __proto__: Generator [[GeneratorLocation]]: VM272:1 [[GeneratorStatus]]: "suspended" [[GeneratorFunction]]: ƒ* generatorFunction() [[GeneratorReceiver]]: Window [[Scopes]]: Scopes[3]

      El objeto Generator mostrado por la función es un iterador. Un iterador es un objeto que tiene un método next() disponible, el cual se utiliza para iterar una secuencia de valores. El método next() muestra un objeto con propiedades value y done. value representa el valor mostrado y done indica si el iterador recorrió o no todos sus valores.

      Conociendo esto, invocaremos next() en nuestro generator y obtendremos el valor y el estado actual del iterador:

      // Call the next method on the Generator object
      generator.next()
      

      Esto generará el siguiente resultado:

      Output

      {value: "Hello, Generator!", done: true}

      El valor mostrado después de invocar next() es Hello, Generator!, y el estado de done es true, ya que este valor provino de un return que cerró el iterador. Debido a que la operación del iterador se completó, el estado de la función generadora pasará de suspended a closed. Invocar generator de nuevo dará el siguiente resultado:

      Output

      generatorFunction {<closed>}

      Hasta ahora, solo hemos demostrado que una función generadora puede ser una alternativa más compleja para obtener el valor return de una función. Pero las funciones generadoras también tienen características únicas que las distinguen de las funciones normales. En la siguiente sección, aprenderá sobre el operador yield y veremos cómo se puede pausar y reanudar la ejecución de un generador.

      Operadores yield

      Con los generadores se introdujo una nueva palabra clave en JavaScript: yield. Con yield se puede pausar una función generadora y mostrar el valor que le sigue a yield, y así proporcionar una opción ligera para iterar valores.

      En este ejemplo, pausaremos la función generadora tres veces con diferentes valores y mostraremos un valor al final. Luego asignaremos nuestro objeto Generator a la variable generator.

      // Create a generator function with multiple yields
      function* generatorFunction() {
        yield 'Neo'
        yield 'Morpheus'
        yield 'Trinity'
      
        return 'The Oracle'
      }
      
      const generator = generatorFunction()
      

      Ahora, cuando invoquemos next() en la función generadora, se pausará cada vez que encuentre yield. Se fijará el valor false para done después de cada yield, lo cual indicará que el generador no ha terminado. Una vez que encuentre un return, o ya no se encuentren más yield en la función, done pasará a tener el valor true y el generador habrá terminado.

      Utilice el método next() cuatro veces seguidas:

      // Call next four times
      generator.next()
      generator.next()
      generator.next()
      generator.next()
      

      Estos darán las siguientes cuatro líneas de resultados en orden:

      Output

      {value: "Neo", done: false} {value: "Morpheus", done: false} {value: "Trinity", done: false} {value: "The Oracle", done: true}

      Tenga en cuenta que un generador no requiere un return; si se omite, la última iteración mostrará {value: undefined, done: true}, al igual que cualquier invocación posterior de next() después de que un generador haya finalizado.

      Iterar un generador

      Usando el método next(), iteramos manualmente el objeto Generator y recibimos todas las propiedades de value y done del objeto completo. Sin embargo, al igual que Array, Map y Set, un Generator sigue el protocolo de iteración y puede iterarse con for...of:

      // Iterate over Generator object
      for (const value of generator) {
        console.log(value)
      }
      

      Con esto, se mostrará lo siguiente:

      Output

      Neo Morpheus Trinity

      El operador de propagación también puede usarse para asignar los valores de un Generator en una matriz.

      // Create an array from the values of a Generator object
      const values = [...generator]
      
      console.log(values)
      

      Esto proporcionará la siguiente matriz:

      Output

      (3) ["Neo", "Morpheus", "Trinity"]

      Tanto con la propagación como con for...of, no se tomará en cuenta return en los valores (en este caso, habría sido 'The Oracle').

      Nota: Si bien ambos métodos son eficaces para trabajar con generadores finitos, si un generador se encarga de un flujo de datos infinito, no será posible usar directamente la propagación ni for...of sin crear un bucle infinito.

      Cerrar un generador

      Como vimos, en el caso de un generador se puede fijar el valor true para la propiedad done y el valor closed para su estado mediante la iteración de todos sus valores. Existen dos alternativas adicionales para cancelar inmediatamente un generador: el método return() y el método throw().

      Con return(), el proceso del generador se puede finalizar en cualquier punto, como si hubiera una declaración return en el cuerpo de la función. Puede pasar un argumento a return() o dejarlo en blanco para un valor indefinido.

      Para demostrar return(), se creará un generador con algunos valores yield pero sin return en la definición de la función:

      function* generatorFunction() {
        yield 'Neo'
        yield 'Morpheus'
        yield 'Trinity'
      }
      
      const generator = generatorFunction()
      

      El primer next() nos proporcionará 'Neo', con el valor false fijado para done. Si invocamos un método return() en el objeto Generator justo después de esto, obtendremos el valor pasado y se cambiará done a true. Cualquier invocación adicional de next() nos proporcionará la respuesta del generador completada predeterminada con un valor indefinido.

      Para demostrar esto, ejecute los siguientes tres métodos en generator:

      generator.next()
      generator.return('There is no spoon!')
      generator.next()
      

      Esto proporcionará los tres resultados siguientes:

      Output

      {value: "Neo", done: false} {value: "There is no spoon!", done: true} {value: undefined, done: true}

      El método return() forzó al objeto Generator a completarse e ignorar cualquier otra palabra clave de yield. Esto es particularmente útil en la programación asíncrona cuando es necesario hacer que las funciones puedan cancelarse; por ejemplo, mediante la interrupción de una solicitud web cuando un usuario quiere realizar una acción diferente, ya que no es posible cancelar directamente una promesa.

      Si en el cuerpo de una función generadora se incluye una alternativa para detectar y manejar los errores, puede usar el método throw() para generar un error en el generador. Con esto se inicia el generador, se genera un error y se finaliza el generador.

      Para demostrar esto, dispondremos un try...catch dentro del cuerpo de la función generadora y registraremos un error si se encuentra:

      // Define a generator function with a try...catch
      function* generatorFunction() {
        try {
          yield 'Neo'
          yield 'Morpheus'
        } catch (error) {
          console.log(error)
        }
      }
      
      // Invoke the generator and throw an error
      const generator = generatorFunction()
      

      Ahora, ejecutaremos el método next(), seguido de throw():

      generator.next()
      generator.throw(new Error('Agent Smith!'))
      

      Esto generará el siguiente resultado:

      Output

      {value: "Neo", done: false} Error: Agent Smith! {value: undefined, done: true}

      Mediante throw(), se introdujo en el generador un error detectado por try...catch y registrado en la consola.

      Métodos y estados del objeto generador

      En la siguiente tabla, se muestra una lista de métodos que pueden utilizarse en los objetos Generator:

      MétodoDescripción
      next()Muestra el valor siguiente en un generador.
      return()Muestra un valor en un generador y finaliza el generador.
      throw()Genera un error y finaliza el generador.

      En la siguiente tabla, se enumeran los posibles estados de un objeto Generator:

      EstadoDescripción
      suspendedLa ejecución del generador se detuvo, pero el proceso de este no finalizó.
      closedEl proceso del generador finalizó porque se produjo un error, un retorno o una iteración de todos los valores.

      Delegación de yield

      Además del operador regular yield, los generadores también pueden usar la expresión yield* para delegar más valores a otros generadores. Cuando se encuentre yield* dentro de un generador, se ubicará dentro del generador delegado y empezarán a iterarse todos los yield hasta que se cierre ese generador. Esto se puede utilizar para separar diferentes funciones generadoras con el fin de organizar su código de forma semántica y, al mismo tiempo, iterar todos los yield en el orden correcto.

      Para demostrar esto, podemos crear dos funciones generadoras, una de las cuales operará mediante yield* en la otra:

      // Generator function that will be delegated to
      function* delegate() {
        yield 3
        yield 4
      }
      
      // Outer generator function
      function* begin() {
        yield 1
        yield 2
        yield* delegate()
      }
      

      A continuación, iteraremos la función generadora begin():

      // Iterate through the outer generator
      const generator = begin()
      
      for (const value of generator) {
        console.log(value)
      }
      

      Esto dará los siguientes valores en el orden en que se generan:

      Output

      1 2 3 4

      El generador externo produjo los valores 1 y 2, y luego se delegó al otro generador con yield*, que mostró 3 y 4.

      yield* también puede hacer delegaciones a cualquier objeto que sea iterable, como una matriz o un mapa. La delegación de Yield puede ser útil para organizar código, ya que cualquier función de un generador que intente usar yield tendría que ser también un generador.

      Flujos de datos infinitos

      Uno de los aspectos útiles de los generadores es la capacidad de trabajar con flujos de datos infinitos y grupos. Esto puede demostrarse creando un bucle infinito dentro de una función generadora que incremente un número en una unidad.

      En el siguiente bloque de código, definimos esta función generadora y luego iniciamos el generador:

      // Define a generator function that increments by one
      function* incrementer() {
        let i = 0
      
        while (true) {
          yield i++
        }
      }
      
      // Initiate the generator
      const counter = incrementer()
      

      Ahora, itere los valores usando next():

      // Iterate through the values
      counter.next()
      counter.next()
      counter.next()
      counter.next()
      

      Esto generará el siguiente resultado:

      Output

      {value: 0, done: false} {value: 1, done: false} {value: 2, done: false} {value: 3, done: false}

      En la función se muestran los valores sucesivos en el bucle infinito mientras que el valor de la propiedad done se mantiene en false, lo cual garantiza que no finalizará.

      Con los generadores, no tiene que preocuparse por crear un bucle infinito, porque puede detener y reanudar la ejecución cuando lo desee. Sin embargo, de todos modos debe tener cuidado con la forma en la que invoca el generador. Si utiliza la propagación o for...of en un flujo de datos infinito, seguirá iterando un bucle infinito todo a la vez, lo cual hará que el entorno se bloquee.

      Para un ejemplo más complejo de un flujo de datos infinito, podemos crear una función generadora de Fibonacci. La secuencia de Fibonacci, que suma continuamente los dos valores anteriores, puede escribirse usando un bucle infinito en un generador, como se muestra a continuación:

      // Create a fibonacci generator function
      function* fibonacci() {
        let prev = 0
        let next = 1
      
        yield prev
        yield next
      
        // Add previous and next values and yield them forever
        while (true) {
          const newVal = next + prev
      
          yield newVal
      
          prev = next
          next = newVal
        }
      }
      

      Para probar esto, podemos usar un bucle un número finito de veces e imprimir la secuencia de Fibonacci en la consola.

      // Print the first 10 values of fibonacci
      const fib = fibonacci()
      
      for (let i = 0; i < 10; i++) {
        console.log(fib.next().value)
      }
      

      Esto dará el siguiente resultado:

      Output

      0 1 1 2 3 5 8 13 21 34

      La capacidad de trabajar con conjuntos de datos infinitos es una de las razones por la cuales los generadores son tan poderosos. Esto puede ser útil para ejemplos como el caso de la implementación de desplazamiento infinito en el frontend de una aplicación web.

      Pasar los valores en los generadores

      A lo largo de este artículo, usamos generadores como iteradores y produjimos valores en cada iteración. Además de producir valores, los generadores también pueden consumir valores de next(). En este caso, yield contendrá un valor.

      Es importante observar que con el primer next() que se invoqué no se pasará un valor, sino que solo se iniciará el generador. Para demostrar esto, podemos registrar el valor de yield e invocar next() unas cuantas veces con algunos valores.

      function* generatorFunction() {
        console.log(yield)
        console.log(yield)
      
        return 'The end'
      }
      
      const generator = generatorFunction()
      
      generator.next()
      generator.next(100)
      generator.next(200)
      

      Esto generará el siguiente resultado:

      Output

      100 200 {value: "The end", done: true}

      También es posible propagar el generador con un valor inicial. En el siguiente ejemplo, crearemos un bucle for y pasaremos cada valor al método next(), pero también pasaremos un argumento a la función inicial:

      function* generatorFunction(value) {
        while (true) {
          value = yield value * 10
        }
      }
      
      // Initiate a generator and seed it with an initial value
      const generator = generatorFunction(0)
      
      for (let i = 0; i < 5; i++) {
        console.log(generator.next(i).value)
      }
      

      Recuperaremos el valor de next() y produciremos un nuevo valor para la siguiente iteración, que es igual a diez veces el valor anterior. Esto dará el siguiente resultado:

      Output

      0 10 20 30 40

      Otra forma de abordar la iniciación de un generador es ajustarlo en una función que siempre invocará next() una vez antes de hacer cualquier otra acción.

      async y await con generadores

      Una función asíncrona es un tipo de función disponible en ES6+ de JavaScript que facilita el trabajo con datos asíncronos al hacer que parezcan síncronos. Los generadores tienen una matriz de capacidades más amplia que las funciones asíncronas, pero son capaces de replicar un comportamiento similar. Implementar de esta manera una programación asíncrona puede aumentar la flexibilidad de su código.

      En esta sección, mostraremos un ejemplo de la reproducción de async y await con generadores.

      Crearemos una función asíncrona que utiliza la API de Fetch para obtener datos de la API JSONPlaceholder (que proporciona datos JSON de ejemplo para pruebas) y registrar la respuesta en la consola.

      Comience definiendo una función asíncrona llamada getUsers, que obtiene datos de la API y muestra una matriz de objetos, y luego invoque getUsers:

      const getUsers = async function() {
        const response = await fetch('https://jsonplaceholder.typicode.com/users')
        const json = await response.json()
      
        return json
      }
      
      // Call the getUsers function and log the response
      getUsers().then(response => console.log(response))
      

      Esto proporcionará datos JSON similares a los siguientes:

      Output

      [ {id: 1, name: "Leanne Graham" ...}, {id: 2, name: "Ervin Howell" ...}, {id: 3, name": "Clementine Bauch" ...}, {id: 4, name: "Patricia Lebsack"...}, {id: 5, name: "Chelsey Dietrich"...}, ...]

      Usando generadores, podemos crear algo casi idéntico que no utilice las palabras claves async y await. En su lugar, se usarán una nueva función que creamos y los valores yield en vez de las promesas await.

      En el siguiente bloque de código, definimos una función llamada getUsers que utiliza nuestra nueva función asyncAlt (que escribiremos más adelante) para imitar a async y await.

      const getUsers = asyncAlt(function*() {
        const response = yield fetch('https://jsonplaceholder.typicode.com/users')
        const json = yield response.json()
      
        return json
      })
      
      // Invoking the function
      getUsers().then(response => console.log(response))
      

      Como podemos ver, es casi idéntica a la implementación de async y await, excepto que se pasa una función generadora que produce valores.

      Ahora podemos crear una función asyncAlt que se asemeje a una función asíncrona. asyncAlt cuenta con una función generadora como un parámetro, que es nuestra función productora de promesas que muestra fetch. asyncAlt muestra una función por sí misma y resuelve cada promesa que encuentra hasta la última:

      // Define a function named asyncAlt that takes a generator function as an argument
      function asyncAlt(generatorFunction) {
        // Return a function
        return function() {
          // Create and assign the generator object
          const generator = generatorFunction()
      
          // Define a function that accepts the next iteration of the generator
          function resolve(next) {
            // If the generator is closed and there are no more values to yield,
            // resolve the last value
            if (next.done) {
              return Promise.resolve(next.value)
            }
      
            // If there are still values to yield, they are promises and
            // must be resolved.
            return Promise.resolve(next.value).then(response => {
              return resolve(generator.next(response))
            })
          }
      
          // Begin resolving promises
          return resolve(generator.next())
        }
      }
      

      Esto dará el mismo resultado que la versión de async y await:

      Output

      [ {id: 1, name: "Leanne Graham" ...}, {id: 2, name: "Ervin Howell" ...}, {id: 3, name": "Clementine Bauch" ...}, {id: 4, name: "Patricia Lebsack"...}, {id: 5, name: "Chelsey Dietrich"...}, ...]

      Tenga en cuenta que esta implementación es para demostrar cómo se pueden usar los generadores en lugar de async y await, y que no es un diseño listo para la producción. No tiene configurada la gestión de errores ni tiene la capacidad de pasar los parámetros a los valores producidos. Aunque con este método se puede añadir flexibilidad a su código, a menudo async/await será una mejor opción, ya que abstrae los detalles de la implementación y le permite enfocarse en escribir código productivo.

      Conclusión

      Los generadores son procesos que cuya ejecución puede detenerse y reanudarse. Son una potente y versátil característica de JavaScript, aunque no se utilizan comúnmente. En este tutorial, aprendió acerca de las funciones generadoras y los objetos generadores, los métodos disponibles para los generadores, los operadores yield y yield* y los generadores utilizados con conjuntos de datos finitos e infinitos. También exploramos una manera de implementar código asíncrono sin devoluciones de llamada anidadas ni largas cadenas de promesas.

      Si desea aprender más sobre la sintaxis de JavaScript, consulte nuestros tutoriales Información sobre This, Bind, Call y Apply en JavaScript e Información sobre los objetos Map y Set en JavaScript.



      Source link

      Información sobre los objetos Map y Set en JavaScript


      El autor seleccionó a Open Internet/Free Speech Fund para recibir una donación como parte del programa Write for DOnations.

      En el ámbito de JavaScript, a menudo los desarrolladores pasan mucho tiempo decidiendo la estructura de datos correcta que se usará. Esto se debe a que elegir la estructura de datos correcta puede facilitar la manipulación de esos datos posteriormente, con lo cual se puede ahorrar tiempo y facilitar la comprensión del código. Las dos estructuras de datos predominantes para almacenar conjuntos de datos son los Object y Array (un tipo de objeto). Los desarrolladores utilizan Object para almacenar pares clave-valor y Array para almacenar listas indexadas. Sin embargo, para dar más flexibilidad a los desarrolladores, en la especificación ECMAScript 2015 se introdujeron dos nuevos tipos de objetos iterables: los Map, que son grupos ordenados de pares clave-valor, y los Set, que son grupos de valores únicos.

      En este artículo, repasará los objetos Map y Set, veremos qué los hace similares o diferentes de los Object y Array, las propiedades y los métodos disponibles para ellos y ejemplos de algunos usos prácticos.

      Map

      Un Map es un grupo de pares clave-valor en el que se puede usar cualquier tipo de datos como clave y que puede mantener el orden de sus entradas. En los Map se incluyen elementos tanto de Object (un grupo único de pares clave-valor) como de Array (un grupo ordenado), pero conceptualmente son más parecidos a los Object. Esto se debe a que, si bien el tamaño y el orden de las entradas se mantiene como en un Array, las propias entradas son pares clave-valor como los Object.

      Los Map se pueden inicializar con la sintaxis new Map():

      const map = new Map()
      

      Esto nos proporciona un Map vacío:

      Output

      Map(0) {}

      Añadir valores a un Map

      Puede añadir valores a un mapa con el método set(). En el primer argumento se representará la clave y en el segundo el valor.

      A continuación, se añaden tres pares clave-valor a map:

      map.set('firstName', 'Luke')
      map.set('lastName', 'Skywalker')
      map.set('occupation', 'Jedi Knight')
      

      Aquí, empezamos a ver cómo en los Map se encuentran elementos tanto de Object como de Array. Como en el caso de un Array, tenemos un grupo con índice cero y también podemos ver la cantidad de elementos disponibles por defecto en el Map. En los Map se utiliza la sintaxis => para indicar los pares clave-valor como key => value:

      Output

      Map(3) 0: {"firstName" => "Luke"} 1: {"lastName" => "Skywalker"} 2: {"occupation" => "Jedi Knight"}

      Este ejemplo se parece a un objeto normal con claves basadas en cadenas, pero con los Map podemos usar cualquier tipo de datos como clave.

      Además de establecer los valores manualmente en un Map, también podemos inicializarlo con estos ya incluidos. Esto se hace utilizando un Array de Array, con dos elementos que son cada uno pares clave-valor, que tiene el siguiente aspecto:

      [ [ 'key1', 'value1'], ['key2', 'value2'] ]
      

      Usando la siguiente sintaxis, podemos volver a crear el mismo Map:

      const map = new Map([
        ['firstName', 'Luke'],
        ['lastName', 'Skywalker'],
        ['occupation', 'Jedi Knight'],
      ])
      

      Nota: En este ejemplo se utilizan las comas al final, también conocidas como comas pendientes. Esta es una práctica de formato de JavaScript en la cual, cuando se declara un grupo de datos, el elemento final de una serie tiene una coma al final. Aunque esta opción de formato puede utilizarse para lograr diferenciales más limpios y facilitar la manipulación del código, su uso está atado a la preferencia. Para obtener más información sobre las comas al final, consulte este artículo de Comas al final de los documentos web de MDN.

      De hecho, esta sintaxis es la misma que la del resultado de la invocación de Object.entries() en un Object. Esto ofrece una alternativa ya creada para convertir un Object en un Map, como se muestra en el siguiente bloque de código:

      const luke = {
        firstName: 'Luke',
        lastName: 'Skywalker',
        occupation: 'Jedi Knight',
      }
      
      const map = new Map(Object.entries(luke))
      

      También puede convertir un Map de nuevo en un Object o Array con una sola línea de código.

      Con lo siguiente convertiremos un Map en un Object:

      const obj = Object.fromEntries(map)
      

      Esto dará como resultado el siguiente valor de obj:

      Output

      {firstName: "Luke", lastName: "Skywalker", occupation: "Jedi Knight"}

      Ahora, convertiremos un Map en un Array:

      const arr = Array.from(map)
      

      Esto dará como resultado el siguiente Array para arr:

      Output

      [ ['firstName', 'Luke'], ['lastName', 'Skywalker'], ['occupation', 'Jedi Knight'] ]

      Claves de Map

      En los Map se acepta cualquier tipo de datos como clave y no se permiten valores de clave duplicados. Podemos demostrarlo creando un mapa y usando valores que no sean de cadena como claves y estableciendo dos valores para la misma clave.

      Primero, iniciaremos un mapa con claves que no sean de cadena:

      const map = new Map()
      
      map.set('1', 'String one')
      map.set(1, 'This will be overwritten')
      map.set(1, 'Number one')
      map.set(true, 'A Boolean')
      

      En este ejemplo, se anulará la primera clave de 1 con la siguiente y se tratarán la cadena '1'​​​ y el número 1 como claves únicas:

      Output

      0: {"1" => "String one"} 1: {1 => "Number one"} 2: {true => "A Boolean"}

      Aunque comúnmente se cree que un Object normal de JavaScript puede usar números de antemano, booleanos y otros tipos de datos primitivos como claves, este realmente no es el caso debido a que los Object cambian todas las claves por cadenas.

      A modo de ejemplo, inicialice un objeto con una clave numérica y compare el valor para una clave numérica 1 y una clave "1" convertida a cadena.

      // Initialize an object with a numerical key
      const obj = { 1: 'One' }
      
      // The key is actually a string
      obj[1] === obj['1']  // true
      

      Es por esto que si intenta usar un Object como clave se imprimirá la cadena object Object en su lugar.

      Como ejemplo, cree un Object y luego utilícelo como clave de otro Object:

      // Create an object
      const objAsKey = { foo: 'bar' }
      
      // Use this object as the key of another object
      const obj = {
        [objAsKey]: 'What will happen?'
      }
      

      Obtendrá el siguiente resultado:

      Output

      {[object Object]: "What will happen?"}

      Este no es el caso de Map. Intente crear un Object y establecerlo como clave de un Map:

      // Create an object
      const objAsKey = { foo: 'bar' }
      
      const map = new Map()
      
      // Set this object as the key of a Map
      map.set(objAsKey, 'What will happen?')
      

      La key del elemento del Map es ahora el objeto que creamos.

      Output

      key: {foo: "bar"} value: "What will happen?"

      Hay algo importante que se debe considerar respecto del uso de un Object o Array como clave: en el Map se utiliza la referencia al Object para comparar la igualdad y no el valor literal del Object. En JavaScript {} == {} muestra false, porque los dos Object no son los mismos, a pesar de tener el mismo valor (vacío).

      Esto significa que al añadir dos Object únicos con el mismo valor se creará un Map con dos entradas:

      // Add two unique but similar objects as keys to a Map
      map.set({}, 'One')
      map.set({}, 'Two')
      

      Obtendrá el siguiente resultado:

      Output

      Map(2) {{…} => "One", {…} => "Two"}

      Sin embargo, si se usa la misma referencia de Object dos veces se creará un Map con una entrada.

      // Add the same exact object twice as keys to a Map
      const obj = {}
      
      map.set(obj, 'One')
      map.set(obj, 'Two')
      

      Esto dará como resultado lo siguiente:

      Output

      Map(1) {{…} => "Two"}

      El segundo set() actualiza exactamente la misma clave que la primera, por lo que finalmente obtenemos un Map que solo tiene un valor.

      Obtener y eliminar elementos de un Map

      Una de las desventajas de trabajar con Object es que puede resultar difícil enumerarlos, así como trabajar con las claves o los valores. En la estructura de Map, en contraste, se incluyen muchas propiedades integradas. Esto hace que pueda trabajar de forma más directa con sus elementos.

      Podemos inicializar un nuevo Map para mostrar los métodos y las propiedades siguientes: delete(), has(), get() y size.

      // Initialize a new Map
      const map = new Map([
        ['animal', 'otter'],
        ['shape', 'triangle'],
        ['city', 'New York'],
        ['country', 'Bulgaria'],
      ])
      

      Utilice el método has() para verificar la existencia de un elemento en un mapa. has() mostrará un booleano.

      // Check if a key exists in a Map
      map.has('shark') // false
      map.has('country') // true
      

      Utilice el método get() para obtener un valor por clave.

      // Get an item from a Map
      map.get('animal') // "otter"
      

      Un beneficio especial de los Map respecto los Object es que pueden encontrar el tamaño de un Map en cualquier momento, como con un Array. Puede obtener el recuento de elementos en un Map con la propiedad size. Para esto se requieren menos pasos que al convertir un Object en un Array para encontrar la longitud.

      // Get the count of items in a Map
      map.size // 4
      

      Utilice el método delete() para eliminar un elemento de un Map por clave. En el método, se mostrará un booleano (true si existió un elemento y se eliminó, y false si no coincidió con ningún elemento).

      // Delete an item from a Map by key
      map.delete('city') // true
      

      Esto dará como resultado el siguiente Map:

      Output

      Map(3) {"animal" => "otter", "shape" => "triangle", "country" => "Bulgaria"}

      Por último, se pueden eliminar todos los valores de un Map con map.clear().

      // Empty a Map
      map.clear()
      

      Verá el siguiente resultado:

      Output

      Map(0) {}

      Claves, valores y entradas para Map

      Los Object pueden recuperar claves, valores y entradas usando las propiedades del constructor Object. Por otro lado, en los Map se incluyen los métodos de prototipo que nos permiten obtener directamente las claves, los valores y las entradas de la instancia del Map.

      En los métodos keys(), values() y entries() se muestra un MapIterator, que es similar a un Array en el sentido que se puede usar for...of para repetir los valores.

      Aquí se muestra otro ejemplo de un Map, que podemos usar para demostrar estos métodos:

      const map = new Map([
        [1970, 'bell bottoms'],
        [1980, 'leg warmers'],
        [1990, 'flannel'],
      ])
      

      El método keys() muestra las claves:

      map.keys()
      

      Output

      MapIterator {1970, 1980, 1990}

      El método values() muestra los valores:

      map.values()
      

      Output

      MapIterator {"bell bottoms", "leg warmers", "flannel"}

      Con el método entries() se muestra una matriz de pares clave-valor:

      map.entries()
      

      Output

      MapIterator {1970 => "bell bottoms", 1980 => "leg warmers", 1990 => "flannel"}

      Iteración con Map

      En el Map se encuentra incorporado un método forEach, similar a un Array para la iteración integrada. Sin embargo, existe cierta diferencia respecto de lo que se itera. La devolución de llamada del forEach de un Map itera value, key y el propio map, mientras que en la versión de Array se iteran item, index y el propio array.

      // Map
      Map.prototype.forEach((value, key, map) = () => {})
      
      // Array
      Array.prototype.forEach((item, index, array) = () => {})
      

      Esta es una gran ventaja de los Map respecto de los Object, ya que estos deben convertirse con keys(), values() o entries() y no existe una forma sencilla de recuperar las propiedades de un Object sin convertirlo.

      Para demostrar esto,iteraremos nuestro Map y registraremos los pares clave-valor en la consola:

      // Log the keys and values of the Map with forEach
      map.forEach((value, key) => {
        console.log(`${key}: ${value}`)
      })
      

      Esto proporcionará lo siguiente:

      Output

      1970: bell bottoms 1980: leg warmers 1990: flannel

      Debido a que en un bucle for...of se iteran elementos iterables como los de Map y Array, podemos obtener exactamente el mismo resultado desestructurando la matriz de elementos de Map:

      // Destructure the key and value out of the Map item
      for (const [key, value] of map) {
        // Log the keys and values of the Map with for...of
        console.log(`${key}: ${value}`)
      }
      

      Propiedades y métodos de Map

      Para una consulta rápida, en la siguiente tabla se muestra una lista de propiedades y métodos de Map:

      Propiedades y métodosDescripciónResultado
      set(key, value)Añade un par clave-valor a un Map.Objeto Map
      delete(key)Elimina un par clave-valor de un Map por clave.Booleano
      get(key)Muestra un valor por clave.Valor
      has(key)Comprueba la presencia de un elemento en un Map por clave.Booleano
      clear()Eliminan todos los elementos de un Map.No es pertinente
      keys()Muestran todas las claves de un Map.Objeto MapIterator
      values()Muestra todos los valores de un Map.Objeto MapIterator
      entries()Muestra las claves y los valores de un Map como [key, value].Objeto MapIterator
      forEach()Itera el Map en el orden de inserción.No es pertinente
      sizeMuestra el número de elementos de un Map.Número

      Cuando utilizar un Map

      En resumen, los Map son similares a los Object en el sentido de que almacenan pares clave-valor, pero tienen varias ventajas respecto de ellos:

      • Tamaño: los Map tienen una propiedad size, mientras que los Object no cuentan con una forma ya integrada de obtener su tamaño.
      • Iteración: los Map son directamente iterables, mientras que los Objects no.
      • Flexibilidad: los Map pueden tener cualquier tipo de datos (primitivo u Object) como clave para un valor, mientras que los Object solo pueden tener cadenas.
      • Ordenado: los Map retienen el orden de inserción, mientras que los objetos no tienen un orden garantizado.

      Debido a estos factores, los Map son una poderosa estructura de datos que se debe tener en cuenta. Sin embargo, los Object también ofrecen ventajas importantes:

      • JSON: los Object funcionan sin problema con JSON.parse() y JSON.stringify(), dos funciones esenciales para trabajar con JSON, un formato de datos común que utilizan muchas API REST.
      • Funcionamiento con un solo elemento: si trabaja con un valor conocido en un Object, puede acceder a él de forma directa con la clave sin necesidad de usar un método, como el get() de Map.

      Esta lista le servirá de ayuda para decidir si un Map u Object es la estructura de datos correcta para su caso de uso.

      Set

      Un set es un grupo de valores únicos. A diferencia de un Map, un Set es conceptualmente más parecido a un Array que a un Object, ya que es una lista de valores y no de pares clave-valor. Sin embargo, un Set no es un sustituto de los Array, sino un complemento que permite brindar respaldo adicional para trabajar con datos duplicados.

      Puede inicializar Set con la sintaxis new Set().

      const set = new Set()
      

      Esto proporciona un set vacío:

      Output

      Set(0) {}

      Se pueden añadir elementos a un set con el método add(). (Esto no debe confundirse con el método set() disponible para Map, aunque son similares).

      // Add items to a Set
      set.add('Beethoven')
      set.add('Mozart')
      set.add('Chopin')
      

      Debido a que los sets solo pueden contener valores únicos, cualquier intento de añadir un valor ya existente se ignorará.

      set.add('Chopin') // Set will still contain 3 unique values
      

      Nota: La misma comparación de igualdad que se aplica a las claves de Map sirve para elementos de Set. Dos objetos que tengan el mismo valor y no compartan la misma referencia no se considerarán iguales.

      También puede inicializar Sets con un Array de valores. Si hay valores duplicados en la matriz, se eliminarán del Set.

      // Initialize a Set from an Array
      const set = new Set(['Beethoven', 'Mozart', 'Chopin', 'Chopin'])
      

      Output

      Set(3) {"Beethoven", "Mozart", "Chopin"}

      En cambio, un Set se puede convertir en un Array con una línea de código:

      const arr = [...set]
      

      Output

      (3) ["Beethoven", "Mozart", "Chopin"]

      Los sets tienen muchos de los métodos y las propiedades idénticos de los Map, incluidos delete(), has(), clear() y size.

      // Delete an item
      set.delete('Beethoven') // true
      
      // Check for the existence of an item
      set.has('Beethoven') // false
      
      // Clear a Set
      set.clear()
      
      // Check the size of a Set
      set.size // 0
      

      Tenga en cuenta que los sets no tienen una una forma de acceder a un valor por medio de una clave o un índice, como Map.get(key) o arr[index].

      Claves, valores y entradas para los sets

      Los Map y Set cuentan con métodos keys(), values() y entries() que muestran un iterador. Sin embargo, mientras que cada uno de estos métodos tiene un propósito distinto en los Map, en los Set no se incluyen claves y, por lo tanto, estas son un alias para los valores. Esto significa que tanto keys() y values() mostrarán el mismo iterador y entries() mostrará el valor dos veces. Tiene más sentido usar únicamente values() con Set, ya que los otros dos métodos existen para la uniformidad y la compatibilidad cruzada con Map.

      const set = new Set([1, 2, 3])
      // Get the values of a set
      set.values()
      

      Output

      SetIterator {1, 2, 3}

      Iteraciones con sets

      Como en el caso de los Map, los Set cuentan con un método forEach() incorporado. Debido a que los Set no tienen claves, el primer y segundo parámetro de la devolución de llamada forEach() muestran el mismo valor, por lo que no hay un caso de uso para este fuera de la compatibilidad con Map. Los parámetros de forEach() son (value, key, set).

      Tanto forEach() como for...of pueden utilizarse en Set. Primero, veremos la iteración de forEach():

      const set = new Set(['hi', 'hello', 'good day'])
      
      // Iterate a Set with forEach
      set.forEach((value) => console.log(value))
      

      Luego podemos escribir la versión de for...of:

      // Iterate a Set with for...of
      for (const value of set) {  
          console.log(value);
      }
      

      El resultado de ambas estrategias será el siguiente:

      Output

      hi hello good day

      Propiedades y métodos de los Set

      A modo de referencia rápida, en la siguiente tabla se muestra una lista de las propiedades y los métodos de Set:

      Propiedades y métodosDescripciónResultado
      add(value)Añade un nuevo elemento a un Set.Objeto Set
      delete(value)Elimina el elemento especificado de un Set.Booleano
      has()Verifica la presencia de un elemento en un Set.Booleano
      clear()Elimina todos los elementos de un Set.No es pertinente
      keys()Muestra todos los valores de un Set (como values()).Objeto SetIterator
      values()Muestra todos los valores de un Set (como keys()).Objeto SetIterator
      entries()Muestra todos los valores de un Set como [value, value].Objeto SetIterator
      forEach()Itera el Set en el orden de inserción.No es pertinente
      sizeMuestra el número de elementos de un Set.Número

      Cuando utilizar un Set

      Los Set son un elemento adicional útil para su juego de herramientas de JavaScript, en particular para trabajar con valores duplicados en los datos.

      En una sola línea, podemos crear un nuevo Array sin los valores duplicados de un Array que sí los tiene.

      const uniqueArray = [ ...new Set([1, 1, 2, 2, 2, 3])] // (3) [1, 2, 3]
      

      Esto proporcionará lo siguiente:

      Output

      (3) [1, 2, 3]

      Un Set se puede usar para encontrar la unión, intersección y diferencia entre dos conjuntos de datos. Sin embargo, los Array tienen una ventaja importante respecto de los Set para la manipulación adicional de los datos debido a los métodos sort(), map(), filter() y reduce(), y también compatibilidad directa con los métodos JSON.

      Conclusión

      A través de este artículo, aprendió que un Map es un grupo de pares clave-valor ordenados y que un Set es un grupo de valores únicos. Ambas estructuras de datos añaden capacidades a JavaScript y simplifican tareas comunes, como encontrar la extensión de un grupo de pares clave-valor y eliminar elementos duplicados de un conjunto de datos, respectivamente. Por otro lado, los Object y Array se han utilizado tradicionalmente para el almacenamiento y la manipulación de datos en JavaScript, y tienen compatibilidad directa con JSON, lo cual sigue haciendo que sean las estructuras de datos más esenciales, sobre todo para trabajar con API de REST. Los Map y los Set son principalmente útiles como estructuras de datos de respaldo para los Object y Array.

      Si desea obtener más información sobre JavaScript, consulte la página de inicio de nuestra serie Cómo producir código en JavaScript o busque nuestra serie Cómo producir código en Node.js para hallar artículos sobre el desarrollo de backend.



      Source link

      Información sobre matrices y segmentos en Go


      Introducción

      En Go, las matrices y los segmentos son estructuras de datos que consisten en una secuencia ordenada de elementos. Estas colecciones de datos son muy útiles cuando se necesita trabajar con muchos valores relacionados. Permiten mantener en un mismo lugar los datos que deben ir juntos, condensar su código y aplicar los mismos métodos y las mismas operaciones en varios valores a la vez.

      Aunque las matrices y los segmentos de Go son secuencias ordenadas de elementos, existen grandes diferencias entre ambos. Una matriz de Go es una estructura de datos que consta de una secuencia ordenada de elementos con su capacidad definida en el momento de su creación. Una vez que se asigna el tamaño a una matriz, este ya no se puede cambiar. Por otra parte, un segmento es una versión de extensión variable de una matriz, lo que ofrece mayor flexibilidad a los desarrolladores que usan estas estructuras de datos. Los segmentos representan lo que se podría considerar como matrices en otros lenguajes.

      Dadas estas diferencias, existen situaciones específicas en las que se usará uno en vez del otro. Si recién comienza a usar Go, determinar el momento en que se usarán puede ser confuso: aunque la versatilidad de los segmentos los convierte en la opción más apropiada en la mayoría de los casos, existen casos específicos en los que las matrices pueden optimizar el rendimiento de su programa.

      En este artículo se abordarán en detalle las matrices y los segmentos, que le proporcionarán la información necesaria para hacer la elección adecuada cuando tenga que decidirse por uno de estos tipos de datos. Además, verá las formas más comunes de declarar matrices y segmentos, y de trabajar con ellos. En el tutorial se brindará primero una descripción de las matrices y la manera de manipularlas, y luego una explicación de los segmentos y las diferencias entre ellos.

      Matrices

      Las matrices son estructuras de conjuntos de datos con un número determinado de elementos. Debido a que el tamaño de una matriz es estático, la estructura de datos solo debe asignar memoria una vez, a diferencia de una estructura de datos de extensión variable que debe asignar memoria de forma dinámica para que pueda ser mayor o menor en el futuro. Aunque la extensión fija de las matrices puede hacer que sea complicado trabajar con ellas, la asignación de memoria por única vez puede aumentar la velocidad y el rendimiento de su programa. Debido a esto, los desarrolladores suelen usar matrices cuando optimizan programas en instancias en las cuales la estructura de datos nunca necesitará una cantidad variable de elementos.

      Definir una matriz

      Las matrices se definen declarando su tamaño entre corchetes [], seguido del tipo de datos de los elementos. Todos los elementos de una matriz de Go deben tener el mismo tipo de datos. Después del tipo de datos, puede declarar los valores individuales de los elementos de la matriz entre llaves { }.

      A continuación, se muestra el esquema general para declarar una matriz:

      [capacity]data_type{element_values}
      

      Nota: Es importante recordar que con cada declaración de una nueva matriz se crea un tipo distinto. Por lo tanto, aunque [2]int y [3]int tienen elementos enteros, sus extensiones diferentes hacen que sus tipos de datos sean incompatibles.

      Si no declara los valores de los elementos de la matriz, el valor predeterminado es cero, lo cual significa que los elementos de la matriz estarán vacíos. Para los enteros, esto se representa con 0 y para cadenas con una cadena vacía.

      Por ejemplo, la siguiente matriz numbers tiene tres elementos enteros que aún no tienen valor:

      var numbers [3]int
      

      Si imprime numbers, obtendrá el siguiente resultado:

      Output

      [0 0 0]

      Si desea asignar los valores de los elementos cuando cree la matriz, disponga los valores entre llaves. Una matriz de cadenas con valores determinados tiene el siguiente aspecto:

      [4]string{"blue coral", "staghorn coral", "pillar coral", "elkhorn coral"}
      

      Puede almacenar una matriz en una variable e imprimirla:

      coral := [4]string{"blue coral", "staghorn coral", "pillar coral", "elkhorn coral"}
      fmt.Println(coral)
      

      Si ejecuta un programa con las líneas anteriores, obtendrá el siguiente resultado:

      Output

      [blue coral staghorn coral pillar coral elkhorn coral]

      Observe que no hay delineación entre los elementos de la matriz cuando se imprime. Esto hace que resulte difícil saber dónde termina un elemento y comienza otro. Debido a esto, a veces es útil usar la función fmt.Printf en su lugar, la cual le puede dar formato a las cadenas antes de imprimirlas en la pantalla. Proporcione el verbo %q con este comando para indicar a la función que disponga los valores entre comillas:

      fmt.Printf("%qn", coral)
      

      Esto dará como resultado lo siguiente:

      Output

      ["blue coral" "staghorn coral" "pillar coral" "elkhorn coral"]

      Ahora cada elemento está entre comillas. El verbo n indica al formateador que añada un salto de línea al final.

      Con una idea general sobre cómo declarar matrices y en qué consisten, ahora podrá aprender a especificar elementos en una matriz con un número de índice.

      Indexar matrices (y segmentos)

      Cada elemento de una matriz (y también de un segmento) puede invocarse de forma individual mediante indexación. Cada elemento corresponde a un número de índice, que es un valor int a partir del número de índice 0 en adelante.

      Usaremos una matriz en los siguientes ejemplos, pero también podría utilizar un segmento, ya que la forma en la que se indexan es igual para ambos.

      Para la matriz coral, el desglose del índice tiene este aspecto:

      “blue coral”“staghorn coral”“pillar coral”“elkhorn coral”
      0123

      El primer elemento, la cadena “blue coral”, comienza en el índice 0, y el segmento termina en el índice 3 con el elemento “elkhorn coral”.

      Debido a que cada elemento en un segmento o matriz tiene un número de índice correspondiente, podemos acceder a ellos y manipularlos como a otros tipos de datos secuenciales.

      Ahora, podemos invocar un elemento discreto del segmento haciendo referencia a su número de índice:

      fmt.Println(coral[1])
      

      Output

      staghorn coral

      Los números de índice para este segmento tienen un intervalo 0-3, como se muestra en la tabla anterior. Por lo tanto, para llamar a cualquiera de los elementos individualmente, nos referiremos a los números de índice de la siguiente manera:

      coral[0] = "blue coral"
      coral[1] = "staghorn coral"
      coral[2] = "pillar coral"
      coral[3] = "elkhorn coral"
      

      Si invocamos la matriz coral con cualquier número de índice superior a 3, estará fuera de rango porque no será válido:

      fmt.Println(coral[18])
      

      Output

      panic: runtime error: index out of range

      Al indexar una matriz o un segmento, siempre debe usar un número positivo. A diferencia de algunos lenguajes que le permiten aplicar indexación en sentido inverso con un número negativo, hacer eso en Go producirá un error:

      fmt.Println(coral[-1])
      

      Output

      invalid array index -1 (index must be non-negative)

      Podemos concatenar elementos de cadenas de una matriz o un segmento con otras cadenas usando el operador +:

      fmt.Println("Sammy loves " + coral[0])
      

      Output

      Sammy loves blue coral

      Pudimos concatenar el elemento de la cadena en el índice de número 0 con la cadena “Sammy loves”.

      Con los números de índice que corresponden a elementos dentro de una matriz o segmento, podemos acceder a cada elemento con discreción y trabajar con ellos. Para demostrar esto, a continuación veremos cómo modificar un elemento en un índice determinado.

      Modificar elementos

      Podemos usar la indexación para cambiar elementos dentro de una matriz o segmento configurando un elemento numerado con un índice igual a un valor diferente. Esto nos proporciona un mayor control sobre los datos de nuestros segmentos y matrices, y nos permitirá manipular mediante programación elementos individuales.

      Si queremos cambiar el valor de la cadena del elemento en el índice 1 de la matriz coral, de “staghorn coral” a “foliose coral ”, podemos hacerlo de la siguiente manera:

      coral[1] = "foliose coral"
      

      Ahora cuando imprimamos coral, la matriz será diferente:

      fmt.Printf("%qn", coral)
      

      Output

      ["blue coral" "foliose coral" "pillar coral" "elkhorn coral"]

      Ahora que ya sabe manipular elementos individuales de una matriz o un segmento, veamos algunas funciones que le darán mayor flexibilidad al trabajar con tipos de datos de colección.

      Contar elementos con len()

      En Go, len() es una función integrada creada para ayudarlo a trabajar con matrices y segmentos. Al igual que con las cadenas, puede calcular la extensión de una matriz o un segmento usando len() y pasar la matriz o segmento como parámetro.

      Por ejemplo, para encontrar la cantidad elementos de la matriz coral, utilizaría lo siguiente:

      len(coral)
      

      Si imprime la longitud de la matriz coral, obtendrá el siguiente resultado:

      Output

      4

      Esto proporciona 4 a la extensión de la matriz en el tipo de datos int, lo cual es correcto porque la matriz coral tiene cuatro elementos:

      coral := [4]string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral"}
      

      Si crea una matriz de enteros con más elementos, podrá usar la función len() también:

      numbers := [13]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
      fmt.Println(len(numbers))
      

      Esto daría como resultado lo siguiente:

      Output

      13

      Aunque estas matrices de ejemplo tienen relativamente pocos elementos, la función len() es particularmente útil para determinar la cantidad de elementos disponibles en matrices muy grandes.

      A continuación, veremos cómo añadir un elemento a un tipo de datos de coleccción y a demostrar cómo, debido a la extensión fija de las matrices, añadir estos tipos de datos estáticos provocará un error.

      Añadir elementos con append()

      append() es un método incorporado en Go que añade elementos a un tipo de datos de colección. Sin embargo, este método no funcionará cuando se utilice con una matriz. Como se mencionó anteriormente, la principal diferencia entre las matrices y los segmentos es que el tamaño de una matriz no se puede modificar. Esto significa que si bien se pueden cambiar los valores de los elementos de una matriz, no es posible expandirla o reducirla matriz una vez que se haya definido.

      Analicemos su matriz coral:

      coral := [4]string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral"}
      

      Supongamos que quiere añadir el elemento “black coral” a esta matriz. Si intenta usar la función append() con la matriz escribiendo lo siguiente:

      coral = append(coral, "black coral")
      

      Como resultado, verá un error:

      Output

      first argument to append must be slice; have [4]string

      Para corregir esto, veamos más sobre el tipo de datos de segmento, cómo definir un segmento y cómo realizar la conversión de una matriz a un segmento.

      Segmentos

      Un segmento es un tipo de datos en Go que es una secuencia ordenada mutable o modificable de elementos. Debido a que el tamaño de un segmento es variable, existe mucha mayor flexibilidad al usarlos; al trabajar con colecciones de datos que podrían necesitar expandirse o contraerse en el futuro, utilizar un segmento garantizará que su código no tenga errores cuando intente manipular la extensión del conjunto. En la mayoría de los casos, esta mutabilidad hace que valga la pena la posible reasignación de memoria que a veces requieren los segmentos en comparación con las matrices. Cuando necesite almacenar muchos elementos o iterarlos y desee ser capaz de modificar fácilmente esos elementos, es probable que desee trabajar con el tipo de datos de segmento.

      Definir un segmento

      Los segmentos se definen declarando el tipo de datos precedido por un conjunto vacío de corchetes ([]) y una lista de elementos entre llaves ({}). Observará que, a diferencia de las matrices que requieren un int entre los corchetes para declarar una extensión específica, un segmento no contiene nada entre corchetes, lo que representa su extensión variable.

      Crearemos un segmento que contenga elementos del tipo de datos de cadena:

      seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp", "anemone"}
      

      Cuando imprimimos el segmento, podemos observar los elementos que están en el segmento:

      fmt.Printf("%qn", seaCreatures)
      

      Esto dará como resultado lo siguiente:

      Output

      ["shark" "cuttlefish" "squid" "mantis shrimp" "anemone"]

      Si desea crear un segmento de una extensión determinada sin completar aún los elementos del grupo, puede usar la función integrada make():

      oceans := make([]string, 3)
      

      Si imprimiera este segmento, obtendría lo siguiente:

      Output

      ["" "" ""]

      Si desea asignar previamente la memoria a una capacidad determinada, puede pasar un tercer argumento a make():

      oceans := make([]string, 3, 5)
      

      Con esto, se crearía un segmento con una extensión de 3, valor cero y una capacidad previamente asignada de 5 elementos.

      Ahora sabe cómo declarar un segmento. Sin embargo, esto aún no resuelve el error que se produjo antes con la matriz coral. Para usar la función append() con coral, primero tendrá que aprender a quitar secciones de una matriz.

      Dividir matrices en segmentos

      Usando números de índice para determinar los puntos de inicio y final, puede invocar un apartado de los valores dentro de una matriz. Esto se conoce como segmentación de la matriz y puede hacerlo creando un rango de números de índice separados por dos puntos con la forma [first_index:second_index]. Sin embargo, es importante observar que cuando se divide una matriz, el resultado es un segmento, no una matriz.

      Digamos que desea imprimir solo los elementos centrales de la matriz coral, sin el primer y último elemento. Puede realizarlo creando un segmento que empiece en el índice 1 y termine justo antes del índice 3:

      fmt.Println(coral[1:3])
      

      La ejecución de un programa con esta línea produciría el siguiente resultado:

      Output

      [foliose coral pillar coral]

      Al crear una división, como en [1:3], el primer número marca el inicio del segmento (incluido) y el segundo es la suma del primer número y del número total de elementos que desea obtener:

      array[starting_index : (starting_index + length_of_slice)]
      

      En este caso, llamó al segundo elemento (o índice 1) como el punto de inicio y llamó dos elementos en total. Así es como se vería el cálculo:

      array[1 : (1 + 2)]
      

      Esto le permitió obtener la siguiente notación:

      coral[1:3]
      

      Si desea establecer el inicio o el final de la matriz como un punto de inicio o final de la división, puede omitir uno de los números de la sintaxis array[first_index:second_index]. Por ejemplo, si desea imprimir los primeros tres elementos de la matriz coral, que serían “blue coral”, “foliose coral” y “pillar coral”, puede hacerlo al escribiendo lo siguiente:

      fmt.Println(coral[:3])
      

      Con esto se imprimirá lo siguiente:

      Output

      [blue coral foliose coral pillar coral]

      Esto imprimió el principio de la matriz y se detuvo justo antes del índice 3.

      Para incluir todos los elementos al final de una matriz, invertiría la sintaxis:

      fmt.Println(coral[1:])
      

      De esto surgiría el siguiente segmento:

      Output

      [foliose coral pillar coral elkhorn coral]

      En esta sección se explicó la manera de invocar partes individuales de una matriz mediante la eliminación de apartados. A continuación, aprenderá a usar la segmentación para convertir matrices enteras en segmentos.

      Convertir una matriz en un segmento

      Si creó una matriz y decide que necesita que tenga una extensión variable, puede convertirla en un segmento. Para convertir una matriz en un segmento, utilice el proceso de segmentación que aprendió en el paso Dividir matrices en segmentos de este tutorial. Como excepción, esta vez seleccione el segmento completo omitiendo los dos números de índice que determinan los extremos:

      coral[:]
      

      Tenga en cuenta que no puede convertir la variable coral en un segmento en sí, ya que una vez que una variable se define en Go, no se puede cambiar su tipo. Para evitar esto, puede copiar todo el contenido de la matriz en una nueva variable como segmento:

      coralSlice := coral[:]
      

      Si imprimió coralSlice, obtendría el siguiente resultado:

      Output

      [blue coral foliose coral pillar coral elkhorn coral]

      Ahora, intente añadir el elemento black coral como lo hizo en la sección de matrices, usando append() con el segmento recién convertido:

      coralSlice = append(coralSlice, "black coral")
      fmt.Printf("%qn", coralSlice)
      

      Esto mostrará el segmento con el elemento añadido:

      Output

      ["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral"]

      También podemos añadir más de un elemento en una sola instrucción append():

      coralSlice = append(coralSlice, "antipathes", "leptopsammia")
      

      Output

      ["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia"]

      Para combinar dos segmentos, puede usar append(), pero debe ampliar el segundo argumento para la anexión usando la sintaxis de expansión ...:

      moreCoral := []string{"massive coral", "soft coral"}
      coralSlice = append(coralSlice, moreCoral...)
      

      Output

      ["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]

      Ahora que aprendió a anexar un elemento a su segmento, veremos la manera de eliminar uno.

      Eliminar un elemento de un segmento

      A diferencia de otros lenguajes, Go no cuenta con funciones incorporadas para eliminar un elemento de un segmento. Los elementos deben eliminarse de un segmento mediante segmentación.

      Para eliminar un elemento, debe retirar los elementos anteriores y posteriores a este, luego anexar estos dos nuevos segmentos sin el elemento que desee eliminar.

      Si i es el índice del elemento que se eliminará, el formato de este proceso sería el siguiente:

      slice = append(slice[:i], slice[i+1:]...)
      

      De coralSlice, eliminaremos el elemento “elkhorn coral”. Este elemento se encuentra en la posición de índice 3.

      coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}
      
      coralSlice = append(coralSlice[:3], coralSlice[4:]...)
      
      fmt.Printf("%qn", coralSlice)
      

      Output

      ["blue coral" "foliose coral" "pillar coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]

      Ahora, el elemento de la posición de índice 3, la cadena “elkhorn coral”, ya no se encuentra en nuestro segmento coralSlice.

      También podemos eliminar un rango utilizando el mismo enfoque. Supongamos que no solo queremos eliminar el elemento “elkhorn coral”, sino también “ black coral ” y “ antipathes ”. Podemos usar un rango en la expresión para lograr esto:

      coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}
      
      coralSlice = append(coralSlice[:3], coralSlice[6:]...)
      
      fmt.Printf("%qn", coralSlice)
      

      En este código, se eliminarán los índices 3, 4 y 5 del segmento:

      Output

      ["blue coral" "foliose coral" "pillar coral" "leptopsammia" "massive coral" "soft coral"]

      Ahora que sabe añadir elementos y eliminarlos de un segmento, veremos la manera de medir la cantidad de datos que un segmento puede contener en un momento determinado.

      Medir la capacidad de un segmento con cap()

      Debido a que los segmentos tienen una extensión variable, el método len() no es la mejor opción para determinar el tamaño de este tipo de datos. En lugar de esto, puede usar la función cap() para conocer la capacidad de un segmento. Esto le mostrará la cantidad de elementos que un segmento puede contener, lo cual se determina por la cantidad de memoria ya asignada al segmento.

      Nota: Debido a que la extensión y la capacidad de una matriz siempre son iguales, la función cap() no funcionará en matrices.

      Una aplicación común para cap() consiste en crear un segmento con un número de elementos predeterminado y luego completar estos elementos mediante programación. Esto evita asignaciones innecesarias que podrían producirse al usar append() para añadir elementos que superen la capacidad actualmente asignada.

      Consideraremos una situación en la que queramos realizar una lista de números del 0 a 3. Podemos usar append() en un bucle para hacerlo, o primero podemos asignar previamente el segmento y usar cap() en un bulce para llenar los valores.

      Primero, analicemos el uso de append():

      numbers := []int{}
      for i := 0; i < 4; i++ {
          numbers = append(numbers, i)
      }
      fmt.Println(numbers)
      

      Output

      [0 1 2 3]

      En este ejemplo, creamos un segmento y luego un bucle for que haría cuatro iteraciones. Cada iteración anexó el valor actual de la variable del bucle i en el índice del segmento numbers. Sin embargo, esto podría provocar asignaciones de memoria innecesarias que podrían ralentizar su programa. Al añadir a un segmento vacío, cada vez que realice una invocación para una anexión, el programa verificará la capacidad del segmento. Si el elemento añadido hace que el segmento supere esta capacidad, el programa asignará memoria adicional para compensarlo. Esto crea una sobrecarga adicional en su programa y puede ralentizar la ejecución.

      Ahora, completaremos el segmento sin usar append() asignando previamente una extensión y una capacidad determinadas:

      numbers := make([]int, 4)
      for i := 0; i < cap(numbers); i++ {
          numbers[i] = i
      }
      
      fmt.Println(numbers)
      
      

      Output

      [0 1 2 3]

      En este ejemplo, usamos make() para crear un segmento e indicar a este que asigne previamente 4 elementos. Luego usamos la función cap() en el bucle para iterar cada elemento con cero y completar cada uno hasta que cada uno alcanzara la capacidad previamente asignada. En cada bucle, dispusimos el valor actual de la variable de bucle i en el índice del segmento numbers.

      Si bien las estrategias append() y cap() tienen un funcionamiento equivalente, en el ejemplo de cap() se evita cualquier asignación de memoria adicional que hubiera sido necesaria si se utilizaba la función append().

      Crear segmentos multidimensionales

      También puede definir segmentos que consistan en otros segmentos como elementos y disponga cada una de las listas entre llaves, dentro de las llaves más amplias del segmento principal. Los grupos de segmentos como estos se conocen como segmentos multidimensionales. Se puede pensar que representan coordenadas multidimensionales; por ejemplo, un conjunto de cinco segmentos de seis elementos de extensión cada uno podría representar una cuadrícula de bidimensional con una extensión horizontal de cinco y una altura vertical de seis.

      Analicemos el siguiente segmento multidimensional:

      seaNames := [][]string{{"shark", "octopus", "squid", "mantis shrimp"}, {"Sammy", "Jesse", "Drew", "Jamie"}}
      

      Para acceder a un elemento dentro de este segmento, debemos usar varios índices, uno para cada dimensión de la construcción:

      fmt.Println(seaNames[1][0])
      fmt.Println(seaNames[0][0])
      

      En el código anterior, identificamos primero el elemento del índice 0 del segmento del índice 1, luego indicamos el elemento del índice 0 del segmento del índice 0. Con esto, se mostrará lo siguiente:

      Output

      Sammy shark

      Los siguientes son los valores de índice para el resto de los elementos individuales:

      seaNames[0][0] = "shark"
      seaNames[0][1] = "octopus"
      seaNames[0][2] = "squid"
      seaNames[0][3] = "mantis shrimp"
      
      seaNames[1][0] = "Sammy"
      seaNames[1][1] = "Jesse"
      seaNames[1][2] = "Drew"
      seaNames[1][3] = "Jamie"
      

      Al trabajar con segmentos multidimensionales, es importante tener en cuenta que deberá hacer referencia a más de un número de índice para poder acceder a elementos específicos dentro del segmento anidado correspondiente.

      Conclusión

      A través de este tutorial, aprendió aspectos básicos del uso de matrices y segmentos en Go. Realizó varios ejercicios para demostrar que las matrices tienen una extensión fija, mientras que los segmentos no, y descubrió el efecto de esta diferencia en el uso de estas estructuras de datos en cada caso.

      Para continuar estudiando estructuras de datos en Go, consulte nuestro artículo Información sobre mapas en Go o toda la serie Cómo aplicar codificación en Go.



      Source link