Question Le moyen le plus efficace de créer un tableau JavaScript à remplissage nul?


Quel est le moyen le plus efficace pour créer un tableau rempli de longueur arbitraire zéro dans JavaScript?


412
2017-08-18 18:11


origine


Réponses:


Bien que ce soit un vieux fil, je voulais y ajouter mes 2 cents. Je ne sais pas à quel point c'est lent, mais c'est un paquebot rapide. Voici ce que je fais:

Si je veux pré-remplir avec un nombre:

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

Si je veux pré-remplir avec une chaîne:

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

D'autres réponses ont suggéré:

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

mais si vous voulez 0 (le nombre) et non "0" (zéro dans une chaîne), vous pouvez faire:

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

353
2017-12-06 01:16



ES6 introduit Array.prototype.fill. Il peut être utilisé comme ceci:

new Array(len).fill(0);

Je ne sais pas si c'est rapide, mais je l'aime parce que c'est court et auto-descriptif.

Ce n'est toujours pas dans IE (vérifier la compatibilité), mais il y a polyfill disponible.


304
2018-04-27 17:30



Manière élégante de remplir un tableau avec des valeurs précalculées

Voici une autre façon de le faire en utilisant ES6 que personne n'a mentionné jusqu'à présent:

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

Il fonctionne en passant une fonction de carte comme le deuxième paramètre de Array.from.

Dans l'exemple ci-dessus, le premier paramètre alloue un tableau de 3 positions remplies avec la valeur undefined et puis la fonction lambda mappe chacun d'eux à la valeur 0.

Bien que Array(len).fill(0) est plus court, cela ne fonctionne pas si vous devez remplir le tableau en faisant d'abord un calcul (Je sais que la question ne l’a pas demandé, mais beaucoup de gens finissent par chercher ici).

Par exemple, si vous avez besoin d'un tableau avec 10 nombres aléatoires:

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

C'est plus concis (et élégant) que l'équivalent:

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

Cette méthode peut également être utilisée pour générer des séquences de nombres en tirant parti du paramètre d’index fourni dans le rappel:

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

Réponse bonus: remplissez un tableau en utilisant String repeat()

Comme cette réponse reçoit beaucoup d'attention, je voulais aussi montrer ce truc cool. Bien que n'étant pas aussi utile que ma réponse principale, introduira la chaîne encore peu connue, mais très utile repeat() méthode. Voici l'astuce:

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

Cool hein? repeat() est une méthode très utile pour créer une chaîne qui est la répétition de la chaîne d'origine un certain nombre de fois. Après ça, split() crée un tableau pour nous, qui est alors map()ped pour les valeurs que nous voulons. La décomposer en étapes:

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

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

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

57
2018-05-22 17:17



La méthode de remplissage ES 6 mentionnée ci-dessus s'en occupe bien. La plupart des navigateurs de bureau modernes prennent déjà en charge les méthodes de prototype Array requises à ce jour (Chromium, FF, Edge et Safari) [1] Vous pouvez rechercher des détails sur MDN. Un exemple d'utilisation simple est

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

Compte tenu de la prise en charge actuelle du navigateur, vous devez être prudent avant de l'utiliser, sauf si vous êtes sûr que votre public utilise des navigateurs de bureau modernes.


54
2018-01-09 13:03



Note ajoutée août 2013, mise à jour février 2015: La réponse ci-dessous de 2009 concerne le générique de JavaScript Array type. Il ne concerne pas le plus récent tapé tableaux définis dans ES2015 [et disponibles maintenant dans de nombreux navigateurs], comme Int32Array et autres choses de ce genre. Notez également que l’ES2015 ajoute un fill méthode à la fois Tableaux et tableaux tapés, qui est probablement le moyen le plus efficace de les remplir ...

