57 votes

Moyen plus rapide de trouver la première ligne vide dans une colonne de feuille Google

J'ai fait un script qui toutes les quelques heures ajoute une nouvelle ligne à une feuille de calcul Google Apps.

Voici la fonction que j'ai créée pour trouver la première ligne vide :

function getFirstEmptyRow() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var cell = spr.getRange('a1');
  var ct = 0;
  while ( cell.offset(ct, 0).getValue() != "" ) {
    ct++;
  }
  return (ct);
}

Ça fonctionne bien, mais lorsque j'atteins environ 100 lignes, c'est vraiment lent, même dix secondes. Je crains qu'en atteignant des milliers de lignes, ce sera trop lent, peut-être atteindra-t-il le délai d'attente ou pire. Y a-t-il une meilleure façon de faire ?

64voto

Mogsdad Points 11078

Cette question a maintenant eu plus de 12K vues - il est donc temps pour une mise à jour, car les caractéristiques de performance des nouvelles feuilles sont différentes de lorsque Serge a effectué ses tests initiaux.

Bonne nouvelle : les performances sont bien meilleures dans l'ensemble !

Le plus rapide :

Comme dans le premier test, lire les données de la feuille une seule fois, puis opérer sur le tableau, a donné un énorme bénéfice en termes de performances. Fait intéressant, la fonction originale de Don a mieux performé que la version modifiée testée par Serge. (Il semble que while est plus rapide que for, ce qui n'est pas logique.)

Le temps d'exécution moyen sur les données d'échantillon est seulement de 38ms, en baisse par rapport aux 168ms précédents.

// Approche du tableau de Don - vérifie uniquement la première colonne
// Avec condition d'arrêt ajoutée et bon résultat.
// De la réponse https://stackoverflow.com/a/9102463/1677912
function getFirstEmptyRowByColumnArray() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var column = spr.getRange('A:A');
  var values = column.getValues(); // obtenir toutes les données en une seule fois
  var ct = 0;
  while ( values[ct] && values[ct][0] != "" ) {
    ct++;
  }
  return (ct+1);
}

Résultats du test :

Voici les résultats, résumés sur 50 itérations dans une feuille de calcul avec 100 lignes x 3 colonnes (remplies avec la fonction de test de Serge).

Les noms des fonctions correspondent au code dans le script ci-dessous.

capture d'écran

"Première ligne vide"

La question initiale était de trouver la première ligne vide. Aucun des scripts précédents ne répond réellement à cela. Beaucoup vérifient juste une colonne, ce qui signifie qu'ils peuvent donner des résultats faussement positifs. D'autres ne trouvent que la première ligne qui suit toutes les données, ce qui signifie que les lignes vides dans des données non contiguës sont ignorées.

Voici une fonction qui répond à la spécification. Elle a été incluse dans les tests et, bien qu'elle soit plus lente que le vérificateur de colonne unique ultra-rapide, elle est arrivée à 68ms respectables, une prime de 50% pour une réponse correcte !

/**
 * Vérificateur de ligne entière de Mogsdad.
 */
