web-dev-qa-db-fra.com

Comment vérifier efficacement s'il manque des éléments à une liste de numéros consécutifs

J'ai ce tableau

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];

J'essayais de trouver un algorithme qui me dirait quels ss sont manquants. Comme vous pouvez le constater, la liste est composée de ss (s1, s2, etc.) consécutifs.

Au début je suis allé avec cette solution:

    var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];
for (var i=1;i<arr.length;i++){
    var thisI = parseInt(arr[i].toLowerCase().split("s")[1]);
    var prevI = parseInt(arr[i-1].toLowerCase().split("s")[1]);
    if (thisI != prevI+1)
      console.log(`Seems like ${prevI+1} is missing. thisI is ${thisI} and prevI is ${prevI}`)
}

Mais cette méthode échoue pour plusieurs numéros consécutifs manquants (s15, s16). J'ai donc ajouté une boucle while qui fonctionne.

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];
for (var i=1;i<arr.length;i++){
  var thisI = parseInt(arr[i].toLowerCase().split("s")[1]);
  var prevI = parseInt(arr[i-1].toLowerCase().split("s")[1]);
  if (thisI != prevI+1) {
    while(thisI-1 !== prevI++){
       console.log(`Seems like ${prevI} is missing. thisI is ${thisI} and prevI is ${prevI}`)
    }
   }
}

Cependant, je sens que je complique exagérément les choses ... J'ai pensé à créer un tableau idéal:

var idealArray = [];
for (var i =0; i<200;i++) {
  idealArray.Push(i)
}

Et puis, lors de la vérification, manipulez mon tableau (arr) afin que la boucle vérifie deux tableaux de même longueur. C'est-à-dire, utilisez cette solution:

var idealArray = [];
for (var i =0; i<200;i++) {
  idealArray.Push(i)
}
var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];
for (let i = 0; i<idealArray.length;i++){
  if (parseInt(arr[i].toLowerCase().split("s")[1]) != idealArray[i]) {
    console.log(`Seems like ${idealArray[i]}is missing`);
    arr.splice(i,0,"dummyel")
  }
}

Mais, encore une fois, j’ai le sentiment que créer ce deuxième ensemble n’est pas très efficace non plus (en pensant à une grande liste, je perdrais de l’espace inutile).

Alors ... comment puis-je effectuer efficacement cette tâche en JavaScript? (Signification efficace aussi proche que possible de O(1) autant pour la complexité temporelle que pour la complexité spatiale.)

28
Adelin

Puisque vous savez que vous attendez un tableau séquentiel, j'ignore pourquoi il doit être plus compliqué qu'une boucle à travers les nombres arr[0] à arr[end] tout en gardant un compteur pour savoir où vous vous trouvez dans le tableau. Cela fonctionnera à O (n), mais je ne pense pas que vous puissiez améliorer cela - vous devez examiner chaque élément au moins une fois dans le pire des cas.

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];

let first = parseInt(arr[0].substring(1))
let last =  parseInt(arr[arr.length-1].substring(1))
let count = 0
for (let i = first; i< last; i++) {
   if (parseInt(arr[count].substring(1)) == i) {count++; continue}
   else console.log(`seem to be missing ${'s'+i.toString().padStart(2,'0')} between: ${arr[count-1]} and ${arr[count]}` )
}


MODIFIER:

Après avoir réfléchi un peu aux commentaires ci-dessous, j'ai adopté une approche récursive qui divise le tableau et vérifie chaque moitié. Principalement à titre expérimental et non pratique. Cela fonctionne en fait avec moins de n itérations dans la plupart des cas, mais je ne pouvais pas trouver un cas où il était en fait plus rapide. En outre, je viens de pousser des index indiquant où sont les lacunes pour faciliter la visualisation et le test de la structure. Et comme vous le verrez, comme il est récursif, les résultats ne sont pas normaux.

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];

let missingGaps = []

