¿La forma más eficiente de crear una matriz de JavaScript con relleno cero?


602

¿Cuál es la forma más eficiente de crear una matriz arbitraria de longitud cero en JavaScript?


77
Algunos datos reales sobre esto: jsperf.com/zeroarrayjs
Web_Designer

77
El relleno ES6 permite hacer esto de forma nativa.
Salvador Dali

1
arr = nueva matriz (longitud + 1) .joint (carácter) .split ('');
Jordan Stefanelli

44
ACTUALIZACIÓN 2016 : Otro punto de referencia personalizado aquí: jsfiddle.net/basickarl/md5z0Lqq
K - La toxicidad en SO está creciendo.

1
let i = 0; Array.from(Array(10), ()=>i++);
Bart Hoekstra

Respuestas:


543

ES6 presenta Array.prototype.fill. Se puede usar así:

new Array(len).fill(0);

No estoy seguro si es rápido, pero me gusta porque es breve y se describe a sí mismo.

Todavía no está en IE ( verifique la compatibilidad ), pero hay un polyfill disponible .


15
El relleno es rápido. new Array(len)es dolorosamente lento (arr = []).length = len; arr.fill(0);se trata de la solución más rápida que he visto en cualquier lugar ... o al menos atado
Pimp Trizkit

77
@PimpTrizkit arr = Array(n)y se (arr = []).length = ncomportan de manera idéntica de acuerdo con las especificaciones. En algunas implementaciones, uno podría ser más rápido, pero no creo que haya una gran diferencia.
Oriol

2
Bueno, comencé a probarlo con matrices multidimensionales y pareció acelerar mucho mis casos de prueba. Acabo de hacer algunas pruebas más en FF41 y Chrome45.0.2454.99 m. Sí, supongo que realmente necesitaba más espacio para explicarme. La mayoría de mis pruebas fueron prejuicios, lo admito. Pero mira esto. Predefina un var y use solo esta línea (arr = []).length = 1000; contra la arr = new Array(1000);velocidad, pruébelo tanto en Chrome como en FF ... newes terriblemente lento. Ahora, para longitudes de matriz más pequeñas ... digamos <50 o más o menos ... entonces new Array()parece funcionar mejor. Pero ..
Pimp Trizkit

44
... Admito que me perdí esta parte ... cuando agrego la segunda línea a la prueba ... arr.fill(0) ... todo cambia. Ahora, el uso new Array()es más rápido en la mayoría de los casos, excepto cuando llega a tamaños de matriz> 100000 ... Entonces puede comenzar a ver que la velocidad aumenta nuevamente. Pero si en realidad no tiene que rellenarlo con ceros y puede usar falisy estándar de matrices vacías. Entonces (arr = []).length = xes una locura rápida en mis casos de prueba la mayor parte del tiempo.
Pimp Trizkit

44
Tenga en cuenta que para iterar sobre la matriz (por ejemplo, map o forEach), se deben establecer los valores ; de lo contrario, se omitirán esos índices. Los valores que establezca pueden ser lo que desee, incluso sin definir. Ejemplo: try new Array(5).forEach(val => console.log('hi'));vs new Array(5).fill(undefined).forEach(val => console.log('hi'));.
ArneHugo

387

Aunque este es un hilo viejo, quería agregarle mis 2 centavos. No estoy seguro de lo lento / rápido que es esto, pero es rápido. Esto es lo que hago:

Si quiero rellenar previamente con un número:

Array.apply(null, Array(5)).map(Number.prototype.valueOf,0);
// [0, 0, 0, 0, 0]

Si quiero rellenar previamente con una cadena:

Array.apply(null, Array(3)).map(String.prototype.valueOf,"hi")
// ["hi", "hi", "hi"]

Otras respuestas han sugerido:

new Array(5+1).join('0').split('')
// ["0", "0", "0", "0", "0"]

pero si quieres 0 (el número) y no "0" (cero dentro de una cadena), puedes hacer:

new Array(5+1).join('0').split('').map(parseFloat)
// [0, 0, 0, 0, 0]

66
¡Gran respuesta! ¿Puedes explicar el truco con él Array.apply(null, new Array(5)).map(...)? Porque simplemente hacer (nueva matriz (5)). Map (...) no funcionará como dice la especificación
Dmitry Pashkevich

36
(por cierto, realmente no necesitamos new) Cuando lo haces Array(5), estás creando un objeto que se parece a esto: { length: 5, __proto__: Array.prototype }- inténtalo console.dir( Array(5) ). Tenga en cuenta que no tiene ningún propiedades 0, 1, 2, etc, pero cuando applyque hasta el Arrayconstructor, que es como decir Array(undefined, undefined, undefined, undefined, undefined). Y obtienes un objeto que se parece un poco { length: 5, 0: undefined, 1: undefined...}. mapfunciona en las propiedades 0, 1etc., por eso su ejemplo no funciona, pero cuando lo usa applysí lo hace.
zertosh

44
El primer parámetro para .applyes en realidad lo que quieres thisque sea. Para estos fines this, no importa, solo nos importa realmente la "característica" de difusión de parámetros de .apply, por lo que puede ser cualquier valor. Me gusta nullporque es barato, probablemente no quieras usarlo {}o []porque estarías creando una instancia de un objeto sin ningún motivo.
zertosh

2
También inicializar con tamaño + asignar es mucho más rápido que presionar. Ver caso de prueba jsperf.com/zero-fill-2d-array
Colin

2
¿Qué pasa con Array.apply (null, Array (5)). map (x => 0)? ¡Es un poco más corto!
Arch Linux Tux