function getFirstEmptyRowWholeRow() {
  var feuille = SpreadsheetApp.getActiveSheet();
  var plage = feuille.getDataRange();
  var valeurs = plage.getValues();
  var ligne = 0;
  for (var ligne=0; ligne

`

Script complet :

Si vous voulez répéter les tests ou ajouter votre propre fonction pour la comparer, copiez simplement l'ensemble du script et utilisez-le dans une feuille de calcul.

/**
 * Configuration d'une option de menu pour plus de facilité d'utilisation.
 */
function onOpen() {
  var entreesMenu = [ {name: "Remplir la feuille", functionName: "fillSheet"},
                      {name: "tester getFirstEmptyRow", functionName: "testTime"}
                     ];
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  sh.addMenu("exécuter des tests",entreesMenu);
}

/**
 * Tester un tableau de fonctions, mesurer l'exécution de chacune sur plusieurs itérations.
 * Produire des statistiques à partir des données collectées et les présenter dans une feuille "Résultats".
 */
function testTime() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  ss.getSheets()[0].activate();
  var iterations = parseInt(Browser.inputBox("Entrez le nombre d'itérations, min 2 :")) || 2;

  var fonctions = ["getFirstEmptyRowByOffset", "getFirstEmptyRowByColumnArray", "getFirstEmptyRowByCell","getFirstEmptyRowUsingArray", "getFirstEmptyRowWholeRow"]

  var résultats = [["Itération"].concat(fonctions)];
  for (var i=1; i<=iterations; i++) {
    var ligne = [i];
    for (var fn=0; fn"&B$'+ligne+"-3*B$"+(ligne+1)+")" ]]);
  formules.setNumberFormat("##########.");

  for (var col=3; col<=résultats[0].length;col++) {
    formules.copyTo(feuilleRésultat.getRange(ligne, col))
  }

  // Format pour plus de lisibilité
  for (var col=1;col<=résultats[0].length;col++) {
    feuilleRésultat.autoResizeColumn(col)
  }
}

// Fonction originale d'Omiod. Vérifie seulement la première colonne
// Modifiée pour donner le bon résultat.
// question https://stackoverflow.com/questions/6882104
function getFirstEmptyRowByOffset() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var cellule = spr.getRange('a1');
  var ct = 0;
  while ( cellule.offset(ct, 0).getValue() != "" ) {
    ct++;
  }
  return (ct+1);
}

// Approche du tableau de Don - vérifie seulement la première colonne.
// Avec condition d'arrêt ajoutée et bon résultat.
// De la réponse https://stackoverflow.com/a/9102463/1677912
function getFirstEmptyRowByColumnArray() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var colonne = spr.getRange('A:A');
  var valeurs = colonne.getValues(); // obtenir toutes les données en une seule fois
  var ct = 0;
  while ( valeurs[ct] && valeurs[ct][0] != "" ) {
    ct++;
  }
  return (ct+1);
}

// Fonction getFirstEmptyRow de Serge, adaptée de celle d'Omiod, mais
// en utilisant getCell à la place de offset. Vérifie seulement la première colonne.
// Modifiée pour donner le bon résultat.
// De la réponse https://stackoverflow.com/a/18319032/1677912
function getFirstEmptyRowByCell() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var ran = spr.getRange('A:A');
  var arr = []; 
  for (var i=1; i<=ran.getLastRow(); i++){
    if(!ran.getCell(i,1).getValue()){
      break;
    }
  }
  return i;
}

// Adaptation de Serge de la réponse du tableau de Don. Vérifie seulement la première colonne.
// Modifiée pour donner le bon résultat.
// De la réponse https://stackoverflow.com/a/18319032/1677912
function getFirstEmptyRowUsingArray() {
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  var ss = sh.getActiveSheet();
  var données = ss.getDataRange().getValues();
  for(var n=0; n

`

1 votes

Juste un petit commentaire pour mentionner que mon code initial retournait en fait la bonne valeur car j'utilisais le code suivant lorsque la condition était vraie : {n++; break}. Donc ajouter 1 à n (dans votre version modifiée) fait retouner n+2 ... ce qui est un peu trop ;-). J'admets que ce n'était probablement pas la manière la plus évidente de procéder.

0 votes

@La version de Serge insas s'exécute plus lentement car vous récupérez toute la plage, contrairement à une seule colonne comme dans la solution de Don. Après avoir corrigé cela, ils s'exécutent de manière équivalente rapidement.

0 votes

Aussi, votre version de la solution de Serge et sa propre solution sont toutes deux incorrectes - mais de différentes manières: sa solution ne traite pas n=1000 dans votre configuration d'exemple et votre solution ne traite pas n=10 (essayez de supprimer A10).

55voto

Don Kirkby Points 12671

Le blog Google Apps Script a publié un article sur l'optimisation des opérations sur les feuilles de calcul qui parlait de la mise en lot des lectures et écritures qui pourraient vraiment accélérer les choses. J'ai essayé votre code sur une feuille de calcul avec 100 lignes, et cela a pris environ sept secondes. En utilisant Range.getValues(), la version en lot prend une seconde.

function getFirstEmptyRow() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var column = spr.getRange('A:A');
  var values = column.getValues(); // obtenir toutes les données en une seule fois
  var ct = 0;
  while ( values[ct][0] != "" ) {
    ct++;
  }
  return (ct);
}

Si la feuille de calcul devient assez grande, vous pourriez avoir besoin de récupérer les données par tranches de 100 ou 1000 lignes au lieu de récupérer toute la colonne.

2 votes

Cela fonctionne uniquement si vous pouvez garantir qu'une ligne avec une cellule vide dans la colonne A est "vide". Il n'a également aucune condition d'arrêt pour la boucle while, donc il peut (et le fait) déclencher une exception si chaque cellule de la colonne A est pleine. L'adaptation de Serge gère la condition d'arrêt.

2 votes

Aussi, le résultat est décalé de 1. Si la première ligne "vide" est 10 (en comptant les feuilles de calcul), cela donne 9 (en comptant les tableaux).

1 votes

Voir la réponse de Mogsdad pour un contrôle plus complet des lignes vides, ainsi que quelques comparaisons de performances. Merci, @Mogsdad.

36voto

Peter Herrmann Points 3955

C'est déjà là en tant que méthode getLastRow sur la feuille.

var firstEmptyRow = SpreadsheetApp.getActiveSpreadsheet().getLastRow() + 1;

Réf https://developers.google.com/apps-script/class_sheet#getLastRow

5 votes

Le problème avec cela est que si la colonne A a 10 lignes et la colonne B en a 100, cela renverra 100. Pour obtenir la dernière ligne d'une colonne, vous devez itérer sur le contenu (autant que je sache)

4 votes

Hmm, alors pourquoi pas SpreadsheetApp.getActiveSpreadsheet().getRange('A:A').getLastRow() + 1 ?

1 votes

Argh nvm Je viens de remarquer que cela vous donne la vraiment dernière cellule de la plage

8voto

Serge insas Points 34462

En découvrant ce vieux post avec 5k vues, j'ai d'abord vérifié la 'meilleure réponse' et j'ai été assez surpris par son contenu... c'était en effet un processus très lent ! Puis je me suis senti mieux quand j'ai vu la réponse de Don Kirkby, l'approche avec un tableau est en effet beaucoup plus efficace !

Mais qu'en est-il de l'efficacité réelle ?

J'ai donc écrit ce petit code de test sur une feuille de calcul avec 1000 lignes et voici les résultats : (pas mal !... pas besoin de dire lequel est lequel...)

description de l'imagedescription de l'image

et voici le code que j'ai utilisé :

function onOpen() {
  var menuEntries = [ {name: "test méthode 1", functionName: "getFirstEmptyRow"},
                      {name: "test méthode 2 (tableau)", functionName: "getFirstEmptyRowUsingArray"}
                     ];
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  sh.addMenu("exécuter les tests",menuEntries);
}

function getFirstEmptyRow() {
  var time = new Date().getTime();
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var ran = spr.getRange('A:A');
  for (var i= ran.getLastRow(); i>0; i--){
    if(ran.getCell(i,1).getValue()){
      break;
    }
  }
  Browser.msgBox('dernière ligne = '+Number(i+1)+'  durée = '+Number(new Date().getTime()-time)+' mS');
}

function getFirstEmptyRowUsingArray() {
  var time = new Date().getTime();
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  var ss = sh.getActiveSheet();
  var data = ss.getDataRange().getValues();
  for(var n =data.length ; n<0 ;  n--){
    if(data[n][0]!=''){n++;break}
  }
  Browser.msgBox('dernière ligne = '+n+'  durée = '+Number(new Date().getTime()-time)+' mS');
}

function fillSheet(){
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  var ss = sh.getActiveSheet();
  for(var r=1;r<1000;++r){
    ss.appendRow(['remplissage des valeurs',r,'pas important']);
  }
}

Et la feuille de calcul de test pour essayer par vous-même :-)