function missing(arr, low, high) {
  if (high <= low) return

  let l = parseInt(arr[low].substring(1))
  let h = parseInt(arr[high].substring(1))

  if (h - l == high - low) return
  if (high - low === 1) {
    missingGaps.Push([low, high])
    return
  } else {
    let mid = ((high - low) >> 1) + low
    
    missing(arr, low, mid)

    // need to check case where split might contain gap
    let m = parseInt(arr[mid].substring(1))
    let m1 = parseInt(arr[mid + 1].substring(1))
    if (m1 - m !== 1) missingGaps.Push([mid, mid + 1])

    missing(arr, mid + 1, high)
  }
}

missing(arr, 0, arr.length-1)
missingGaps.forEach(g => console.log(`missing between indices ${arr[g[0]]} and ${arr[g[1]]}`))

Peut-être qu'une autre réponse ou commentaire comportera une amélioration qui le rendra un peu plus rapide.

15
Mark Meyer

Si j'ai bien compris votre solution de baie idéale, vous connaissez la taille de baie maximale (?). Donc, si vous avez 100 valeurs maximum et que vous attendez S00 - S99, vous pouvez faire:

var arrayIndex=0;
for (var i =0; i<100;i++) {
   var idealValue="s"+("00"+i).slice(-2); // To get S01-S99
   if(arr.length <= arrayIndex || arr[arrayIndex]!=idealValue){
        console.log(idealValue + 'is missing');
   }
   arrayIndex++;
}

Ou quelque chose comme ça. Je ne peux pas le tester pour l'instant;) Mais parcourez la liste des valeurs idéales et comparez la même valeur dans le tableau. Si cela ne correspond pas, imprimez-le.

6
Veselin Davidov

Votre solution avec la boucle while- intérieure semble déjà assez bonne, omettez simplement la if inutile et gardez une trace du nombre que vous vous attendez actuellement à voir au lieu d'analyser le nombre précédent à chaque fois.

Quelque chose comme ça:

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];
var expectedI = 0
for (var i = 0; i < arr.length; i++) {
  var currentI = parseInt(arr[i].toLowerCase().split("s")[1]);
  while (expectedI < currentI) {
    console.log(`Seems like ${expectedI} is missing.`)
    expectedI++
  }
  expectedI = currentI + 1
}

vous donne:

Seems like 6 is missing.
Seems like 15 is missing.
Seems like 16 is missing.
Seems like 18 is missing.
Seems like 23 is missing.
Seems like 29 is missing.
Seems like 31 is missing.
Seems like 35 is missing.
Seems like 37 is missing.
Seems like 40 is missing.
Seems like 42 is missing.
Seems like 57 is missing.
Seems like 59 is missing.
Seems like 66 is missing.
Seems like 68 is missing.

L'idée est très simple: si vous ne voyez pas le nombre que vous espériez voir, imprimez-le sur la console (ou enregistrez-le ailleurs), puis continuez avec le nombre suivant.

Notez que vous ne pouvez pas obtenir le runtime inférieur à O(N), car vous devez examiner chaque élément de la liste au moins une fois. Il peut également arriver que vous deviez imprimer les éléments manquants de O(N) dans la console.

L'algorithme ci-dessus examine chaque élément de la liste une fois et peut fonctionner avec une surcharge d'espace constante.

EDIT: Le commentaire de vlaz semble proposer un algorithme qui devrait fonctionner plus rapidement pour les tableaux contenant peu de lacunes. Toutefois, cela ne change pas toujours le comportement dans le pire des cas, car dans le pire des cas (si tout est manquant), vous devez toujours imprimer tous les nombres N. Si vous supposez que le nombre k de nombres manquants est "beaucoup plus petit" que N (c'est-à-dire k pas dans Theta(N)), des algorithmes plus efficaces pourraient être possibles.

5
Andrey Tyukin

Ceci est juste une approche pour trouver si, dans un tableau donné, il manque un élément dans une suite numérique. Nous pourrions utiliser (n * (n + 1))/2 pour résoudre l’addition sur n premiers nombres. De plus, si le tableau commence par, par exemple, 10, nous supprimons la somme 1-10. Cela nous dit simplement si quelque chose manque, mais pas ce qui manque. L'avantage est que le tableau pourrait être non trié. Calculer le minimum est moins coûteux que de commander l'ensemble du tableau.

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];