97

Forma elegante de llenar una matriz con valores calculados previamente

Aquí hay otra forma de hacerlo usando ES6 que nadie ha mencionado hasta ahora:

> Array.from(Array(3), () => 0)
< [0, 0, 0]

Funciona pasando una función de mapa como el segundo parámetro de Array.from.

En el ejemplo anterior, el primer parámetro asigna una matriz de 3 posiciones llenas con el valor undefinedy luego la función lambda asigna cada una de ellas al valor 0.

Aunque Array(len).fill(0)es más corto, no funciona si necesita completar la matriz haciendo un cálculo primero (sé que la pregunta no lo hizo, pero mucha gente termina buscando esto) .

Por ejemplo, si necesita una matriz con 10 números aleatorios:

> Array.from(Array(10), () => Math.floor(10 * Math.random()))
< [3, 6, 8, 1, 9, 3, 0, 6, 7, 1]

Es más conciso (y elegante) que el equivalente:

const numbers = Array(10);
for (let i = 0; i < numbers.length; i++) {
    numbers[i] = Math.round(10 * Math.random());
}

Este método también se puede utilizar para generar secuencias de números aprovechando el parámetro de índice proporcionado en la devolución de llamada:

> Array.from(Array(10), (d, i) => i)
< [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Respuesta extra: llenar una matriz usando String repeat()

Como esta respuesta está recibiendo mucha atención, también quería mostrar este truco genial. Aunque no es tan útil como mi respuesta principal, presentaré el repeat()método String aún no muy conocido pero muy útil . Aquí está el truco:

> "?".repeat(10).split("").map(() => Math.floor(10 * Math.random()))
< [5, 6, 3, 5, 0, 8, 2, 7, 4, 1]

Genial, ¿eh? repeat()es un método muy útil para crear una cadena que es la repetición de la cadena original un cierto número de veces. Después de eso, split()crea una matriz para nosotros, que luego es map()pedida a los valores que queremos. Desglosándolo en pasos:

> "?".repeat(10)
< "??????????"

> "?".repeat(10).split("")
< ["?", "?", "?", "?", "?", "?", "?", "?", "?", "?"]

> "?".repeat(10).split("").map(() => Math.floor(10 * Math.random()))
< [5, 6, 3, 5, 0, 8, 2, 7, 4, 1]

Muchos trucos de salón en esa publicación, pero espero que ninguno llegue al código de producción :)
Eric Grange

Aunque el repeattruco definitivamente no es deseado en la producción, Array.from()está perfectamente bien :-)
Lucio Paiva

En realidad no, Array.from () aquí básicamente está creando una matriz, iterando a través de ella con map (), llamando a una función en cada elemento para crear una nueva matriz, luego descartando la primera matriz ... Para una matriz pequeña esto puede ser inocuo, para matrices más grandes, este es el tipo de patrón que hace que las personas llamen a los navegadores "memoria" :)
Eric Grange

Las personas que trabajan con matrices grandes deberían saberlo mejor que esto, definitivamente. Sin embargo, para aplicaciones comunes, crear una matriz auxiliar de tamaño regular (hasta 10k elementos) que se eliminará de inmediato está perfectamente bien (lleva la misma cantidad de tiempo que si evitara la creación de matriz adicional, probada con la última versión de Chrome). Para casos como ese, la legibilidad se vuelve más importante que las pequeñas optimizaciones de rendimiento. Sobre el tiempo O (n), es necesario si necesita calcular algo diferente para cada elemento (el tema principal de mi respuesta). Esta discusión es muy interesante, me alegro de que la hayas planteado
Lucio Paiva

88

En breve

La solución más rápida

let a = new Array(n); for (let i=0; i<n; ++i) a[i] = 0;

La solución más corta (práctica) (3 veces más lenta para matrices pequeñas, ligeramente más lenta para grandes (más lenta en Firefox))

Array(n).fill(0)


Detalles

Hoy 2020.06.09 realizo pruebas en macOS High Sierra 10.13.6 en los navegadores Chrome 83.0, Firefox 77.0 y Safari 13.1. Pruebo las soluciones elegidas para dos casos de prueba

  • matriz pequeña, con 10 elementos, puede realizar la prueba AQUÍ
  • grandes matrices, con elementos 1M, puede realizar la prueba AQUÍ

Conclusiones

  • La solución basada en new Array(n)+for(N) es la solución más rápida para matrices pequeñas y matrices grandes (excepto Chrome, pero todavía es muy rápida allí) y se recomienda como solución rápida de navegador cruzado
  • la solución basada en new Float32Array(n)(I) devuelve una matriz no típica (p. ej., no se puede llamar push(..)), por lo que no comparo sus resultados con otras soluciones; sin embargo, esta solución es aproximadamente 10-20 veces más rápida que otras soluciones para matrices grandes en todos los navegadores
  • Las soluciones basadas en for(L, M, N, O) son rápidas para arreglos pequeños
  • Las soluciones basadas en fill(B, C) son rápidas en Chrome y Safari pero sorprendentemente más lentas en Firefox para grandes matrices. Son medianamente rápidos para matrices pequeñas.
  • solución basada en Array.apply(P) arroja error para grandes matrices

ingrese la descripción de la imagen aquí

Código y ejemplo

El siguiente código presenta soluciones utilizadas en mediciones

Resultados de ejemplo para Chrome

ingrese la descripción de la imagen aquí


Acabo de ejecutar algunas pruebas en Chrome 77 y un bucle simple con push () es dos veces más rápido que fill () ... Me pregunto qué sutiles efectos secundarios de fill () impiden una implementación más eficiente.
Eric Grange