EDIT :

Suite au commentaire de Mogsdad, je devrais mentionner que ces noms de fonction sont en effet de mauvais choix... Cela aurait dû être quelque chose comme getLastNonEmptyCellInColumnAWithPlentyOfSpaceBelow() qui n'est pas très élégant (n'est-ce pas ?) mais plus précis et cohérent avec ce qu'il retourne réellement.

Commentaire :

Quoi qu'il en soit, mon objectif était de montrer la vitesse d'exécution des deux approches, et cela a clairement été atteint (n'est-ce pas ? ;-)

0 votes

Merci pour le test. Je viens de déplacer la meilleure réponse vers celle de Don Kirkby.

0 votes

Excellente idée ! Merci

0 votes

Les deux fonctions "getFirstEmpty" donnent des résultats incorrects. Parce qu'elles comptent à rebours, elles identifient la dernière ligne avec des données dans la colonne A. Dans une feuille de calcul avec des données contiguës, cela sera décalé d'un par rapport à la première ligne vide. S'il y a une ligne vide plus tôt dans la feuille de calcul, aucune des deux ne la trouvera.

8voto

Niccolo Points 777

Je sais que c'est un vieux fil de discussion et qu'il y a eu des approches très intelligentes ici.

J'utilise le script

var firstEmptyRow = SpreadsheetApp.getActiveSpreadsheet().getLastRow() + 1;

si j'ai besoin de la première ligne complètement vide.

Si j'ai besoin de la première cellule vide dans une colonne, je fais ce qui suit.

  • Ma première ligne est généralement une ligne de titre.

  • Ma 2e ligne est une ligne cachée et chaque cellule a la formule

    =COUNTA(A3:A)

    A est remplacé par la lettre de la colonne.

  • Mon script lit simplement cette valeur. Cela se met à jour assez rapidement par rapport aux approches de script.

Il y a un cas où cela ne fonctionne pas et c'est lorsque j'autorise les cellules vides à séparer la colonne. Je n'ai pas encore besoin d'une solution pour cela, je soupçonne qu'une pourrait être dérivée de COUNTIF, ou d'une fonction combinée ou d'une des nombreuses autres fonctions intégrées.

MODIFICATION : COUNTA gère les cellules vides dans une plage, donc le souci du "cas où cela ne fonctionne pas" n'est pas vraiment un souci. (Cela pourrait être un nouveau comportement avec les "nouvelles Feuilles de calcul".)

1 votes

Décharger l'identification de la dernière ligne vers les formules de tableur est astucieux!

0 votes

Merci et merci pour l'édition. Je suppose que le comportement a changé depuis que j'ai écrit cela.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X