En outre, il peut faire une grande différence pour certaines implémentations comment vous créez le tableau. Le moteur V8 de Chrome, en particulier, essaie d'utiliser une matrice de mémoire contiguë très efficace s'il le pense, en se déplaçant vers le tableau basé sur des objets uniquement lorsque c'est nécessaire.


Avec la plupart des langues, il serait pré-allouer, puis zéro-remplir, comme ceci:

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

Mais, Tableaux JavaScript ne sont pas vraiment des tableaux, ils sont des cartes clé / valeur comme tous les autres objets JavaScript, donc il n'y a pas de "pré-allocation" à faire (la définition de la longueur n'attribue pas autant d'emplacements à remplir), et il n'y a aucune raison de croire que de décompter à zéro (ce qui est juste pour faire la comparaison dans la boucle rapide) n'est pas compensé par l'ajout des clés dans l'ordre inverse lorsque l'implémentation peut bien avoir optimisé leur gestion des clés liées aux tableaux sur la théorie que vous aurez généralement les faire dans l'ordre.

En fait, Matthew Crumley a souligné que le décompte est nettement plus lent sur Firefox que le décompte, un résultat que je peux confirmer - c'est la partie tableau (boucler jusqu'à zéro est toujours plus rapide que boucler jusqu'à une limite dans un var). Apparemment, ajouter les éléments au tableau dans l'ordre inverse est une opération lente sur Firefox. En fait, les résultats varient quelque peu par l’implémentation de JavaScript (ce qui n’est pas si surprenant). Voici une page de test rapide et sale (ci-dessous) pour les implémentations de navigateur (très sale, ne cède pas pendant les tests, fournit donc un feedback minimal et ira à l'encontre des limites de temps du script). Je recommande le rafraîchissement entre les tests; FF (au moins) ralentit les tests répétés si vous ne le faites pas.

La version assez compliquée qui utilise Array # concat est plus rapide qu'une init droite sur FF à partir de quelque part entre 1.000 et 2.000 éléments. Sur le moteur V8 de Chrome, cependant, straight-straight gagne à chaque fois ...

Voici la page de test (copie en direct):

<!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>

48
2017-08-18 18:29



Par défaut Uint8Array, Uint16Array et Uint32Array les classes gardent des zéros comme valeurs, vous n'avez donc pas besoin de techniques de remplissage complexes, faites simplement:

var ary = new Uint8Array(10);

tous les éléments du tableau ary sera par défaut des zéros.


30
2018-03-05 21:30



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']

Notez que while est généralement plus efficace que for-in, forEach, etc.


24
2017-08-18 18:22



J'ai testé toutes les combinaisons de préallocation / non préallocation, de comptage haut / bas et de boucles for / while dans IE 6/7/8, Firefox 3.5, Chrome et Opera.

Les fonctions ci-dessous étaient toujours les plus rapides ou les plus proches dans Firefox, Chrome et IE8, et pas beaucoup plus lentes que les plus rapides dans Opera et IE 6. C'est aussi le plus simple et le plus clair à mon avis. J'ai trouvé plusieurs navigateurs où la version de la boucle while est un peu plus rapide, alors je l'inclus également pour référence.

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

ou

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

20
2017-08-18 21:39



Si vous utilisez ES6, vous pouvez utiliser Array.from () comme ça:

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

A le même résultat que

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

Car

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

16
2017-07-16 12:20



utilisant la notation d'objet

var x = [];

zéro rempli? comme...

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

rempli de "indéfini" ...

var x = new Array(7);

notation obj avec des zéros

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

En guise de remarque, si vous modifiez le prototype d’Array, les deux

var x = new Array();

et

var y = [];

aura ces modifications de prototype

En tout cas, je ne serais pas trop préoccupé par l'efficacité ou la rapidité de cette opération, il y a beaucoup d'autres choses que vous ferez probablement qui sont beaucoup plus coûteuses et coûteuses que d'instancier un tableau de longueur arbitraire contenant des zéros.


15
2017-08-18 18:13



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

14
2018-03-03 17:04