@EricGrange Actualizo la respuesta: en la parte inferior, actualizo el enlace a benchamrk con su propuesta: caso P let a=[]; for(i=n;i--;) a.push(0);, pero es 4 veces más lento que fill(0), por lo que ni siquiera actualizaré la imagen de ese caso.
Kamil Kiełczewski

2
Buenas medidas. Análisis: G es lento debido al cambio de tamaño de la matriz en cada iteración, y cambiar el tamaño significa hacer una nueva asignación de memoria. A, B, M rápido porque el dimensionamiento se realiza solo una vez. +1
Roland

63

El método de llenado ES 6 ya mencionado se encarga de esto muy bien. La mayoría de los navegadores de escritorio modernos ya son compatibles con los métodos prototipo de matriz requeridos a partir de hoy (Chromium, FF, Edge y Safari) [ 1 ]. Puede buscar detalles en MDN . Un ejemplo de uso simple es

a = new Array(10).fill(0);

Dado el soporte actual del navegador, debe tener cuidado de usarlo a menos que esté seguro de que su audiencia usa navegadores de escritorio modernos.


44
Si rellena con un tipo de referencia, será la misma referencia en todos ellos. new Array (10) .fill (null) .map (() => []) sería una forma sucinta de
solucionar

44
ACTUALIZACIÓN 2016 : este método elimina todo lo demás fuera del agua, haga clic aquí para obtener los puntos de referencia: jsfiddle.net/basickarl/md5z0Lqq
K - La toxicidad en SO está creciendo.

esto funcionará para matrices. a = Array(10).fill(null).map(() => { return []; });
Andy

2
@AndrewAnthonyGerst Terser:a = Array(10).fill(0).map( _ => [] );
Phrogz

50

Nota agregada en agosto de 2013, actualizada en febrero de 2015: la respuesta a continuación de 2009 se refiere al Arraytipo genérico de JavaScript . No se relaciona con las matrices tipadas más nuevas definidas en ES2015 [y disponibles ahora en muchos navegadores], me gusta Int32Arrayy tal. También tenga en cuenta que ES2015 agrega un fillmétodo para ambas matrices y matrices con tipo , que es probable que sea la forma más eficiente para llenarlos ...

Además, puede hacer una gran diferencia para algunas implementaciones en la forma de crear la matriz. El motor V8 de Chrome, en particular, intenta usar una matriz de memoria contigua altamente eficiente si cree que puede hacerlo, cambiando a la matriz basada en objetos solo cuando es necesario.


Con la mayoría de los idiomas, sería preasignar, luego rellenar con cero, así:

function newFilledArray(len, val) {
    var rv = new Array(len);
    while (--len >= 0) {
        rv[len] = val;
    }
    return rv;
}

Pero , las matrices de JavaScript no son realmente matrices , son mapas de clave / valor al igual que todos los demás objetos de JavaScript, por lo que no hay que "preasignar" (establecer la longitud no asigna tantas ranuras para llenar), ni ¿Hay alguna razón para creer que el beneficio de contar regresivamente a cero (que es solo para hacer una comparación rápida en el bucle) no se supera al agregar las claves en orden inverso cuando la implementación puede haber optimizado su manejo de las claves relacionado con las matrices sobre la teoría, generalmente las hará en orden.

De hecho, Matthew Crumley señaló que la cuenta regresiva es notablemente más lenta en Firefox que la cuenta regresiva, un resultado que puedo confirmar: es la parte de la matriz (el bucle descendente a cero es aún más rápido que el bucle hasta un límite en una var). Aparentemente, agregar los elementos a la matriz en orden inverso es una operación lenta en Firefox. De hecho, los resultados varían bastante según la implementación de JavaScript (que no es tan sorprendente). Aquí hay una página de prueba rápida y sucia (a continuación) para las implementaciones del navegador (muy sucia, no rinde durante las pruebas, por lo que proporciona una respuesta mínima y va en contra de los límites de tiempo del script) Recomiendo refrescarse entre pruebas; FF (al menos) se ralentiza en las pruebas repetidas si no lo hace.

La versión bastante complicada que usa Array # concat es más rápida que un init directo en FF a partir de entre 1,000 y 2,000 arreglos de elementos. Sin embargo, en el motor V8 de Chrome, el init directo gana cada vez ...

Aquí está la página de prueba ( copia en vivo ):

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Zero Init Test Page</title>
<style type='text/css'>
body {
    font-family:    sans-serif;
}
#log p {
    margin:     0;
    padding:    0;
}
.error {
    color:      red;
}
.winner {
    color:      green;
    font-weight:    bold;
}
</style>
<script type='text/javascript' src='prototype-1.6.0.3.js'></script>
<script type='text/javascript'>
var testdefs = {
    'downpre':  {
        total:  0,
        desc:   "Count down, pre-decrement",
        func:   makeWithCountDownPre
    },
    'downpost': {
        total:  0,
        desc:   "Count down, post-decrement",
        func:   makeWithCountDownPost
    },
    'up':       {
        total:  0,
        desc:   "Count up (normal)",
        func:   makeWithCountUp
    },
    'downandup':  {
        total:  0,
        desc:   "Count down (for loop) and up (for filling)",
        func:   makeWithCountDownArrayUp
    },
    'concat':   {
        total:  0,
        desc:   "Concat",
        func:   makeWithConcat
    }
};