let total = 0;

for(let i = 0; i<arr.length; i++){
    arr[i] = parseInt(arr[i].replace("s", ""));
    total += arr[i];
}

let hipFirstSum = ((arr[0]-1)*(arr[0]))/2;  //or minimun
let n = arr[arr.length -1];
let hipSum = (n*(n+1))/2;
let realSum = hipSum - hipFirstSum;

(realSum != total)?console.log("wrong"):console.log("good");
3
Emeeus

Vous pouvez réduire le tableau en prenant deux éléments du tableau et combler les vides, le cas échéant.

const
    getNumber = s => +s.slice(1),
    pad = i => ('00' + i).slice(-2);

var array = ["s00", "s01", "s02", "s03", "s04", "s05", "s07", "s08", "s09", "s10", "s11", "s12", "s13", "s14", "s17", "s19", "s20", "s21", "s22", "s24", "s25", "s26", "s27", "s28", "s30", "s32", "s33", "s34", "s36", "s38", "s39", "s41", "s43", "s44", "s45", "s46", "s47", "s48", "s49", "s50", "s51", "s52", "s53", "s54", "s55", "s56", "s58", "s60", "s61", "s62", "s63", "s64", "s65", "s67", "s69", "s70"],
    result = [];

array.reduce((left, right) => {
    var l = getNumber(left),
        r = getNumber(right);

    while (++l < r) {
        result.Push('s' + pad(l));
    }
    return right;
});

console.log(result);

3
Nina Scholz

Voici l'approche récursive basée sur la réponse acceptée mais refactorisée pour renvoyer les données:

var arr = ["s00", "s01", "s02", "s03", "s04", "s05", "s07", "s08", "s09", "s10", "s11", "s12", "s13", "s14", "s17", "s19", "s20", "s21", "s22", "s24", "s25", "s26", "s27", "s28", "s30", "s32", "s33", "s34", "s36", "s38", "s39", "s41", "s43", "s44", "s45", "s46", "s47", "s48", "s49", "s50", "s51", "s52", "s53", "s54", "s55", "s56", "s58", "s60", "s61", "s62", "s63", "s64", "s65", "s67", "s69", "s70"];

function findMissing(arr, l, r) {
  var lval = Number(arr[l].substr(1));
  var rval = Number(arr[r].substr(1));
  // the segment has no gaps
  if (r - l === rval - lval) {
    return [];
  }
  // the segment has exactly two items
  if (r - l === 1) {
    return Array.from({ length: rval - lval - 1 }, function(x, i) {
      return "s" + (lval + 1 + i);
    });
  }
  // calculate middle using integer cast trick
  var m = (l + r) / 2 | 0;
  // process the segments [l, m] and [m, r]
  // note that m is processed twice and requires extra recursion
  // however this eliminates the extra coding needed to handle
  // the case where m and m + 1 are not consecutive
  return findMissing(arr, l, m).concat(findMissing(arr, m, r));
}
var result = findMissing(arr, 0, arr.length - 1);
console.log(result);

2
Salman A

Vous pouvez opter pour quelque chose comme ceci qui compare chaque élément du tableau à celui qui est à côté, puis si la différence est supérieure à 1, tous les nombres entre les deux peuvent être enregistrés.

const arr = ["s00", "s01", "s02", "s03", "s04", "s05", "s07", "s08", "s09", "s10", "s11", "s12", "s13", "s14", "s17", "s19", "s20", "s21", "s22", "s24", "s25", "s26", "s27", "s28", "s30", "s32", "s33", "s34", "s36", "s38", "s39", "s41", "s43", "s44", "s45", "s46", "s47", "s48", "s49", "s50", "s51", "s52", "s53", "s54", "s55", "s56", "s58", "s60", "s61", "s62", "s63", "s64", "s65", "s67", "s69", "s70"];