document.observe('dom:loaded', function() {
    var markup, defname;

    markup = "";
    for (defname in testdefs) {
        markup +=
            "<div><input type='checkbox' id='chk_" + defname + "' checked>" +
            "<label for='chk_" + defname + "'>" + testdefs[defname].desc + "</label></div>";
    }
    $('checkboxes').update(markup);
    $('btnTest').observe('click', btnTestClick);
});

function epoch() {
    return (new Date()).getTime();
}

function btnTestClick() {

    // Clear log
    $('log').update('Testing...');

    // Show running
    $('btnTest').disabled = true;

    // Run after a pause while the browser updates display
    btnTestClickPart2.defer();
}
function btnTestClickPart2() {

    try {
        runTests();
    }
    catch (e) {
        log("Exception: " + e);
    }

    // Re-enable the button; we don't yheidl
    $('btnTest').disabled = false;
}

function runTests() {
    var start, time, counter, length, defname, def, results, a, invalid, lowest, s;

    // Get loops and length
    s = $F('txtLoops');
    runcount = parseInt(s);
    if (isNaN(runcount) || runcount <= 0) {
        log("Invalid loops value '" + s + "'");
        return;
    }
    s = $F('txtLength');
    length = parseInt(s);
    if (isNaN(length) || length <= 0) {
        log("Invalid length value '" + s + "'");
        return;
    }

    // Clear log
    $('log').update('');

    // Do it
    for (counter = 0; counter <= runcount; ++counter) {

        for (defname in testdefs) {
            def = testdefs[defname];
            if ($('chk_' + defname).checked) {
                start = epoch();
                a = def.func(length);
                time = epoch() - start;
                if (counter == 0) {
                    // Don't count (warm up), but do check the algorithm works
                    invalid = validateResult(a, length);
                    if (invalid) {
                        log("<span class='error'>FAILURE</span> with def " + defname + ": " + invalid);
                        return;
                    }
                }
                else {
                    // Count this one
                    log("#" + counter + ": " + def.desc + ": " + time + "ms");
                    def.total += time;
                }
            }
        }
    }

    for (defname in testdefs) {
        def = testdefs[defname];
        if ($('chk_' + defname).checked) {
            def.avg = def.total / runcount;
            if (typeof lowest != 'number' || lowest > def.avg) {
                lowest = def.avg;
            }
        }
    }

    results =
        "<p>Results:" +
        "<br>Length: " + length +
        "<br>Loops: " + runcount +
        "</p>";
    for (defname in testdefs) {
        def = testdefs[defname];
        if ($('chk_' + defname).checked) {
            results += "<p" + (lowest == def.avg ? " class='winner'" : "") + ">" + def.desc + ", average time: " + def.avg + "ms</p>";
        }
    }
    results += "<hr>";
    $('log').insert({top: results});
}

function validateResult(a, length) {
    var n;

    if (a.length != length) {
        return "Length is wrong";
    }
    for (n = length - 1; n >= 0; --n) {
        if (a[n] != 0) {
            return "Index " + n + " is not zero";
        }
    }
    return undefined;
}

function makeWithCountDownPre(len) {
    var a;

    a = new Array(len);
    while (--len >= 0) {
        a[len] = 0;
    }
    return a;
}

function makeWithCountDownPost(len) {
    var a;

    a = new Array(len);
    while (len-- > 0) {
        a[len] = 0;
    }
    return a;
}

function makeWithCountUp(len) {
    var a, i;

    a = new Array(len);
    for (i = 0; i < len; ++i) {
        a[i] = 0;
    }
    return a;
}

function makeWithCountDownArrayUp(len) {
    var a, i;

    a = new Array(len);
    i = 0;
    while (--len >= 0) {
        a[i++] = 0;
    }
    return a;
}

function makeWithConcat(len) {
    var a, rem, currlen;

    if (len == 0) {
        return [];
    }
    a = [0];
    currlen = 1;
    while (currlen < len) {
        rem = len - currlen;
        if (rem < currlen) {
            a = a.concat(a.slice(0, rem));
        }
        else {
            a = a.concat(a);
        }
        currlen = a.length;
    }
    return a;
}

function log(msg) {
    $('log').appendChild(new Element('p').update(msg));
}
</script>
</head>
<body><div>
<label for='txtLength'>Length:</label><input type='text' id='txtLength' value='10000'>
<br><label for='txtLoops'>Loops:</label><input type='text' id='txtLoops' value='10'>
<div id='checkboxes'></div>
<br><input type='button' id='btnTest' value='Test'>
<hr>
<div id='log'></div>
</div></body>
</html>

No estoy seguro de que el relleno hacia atrás sea importante aquí, dado que solo está accediendo a elementos (no eliminándolos) y ya ha asignado previamente. ¿Me equivoco?
Tríptico

el punto del relleno hacia atrás no tiene que ver particularmente con la matriz, tiene que ver con la condición de escape por un tiempo - el falsey 0 termina el ciclo de manera muy eficiente
annakata

(aunque he acabo de dar cuenta este código en realidad no hacer uso de esa)
annakata

@annakata, no puedes usar eso aquí, porque 0 es un índice válido.
Tríptico

@triptych: no es cierto, todo lo que se necesita es el orden correcto - mira mi publicación
annakata

34

Por defecto Uint8Array, Uint16Arrayy las Uint32Arrayclases mantienen ceros como sus valores, por lo que no necesita ninguna técnica de relleno compleja, simplemente haga lo siguiente:

var ary = new Uint8Array(10);

Todos los elementos de la matriz aryserán ceros por defecto.


55
Esto es bueno, pero tenga en cuenta que esto no puede tratarse de la misma manera que una matriz normal, por ejemplo, Array.isArray(ary)es false. La longitud también es de sólo lectura por lo que no se puede empujar nuevos elementos a ella al igual que conary.push
MusikAnimal

Fwiw todas las matrices escritas se mantienen 0como su valor predeterminado.
jfunk

2
@MusikAnimal, Array.from(new Uint8Array(10))proporcionará una matriz normal.
Tomas Langkaas

@TomasLangkaas: Sí, pero otra respuesta muestra que eso es aproximadamente 5 veces más lento que Array(n).fill(0)en Chrome si lo que realmente necesita es una matriz JS. Sin embargo, si puede usar un TypedArray, esto es mucho más rápido .fill(0), especialmente si puede usar el valor inicializador predeterminado de 0. No parece haber un constructor que tome un valor de relleno y una longitud, como lo hizo C ++ std::vector. Parece que para cualquier valor distinto de cero, tiene que construir un TypedArray a cero y luego llenarlo. : /
Peter Cordes

29

Si usa ES6, puede usar Array.from () de esta manera:

Array.from({ length: 3 }, () => 0);
//[0, 0, 0]

Tiene el mismo resultado que

Array.from({ length: 3 }).map(() => 0)
//[0, 0, 0]

Porque

Array.from({ length: 3 })
//[undefined, undefined, undefined]

23
function makeArrayOf(value, length) {
  var arr = [], i = length;
  while (i--) {
    arr[i] = value;
  }
  return arr;
}

makeArrayOf(0, 5); // [0, 0, 0, 0, 0]

makeArrayOf('x', 3); // ['x', 'x', 'x']

Tenga en cuenta que whilees por lo general más eficientes que for-in, forEach, etc.


3
¿No es iextraña la variable local? lengthse pasa por valor, por lo que debería poder disminuirlo directamente.
Sean Bright

3
Aunque esto se ve muy bien al principio, desafortunadamente es muy lento asignar valores en un punto arbitrario de un archivo (por ejemplo arr[i] = value). Es mucho más rápido recorrer de principio a fin y usar arr.push(value). Es molesto, porque prefiero tu método.
Nick Brunt

19

usando notación de objeto

var x = [];

cero lleno? me gusta...

var x = [0,0,0,0,0,0];

lleno de 'indefinido' ...

var x = new Array(7);

notación obj con ceros

var x = [];
for (var i = 0; i < 10; i++) x[i] = 0;

Como nota al margen, si modifica el prototipo de Array, ambos

var x = new Array();

y

var y = [];

tendrá esas modificaciones prototipo

En cualquier caso, no me preocuparía demasiado la eficiencia o la velocidad de esta operación, hay muchas otras cosas que probablemente harás que son mucho más derrochadoras y costosas que instanciar una variedad de longitud arbitraria que contiene ceros.


55
Err ... no hay nullvar x = new Array(7);
mensajes de correo electrónico

55
En realidad, la matriz no se llena con nada con la nueva matriz (n), ni siquiera 'indefinida', simplemente establece el valor de longitud de la matriz en n. Puede verificar esto llamando a (new Array (1)). ForEach (...). forEach nunca se ejecuta, a diferencia de si lo invoca [undefined].
JussiR

44
new Array(7)no no crear una matriz "lleno indefinido". Crea una matriz vacía con longitud 7.
RobG

1
Es posible que desee reconsiderar partes de su respuesta, ya que lo que dice @RobG es crítico (si lo que decía es cierto, el mapeo hubiera sido mucho más fácil)
Abdo

1
En estos días podrías hacer (new Array(10)).fill(0).
Javier de la Rosa

18

He probado todas las combinaciones de preasignación / no preasignación, conteo ascendente / descendente y bucles for / while en IE 6/7/8, Firefox 3.5, Chrome y Opera.

Las funciones a continuación fueron consistentemente las más rápidas o extremadamente cercanas en Firefox, Chrome e IE8, y no mucho más lentas que las más rápidas en Opera e IE 6. También es la más simple y clara en mi opinión. He encontrado varios navegadores donde la versión del bucle while es un poco más rápida, por lo que también la incluyo como referencia.

function newFilledArray(length, val) {
    var array = [];
    for (var i = 0; i < length; i++) {
        array[i] = val;
    }
    return array;
}

o

function newFilledArray(length, val) {
    var array = [];
    var i = 0;
    while (i < length) {
        array[i++] = val;
    }
    return array;
}

1
También podría lanzar la var array = []declaración en la primera parte del bucle for en realidad, separada por solo una coma.
damianb