for (let i = 0; i < arr.length - 1; i++) {
  let currentNum = parseInt(arr[i].split("s")[1]);
  let difference = parseInt(arr[i + 1].split("s")[1]) - currentNum;
  if (difference === 1) continue

  for (let d = 1; d < difference; d++)
    console.log(`Seems likes ${currentNum+d} is missing`)
}

J'espère que ça t'as aidé.

2
Andrew Bone

La version Javascript du programme C ci-dessus, qui permet de séquencer des éléments manquants.

var util = require( 'util' );

//  Array of data.
var arr = [
        1,  2,  3,  4,  5,  6,  7,  8,  9,
    10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
    20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
    30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
    40, 41, 42, 43,         46, 47, 48, 49,
    50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
    60, 61, 62, 63, 64, 65, 66, 67, 68, 70
];
var arr_len = arr.length;

//  Empty array?
if (arr_len == 0)
{
    console.log(
        util.format(
            "No elements." ));
    process.exit( 0 );
}

//  Pre-check.
var lim = arr[arr_len - 1] - (arr_len - 1);
if (lim == 0)
{
    printf(
        "No missing elements.\n" );
    return 0;
}

//  Initialize binary search.
var lo  = 0;
var hi  = arr_len;
var mid = 0;

//  Search metadata.
var cnt = 0;
var prv = 0;
var val = 0;
var i;

for (i = 0; i < arr_len && cnt < lim; i++)
{
    //  Get mid point of search.
    mid = (lo + hi) >> 1;

    //  Get array value, adjust and do comparisons
    val = arr[ mid ] - cnt;
    if (val === mid)
        lo = mid + 1;
    if (val > mid)
        hi = mid - 1;

    //  Have we found something?
    if (lo > hi)
    {
        //  Yes.  Divide and conquer.
        hi  = arr_len;
        prv = cnt;
        cnt = arr[ lo ] - lo;

        //  Report missing element(s).
        console.log(
            util.format(
                "Missing %d elements @ arr[ %d ] == %d, probes = %d",
                cnt - prv,
                lo,
                arr[ lo ],
                i + 1 ));
    }
}

console.log(
    util.format(
        "Probes: %d",
        i ));
1
John Stevens

Cette version remplit un tableau avec toutes les valeurs possibles, puis sélectionne celles qui manquent:

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];

var fullArray = Array(71).fill().map((item, index) => "s"+(""+(0 + index)).padStart(2,"0"));

var missingValues = fullArray.filter( ( el ) => !arr.includes( el ) );

console.log(missingValues);

Avec un peu plus de lisibilité et de réutilisabilité:

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];

var prependString = "s";
var numberOfDigits = 2;
var initialNumber = 0;
var finalNumber = 70;

var fullArray = Array(finalNumber - initialNumber + 1)
    .fill()
    .map((item, index) => prependString+(""+(initialNumber + index)).padStart(numberOfDigits,"0"));

var missingValues = fullArray.filter( ( el ) => !arr.includes( el ) );

console.log(missingValues);

1
Bernat

Une solution simple et rapide consiste à associer tous les éléments d’un tableau à une chaîne, puis à effectuer une recherche à l’intérieur de cette chaîne.

Voici une solution qui prend un tableau (ordonné ou non ordonné, fonctionne dans tous les cas) avec n'importe quel motif (aucun motif s0x codé en dur n'est nécessaire):

    const arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];
    let firstIndex = Number(arr[0].replace(/\D/, ''));
    let lastIndex = Number(arr[arr.length-1].replace(/\D/, ''));
    let separator = ',';
    
    let arrString = separator + arr.join(separator) + separator;
    for (let i = firstIndex; i <= lastIndex; i++) {
    	let element = arr[0].slice(0, arr[0].length - String(i).length) + i;
    
    	if (arrString.indexOf(separator + element + separator) < 0) {
			console.log(element)
    	}
    }

1
Vitaly