Me gusta la sugerencia de damianb, ¡pero recuerda poner la asignación y la coma antes del incremento! `for (var i = 0; i <length; array [i] = val, i ++);
Punstress

Haga lo que todos los demás le faltan a su segundo, y establezca la longitud de la matriz en el lengthvalor dado para que no cambie constantemente. Trají una matriz de ceros de 1 millón de longitud de 40ms a 8 en mi máquina.
Jonathan Gray

Parece que obtengo un aumento de velocidad del 10-15% cuando refactorizo ​​esta solución en una sola línea. for (i = 0, array = []; i < length; ++i) array[i] = val;¿Menos bloques? ... de todos modos, también ... si configuro la array.lengthnueva matriz a la longitud ... parece que obtengo otro aumento de velocidad del 10% -15% en FF ... en Chrome, parece duplicar la velocidad -> var i, array = []; array.length = length; while(i < length) array[i++] = val;(todavía era más rápido si lo dejaba como un forbucle ... pero el init ya no es necesario, por lo whileque parece ser más rápido en esta versión)
Pimp Trizkit

También lo notaré en mis pruebas. En un número decente de mis casos de prueba, la versión final anterior parece funcionar 3 veces a más de 10 veces más rápido ... No estoy tan seguro de por qué ... (diferentes tamaños de matriz probados entre cromo y FF)
Pimp Trizkit

13

Si necesita crear muchas matrices llenas de cero de diferentes longitudes durante la ejecución de su código, la forma más rápida que he encontrado para lograr esto es crear una matriz cero una vez , usando uno de los métodos mencionados en este tema, de una longitud que sabe que nunca se excederá, y luego corte esa matriz según sea necesario.

Por ejemplo (usando la función de la respuesta elegida arriba para inicializar la matriz), cree una matriz llena de cero de longitud maxLength , como una variable visible para el código que necesita cero matrices:

var zero = newFilledArray(maxLength, 0);

Ahora corte esta matriz cada vez que necesite una matriz llena de ceros de longitud requeridaLongitud < maxLongitud :

zero.slice(0, requiredLength);

Creé miles de arrays llenos miles de veces durante la ejecución de mi código, lo que aceleró enormemente el proceso.


13
function zeroFilledArray(size) {
    return new Array(size + 1).join('0').split('');
}

3
También puede usar new Array(size+1).join("x").split("x").map(function() { return 0; })para obtener números reales
Yuval

66
@Yuval Or justnew Array(size+1).join('0').split('').map(Number)
Paul

11

No tengo nada en contra:

Array.apply(null, Array(5)).map(Number.prototype.valueOf,0);
new Array(5+1).join('0').split('').map(parseFloat);

sugerido por Zertosh, pero en una nueva matriz de ES6, las extensiones le permiten hacer esto de forma nativa con el fillmétodo. Ahora IE edge, Chrome y FF lo admiten, pero consulte la tabla de compatibilidad

new Array(3).fill(0)le dará [0, 0, 0]. Puede llenar la matriz con cualquier valor como new Array(5).fill('abc')(incluso objetos y otras matrices).

Además de eso, puede modificar matrices anteriores con relleno:

arr = [1, 2, 3, 4, 5, 6]
arr.fill(9, 3, 5)  # what to fill, start, end

que te da: [1, 2, 3, 9, 9, 6]


10

La forma en que generalmente lo hago (y es increíblemente rápido) es usar Uint8Array. Por ejemplo, crear un vector lleno de cero de elementos 1M:

  var zeroFilled = [].slice.apply(new Uint8Array(1000000))

Soy un usuario de Linux y siempre he trabajado para mí, pero una vez que un amigo que usaba una Mac tenía algunos elementos distintos de cero. Pensé que su máquina no funcionaba bien, pero aún así esta es la forma más segura que encontramos para solucionarlo:

  var zeroFilled = [].slice.apply(new Uint8Array(new Array(1000000)) 

Editado

Chrome 25.0.1364.160

  1. Frederik Gottlieb - 6.43
  2. Sam Barnum - 4,83
  3. Eli - 3.68
  4. Josué 2.91
  5. Mathew Crumley - 2,67
  6. bduran - 2.55
  7. Arroz Allen - 2.11
  8. kangax - 0.68
  9. Tj. Crowder - 0.67
  10. zertosh - ERROR

Firefox 20.0

  1. Arroz Allen - 1.85
  2. Joshua - 1.82
  3. Mathew Crumley - 1.79
  4. bduran - 1.37
  5. Frederik Gottlieb - 0.67
  6. Sam Barnum - 0.63
  7. Eli - 0.59
  8. kagax - 0.13
  9. Tj. Crowder - 0.13
  10. zertosh - ERROR

Falta la prueba más importante (al menos para mí): la Node.js. Sospecho que está cerca del punto de referencia de Chrome.


Esta es la forma más eficiente para mis dedos y mis ojos. Pero es muy, muy lento para Chrome (de acuerdo con ese jsperf. 99% más lento).
Orwellophile

1
Me pregunto si el problema en la Mac de su amigo estaba relacionado con: stackoverflow.com/questions/39129200/… o tal vez Array.slice no estaba manejando el UInt8Array y perdía memoria no inicializada. (¡Un problema de seguridad!).
robocat el

@robocat Buena captura! Si lo recuerdo bien, estábamos usando Node.js 0.6 o 0.8. Pensamos en algún tipo de fuga, pero no pudimos reproducirlo con la pila de producción, así que decidimos ignorarlo.
Durm

10

Usando lodash o guión bajo

_.range(0, length - 1, 0);

O si tiene una matriz existente y desea una matriz de la misma longitud

array.map(_.constant(0));

Me alegra que hayas agregado esta respuesta, ya que uso guión bajo, y sabía que había algo para esto ... pero aún no había podido encontrarlo. Solo desearía poder crear matrices de objetos usando esto
PandaWood

@PandaWood _.range (0, length -1, 0) .map (Object.new), creo.
djechlin

Debería ser _.range(0, length, 0), creo. Lodash es exclusivo del valor final
user4815162342

9

Solución ES6:

[...new Array(5)].map(x => 0); // [0, 0, 0, 0, 0]

8

A partir de ECMAScript2016 , existe una opción clara para matrices grandes.

Dado que esta respuesta todavía aparece cerca de la parte superior en las búsquedas de Google, aquí hay una respuesta para 2017.

Aquí hay un jsbench actual con algunas docenas de métodos populares, incluidos muchos propuestos hasta ahora sobre esta pregunta. Si encuentra un método mejor, agregue, bifurque y comparta.

Quiero señalar que no existe una forma más eficiente de crear una matriz arbitraria de longitud cero. Puede optimizar la velocidad o la claridad y la facilidad de mantenimiento; puede considerarse la opción más eficiente según las necesidades del proyecto.

Al optimizar la velocidad, desea: crear la matriz usando sintaxis literal; establece la longitud, inicializa la variable iterativa e itera a través de la matriz usando un ciclo while. Aquí hay un ejemplo.

const arr = [];
arr.length = 120000;
let i = 0;
while (i < 120000) {
  arr[i] = 0;
  i++;
}

Otra posible implementación sería:

(arr = []).length = n;
let i = 0;
while (i < n) {
    arr[i] = 0;
    i++;
}

Pero desaconsejo encarecidamente usar esta segunda implantación en la práctica, ya que es menos clara y no le permite mantener el alcance del bloque en su variable de matriz.

Estos son significativamente más rápidos que el llenado con un bucle for, y aproximadamente un 90% más rápido que el método estándar de

const arr = Array(n).fill(0);

Pero este método de relleno sigue siendo la opción más eficiente para matrices más pequeñas debido a su claridad, concisión y facilidad de mantenimiento. La diferencia de rendimiento probablemente no lo matará a menos que esté haciendo muchos arreglos con longitudes del orden de miles o más.

Algunas otras notas importantes. La mayoría de las guías de estilo recomiendan que ya no se use varsin una razón muy especial cuando se usa ES6 o posterior. Úselo constpara variables que no se redefinirán y letpara variables que sí lo serán. El MDN y la Guía de estilo de Airbnb son excelentes lugares para obtener más información sobre las mejores prácticas. Las preguntas no se referían a la sintaxis, pero es importante que las personas nuevas en JS conozcan estos nuevos estándares cuando busquen en estas resmas de respuestas antiguas y nuevas.


8

Para crear una matriz completamente nueva

new Array(arrayLength).fill(0);

Para agregar algunos valores al final de una matriz existente

[...existingArray, ...new Array(numberOfElementsToAdd).fill(0)]

Ejemplo

//**To create an all new Array**

console.log(new Array(5).fill(0));

//**To add some values at the end of an existing Array**

let existingArray = [1,2,3]

console.log([...existingArray, ...new Array(5).fill(0)]);


6

No vi este método en las respuestas, así que aquí está:

"0".repeat( 200 ).split("").map( parseFloat )

Como resultado, obtendrá una matriz de longitud 200 de valor cero:

[ 0, 0, 0, 0, ... 0 ]

No estoy seguro del rendimiento de este código, pero no debería ser un problema si lo usa para arreglos relativamente pequeños.


55
Ni el más rápido ni el más corto, sino una buena contribución a la diversidad de soluciones.
7vujy0f0hy


4

Esta concatversión es mucho más rápida en mis pruebas en Chrome (2013-03-21). Aproximadamente 200 ms para 10,000,000 elementos vs 675 para init directo.

function filledArray(len, value) {
    if (len <= 0) return [];
    var result = [value];
    while (result.length < len/2) {
        result = result.concat(result);
    }
    return result.concat(result.slice(0, len-result.length));
}

Bonificación: si desea llenar su matriz con cadenas, esta es una forma concisa de hacerlo (no tan rápido como concatsi fuera):

function filledArrayString(len, value) {
    return new Array(len+1).join(value).split('');
}

2
Ok, salvaje Eso es MUCHO más rápido que usar una nueva matriz (len). ¡PERO! Veo en Chrome que las lecturas posteriores de esos datos llevan mucho más tiempo. Aquí hay algunas marcas de tiempo para mostrar lo que quiero decir: (usando una nueva matriz (len)) 0.365: haciendo una matriz 4.526: ejecutando una convolución 10.75: completando una convolución (usando concat) 0.339: haciendo una matriz 0.591: ejecutando una convolución // OMG, WAY más rápido 18.056: Convolución completa
Brooks

4

Estaba probando la gran respuesta de TJ Crowder, y se me ocurrió una fusión recursiva basada en la solución concat que supera a cualquiera de sus pruebas en Chrome (no probé otros navegadores).

function makeRec(len, acc) {
    if (acc == null) acc = [];
    if (len <= 1) return acc;
    var b = makeRec(len >> 1, [0]);
    b = b.concat(b);
    if (len & 1) b = b.concat([0]);
    return b;
},

llama al método con makeRec(29).



4

Vale la pena señalar que se Array.prototype.fillhabía agregado como parte de la propuesta ECMAScript 6 (Armonía) . Prefiero ir con el polyfill escrito a continuación, antes de considerar otras opciones mencionadas en el hilo.

if (!Array.prototype.fill) {
  Array.prototype.fill = function(value) {

    // Steps 1-2.
    if (this == null) {
      throw new TypeError('this is null or not defined');
    }

    var O = Object(this);

    // Steps 3-5.
    var len = O.length >>> 0;

    // Steps 6-7.
    var start = arguments[1];
    var relativeStart = start >> 0;

    // Step 8.
    var k = relativeStart < 0 ?
      Math.max(len + relativeStart, 0) :
      Math.min(relativeStart, len);

    // Steps 9-10.
    var end = arguments[2];
    var relativeEnd = end === undefined ?
      len : end >> 0;

    // Step 11.
    var final = relativeEnd < 0 ?
      Math.max(len + relativeEnd, 0) :
      Math.min(relativeEnd, len);

    // Step 12.
    while (k < final) {
      O[k] = value;
      k++;
    }

    // Step 13.
    return O;
  };
}

4

El más corto para el código de bucle

a=i=[];for(;i<100;)a[i++]=0;

edit:
for(a=i=[];i<100;)a[i++]=0;
or
for(a=[],i=100;i--;)a[i]=0;

Versión var segura

var a=[],i=0;for(;i<100;)a[i++]=0;

edit:
for(var i=100,a=[];i--;)a[i]=0;

2
Dado que la longitud es una variable definida n, esta sería más corta:for(var a=[];n--;a[n]=0);
Tomas Langkaas


3

Mi función más rápida sería:

function newFilledArray(len, val) {
    var a = [];
    while(len--){
        a.push(val);
    }
    return a;
}

var st = (new Date()).getTime();
newFilledArray(1000000, 0)
console.log((new Date()).getTime() - st); // returned 63, 65, 62 milliseconds

Usar el método push y shift nativo para agregar elementos a la matriz es mucho más rápido (aproximadamente 10 veces) que declarar el alcance de la matriz y hacer referencia a cada elemento para establecer su valor.

FYI: constantemente obtengo tiempos más rápidos con el primer ciclo, que es una cuenta regresiva, cuando ejecuto esto en firebug (extensión de firefox).

var a = [];
var len = 1000000;
var st = (new Date()).getTime();
while(len){
    a.push(0);
    len -= 1;
}
console.log((new Date()).getTime() - st); // returned 863, 894, 875 milliseconds
st = (new Date()).getTime();
len = 1000000;
a = [];
for(var i = 0; i < len; i++){
    a.push(0);
}
console.log((new Date()).getTime() - st); // returned 1155, 1179, 1163 milliseconds

¿Me interesa saber qué hace TJ Crowder con eso? :-)


Puede hacerlo más rápido cambiándolo a while (len--)... tomó mis tiempos de procesamiento de aproximadamente 60 ms a aproximadamente 54 ms
nickf

¡La respuesta de Matthew Crumbly todavía supera esto (30ms)!
nickf

3

Sabía que tenía este protocolo en alguna parte :)

Array.prototype.init = function(x,n)
{
    if(typeof(n)=='undefined') { n = this.length; }
    while (n--) { this[n] = x; }
    return this;
}

var a = (new Array(5)).init(0);

var b = [].init(0,4);

Editar: pruebas

En respuesta a Joshua y otros métodos, realicé mi propia evaluación comparativa y estoy viendo resultados completamente diferentes a los reportados.

Esto es lo que probé:

//my original method
Array.prototype.init = function(x,n)
{
    if(typeof(n)=='undefined') { n = this.length; }
    while (n--) { this[n] = x; }
    return this;
}

//now using push which I had previously thought to be slower than direct assignment
Array.prototype.init2 = function(x,n)
{
    if(typeof(n)=='undefined') { n = this.length; }
    while (n--) { this.push(x); }
    return this;
}

//joshua's method
function newFilledArray(len, val) {
    var a = [];
    while(len--){
        a.push(val);
    }
    return a;
}

//test m1 and m2 with short arrays many times 10K * 10

var a = new Date();
for(var i=0; i<10000; i++)
{
    var t1 = [].init(0,10);
}
var A = new Date();

var b = new Date();
for(var i=0; i<10000; i++)
{
    var t2 = [].init2(0,10);
}
var B = new Date();

//test m1 and m2 with long array created once 100K

var c = new Date();
var t3 = [].init(0,100000);
var C = new Date();

var d = new Date();
var t4 = [].init2(0,100000);
var D = new Date();

//test m3 with short array many times 10K * 10

var e = new Date();
for(var i=0; i<10000; i++)
{
    var t5 = newFilledArray(10,0);
}
var E = new Date();

//test m3 with long array created once 100K

var f = new Date();
var t6 = newFilledArray(100000, 0)
var F = new Date();

Resultados:

IE7 deltas:
dA=156
dB=359
dC=125
dD=375
dE=468
dF=412

FF3.5 deltas:
dA=6
dB=13
dC=63
dD=8
dE=12
dF=8

Entonces, según mi cálculo, en realidad es más lento en general, pero funciona mejor con matrices más largas en FF pero peor en IE, que en general es una mierda (sorpresa).


Acabo de probar esto: el segundo método ( b = []...) es 10-15% más rápido que el primero, pero es más de 10 veces más lento que la respuesta de Joshua.
nickf

Sé que esta es una publicación antigua . Pero tal vez todavía sea de interés para otros (como yo). Por lo tanto, me gustaría sugerir una adición a la función prototipo: incluir un else {this.length=n;}después de la this.lengthcomprobación. Esto acortará una matriz ya existente si es necesario al volver a initponerla en una longitud diferente n.
cars10m

2

Función anónima:

(function(n) { while(n-- && this.push(0)); return this; }).call([], 5);
// => [0, 0, 0, 0, 0]

Un poco más corto con for-loop:

(function(n) { for(;n--;this.push(0)); return this; }).call([], 5);
// => [0, 0, 0, 0, 0]

Funciona con cualquiera Object, solo cambia lo que hay dentro this.push().

Incluso puedes guardar la función:

function fill(size, content) {
  for(;size--;this.push(content));
  return this;
}

Llámalo usando:

var helloArray = fill.call([], 5, 'hello');
// => ['hello', 'hello', 'hello', 'hello', 'hello']

Agregar elementos a una matriz ya existente:

var helloWorldArray = fill.call(helloArray, 5, 'world');
// => ['hello', 'hello', 'hello', 'hello', 'hello', 'world', 'world', 'world', 'world', 'world']

Rendimiento: http://jsperf.com/zero-filled-array-creation/25

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.