Utilisation d'un tableau de booléens pour suivre les éléments présents: -

let numbers = arr.map(s => +s.slice(1)); // Convert to numbers
let maximum = Math.max.apply(null, numbers); // Get the maximum
let missing = Array(maximum).fill(true); // start with all missing
let answer = numbers.reduce((p, c) => (p[c] = false, p), missing); // set to false if there
answer.forEach((e,i) =>  (e && console.log(i + " seems to be missing"))); // show answer

Fonctionne également pour des nombres disposés au hasard.

1
Quentin 2

Donc, si vous savez qu'il n'y a pas de doublons et que les entrées sont en ordre, un processus assez simple serait de commencer par vérifier le nombre d'éléments dans la liste.

La longueur de l'arr doit être égale au nombre d'éléments consécutifs, oui?

*** Mise en garde: Si les éléments ne sont pas triés ou s'il peut y avoir des doublons, cela ne fonctionnera pas, bien sûr.

En supposant que les conditions s'appliquent, une simple recherche binaire trouverait le premier élément manquant.

Après cela, il s’agit d’une division et d’une conquête, la recherche binaire étant limitée à la partie supérieure de la zone de recherche afin de trouver le prochain élément manquant. Un algorithme récursif serait facile à comprendre, mais vous pourriez aussi le faire en une seule fonction.

Notez que les éléments contiennent une relation entre leurs index de liste. Dans votre recherche binaire, si "s08" est dans l'élément sept, alors vous savez qu'il manque un élément précédemment dans le tableau.

La recherche binaire est assez facile et bénéficierait probablement d’une méthode de comparaison moins naïve, car les éléments sont une chaîne de champ fixe.

L'ajustement pour les éléments manquants est également assez facile. Chaque élément manquant déplace le reste des éléments d'un index vers la gauche, ce qui se transforme en une opération entière simple.

Sérieusement? Ce n'est pas si difficile:

#include    <stdio.h>
#include    <stdlib.h>

#define ARY_SZ  68

static
int arr[ARY_SZ] =
{
        1,  2,  3,  4,  5,  6,  7,  8,  9,
    10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
    20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
    30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
    40, 41, 42, 43,     45, 46, 47, 48, 49,
    50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
    60, 61, 62, 63, 64, 65, 66, 67, 68, 70
};

int
main(
    void )
{
    int     i,
            lim,
            lo,
            hi,
            mid,
            val,
            cnt;

    /*  Pre-check.  */
    lim = arr[ARY_SZ - 1] - (ARY_SZ - 1);
    if (lim == 0)
    {
        /*  No missing elements.    */
        printf(
            "No missing elements.\n" );
        return 0;
    }

    /*  Initialize binary search.   */
    lo  = 0;
    hi  = ARY_SZ;
    cnt = 0;

    /*  For (at most) the number of array elements, do: */
    for (i = 0; i < ARY_SZ && cnt < lim; i++)
    {
        /*  Get mid point of search.    */
        mid = lo + hi >> 1;

        /*  Get array value, adjust and do comparisons. */
        val = arr[ mid ] - cnt;
        if (val == mid)
            lo = mid + 1;
        if (val > mid)
            hi = mid - 1;

        if (lo > hi)
        {
            /*  Report missing element. */
            printf(
                "Missing element @ arr[ %d ] == %d, probes = %d\n",
                lo,
                arr[ lo ],
                i );

            /*  Divide and conquer. */
            hi   = ARY_SZ;
            cnt += 1;
        }
    }

    printf(
        "Probes = %d\n",
        i - 1);

    return 0;
}

Les résultats de la compilation de ce code C et de son exécution:

Missing element @ arr[ 0 ] == 1, probes = 5
Missing element @ arr[ 43 ] == 45, probes = 11
Missing element @ arr[ 67 ] == 70, probes = 16
Probes = 16

Donc, aucune recherche linéaire n'est nécessaire, et un maximum de 16 sondes est nécessaire pour trouver les trois éléments manquants.

0
John Stevens