JavaScript (App Script) manipuler des tableaux

gaston gaston
1 618 contributions
Membre depuis le 01/03/2001
Envoyé le 04/11/2023 à 16:03 Modifié par gaston


Bonjour,
J'ai un tableau de ce genre, avec plusieurs lignes contenant un champ id et un champ date qui peut être vide

tableau1 = [[ 1, b01, c01, d01],
[ 2, b02, , d01],
[ 3, b03, , d03 ],
[ 4,b04, , d04],
[ 5, b05, , d05],
[ 6, b06, , d06 ],
[ 7, b07, c07, d07],
[ 8, b08, , d08],
[ 9, b09, , d09],
[10, b10, c10, d10] ]

Et j'ai un autre tableau avec juste id et 1 autre colonne, et avec moins de lignes, mais donc, les lignes des 2 tableaux ont des id en commun :
tableau2 = [[2, X03],
[3,X03],
[6,X03],
[9, X03] ]

J'aimerais pouvoir mettre à jour le tableau 1 avec les valeurs du tableau 2, et donc obtenir ceci:
tableau1 = [[1, b01,c01,d01],
[2, b02, X03,d01],
[3, b03, X03, d03],
[4, b04, ,d04],
[5, b05, , d05],
[6, b06, X03 ,d06 ],
[7, b07, c07, d07],
[8, b08, , d08],
[9, b09, X03 ,d09],
[10, b10, c10, d10] ]

Comment faire ?

J'ai bien essayé une boucle du style

for (li=0; li<nbLignesTableau1; i++)
{
If (tableau1[li][0] = tableau2[li][0])
{
tableau1[li][2] = tableau2[li][1]
}
}
Seulement bien sûr, ça me met une erreur parce que les de tableaux n'ont pas le même nombre de ligne, si bien que par exemple tableau2[5][0] n'existe pas et paf :
cannot read properties of undefined (reading '0')

Bref je piétine...

Est-ce seulement possible, ou dois-je revoir ma copie ?
carpe diem

Répondre à ce message

Bipbipcoyote Bipbipcoyote
4 287 contributions
Membre depuis le 06/03/2001
Envoyé le 05/11/2023 à 14:17 Modifié par Bipbipcoyote


Bonjour,
tu dois faire 2 boucles imbriquées, donc une première boucle qui prend la valeur de l'Id du tableau 1 et avec cette donnée tu parcours (avec une boucle) le tableau 2 pour retrouver l'id; ensuite tu récupères les valeurs du tableau 2 avec un "si id1 =id2" et tu utilises les offsets
un truc comme ceci

function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var output = [];
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
var value = sheet.getRange(i + 1, j + 1).offset(0, 2).getValue();
output.push(value);
}
}
Logger.log(output);
}

il te faudra sans doute plusieurs offsets puisque tu as plusieurs colonnes
Ensuite il suffit de recopier l'output (c'est une ligne de tableau) dans le tableau 1 sur la ligne du tableau 1
petit exemple sur Excel mais c'est la même mécanique
Visitez mon Site Google est mon ami, il répond mieux que moi, posez lui d'abord vos questions
gaston gaston
1 618 contributions
Membre depuis le 01/03/2001
Envoyé le 06/11/2023 à 14:04 Modifié par gaston


Bonjour,.

je crois que je comprends à peu près le principe (vraiment très à peu près, à dire vrai), et là où je comprends moins c'est quand tu fais
var value = sheet.getRange(i + 1, j + 1).offset(0, 2).getValue();

pourquoi tu reviens sur une feuille ? [:o] Ne peut-on faire directement dans le tableau avec les index puisque les tableaux de données sont enregistré dans des variables ?
et puis je ne comprends pas non plus ici: si j'ai une feuile pour tableau 1 et une autre pour tableau 2, sheet c'est tableau1 ou tableau2 ?

(j'ai modifié la présentation de mon premier post, afin d'essayer de mieux exprimer ma pensée)
ce que je voudrais, c'est, pour reprendre mon exemple, obtenir une colonne ainsi, qui ait le même nombre de lignes que le tableau d'origine mais avec les nouvelles valeurs intégrées:

tableau =
[[c01 ],
[X03 ],
[X03 ],
[ ],
[ ],
[X03 ],
[c07],
[ ],
[ X03 ],
[c10 ] ]

et ne revenir qu'à ce moment-là dans la feuille pour coller directement les valeurs dans la colonne voulue de ma feuille, (il n'y a qu'une colonne à modifier)

en fait, je veux essayer de faire tout en "virtuel" pour gagner du temps: si chaque fois, on revient du tableau sur la feuille, qu'on fait une mise à jour, puis qu'on refait pour la ligne suivante, ça fait beaucoup d'aller et retour et on perd quelques secondes...parait-il...mais, justement, je voudrais vérifier si on perd tant que cela.




carpe diem
Bipbipcoyote Bipbipcoyote
4 287 contributions
Membre depuis le 06/03/2001
Envoyé le 07/11/2023 à 23:29 Modifié par Bipbipcoyote


Bonjour,
On compare 2 tableaux sur les id et s'il y a correspondance, on complète la colonne D de cette ligne (enregistrement). On peut modifier le traitement compris dans la condition, en ajoutant une ligne sur la feuille 1 ou si les id ne se suivent pas, insérer une nouvelle ligne vierge et y recopier les données trouvées (avec un else)

function comparaison2Tableaux() {
var sheet1 = SpreadsheetApp.getActive().getSheetByName("Feuille 1");
var lastRow1 = sheet1.getLastRow()+1;
var dataTableau1 = sheet1.getRange("A2:A"+lastRow1).getValues();

var sheet2 = SpreadsheetApp.getActive().getSheetByName("Feuille 2");
var lastRow2 = sheet2.getLastRow()+1;
var dataTableau2 = sheet2.getRange("A2:A"+lastRow2).getValues();

for (i = 1; i < dataTableau1.length; i++) {
var valueTableau1 = sheet1.getRange("A"+(i + 1)).getValue();
// Browser.msgBox(valueTableau1);

for (j = 1; j < dataTableau2.length; j++) {

var valueTableau2 = sheet2.getRange("A"+(j + 1)).getValue();
// Browser.msgBox(valueTableau2);
if (valueTableau2 == valueTableau1){

sheet1.getRange("A"+(i + 1)).offset(0, 3).setValue(sheet2.getRange("A"+(j + 1)).offset(0, 3).getValue());
}
}

}
}
Visitez mon Site Google est mon ami, il répond mieux que moi, posez lui d'abord vos questions
gaston gaston
1 618 contributions
Membre depuis le 01/03/2001
Envoyé le 16/11/2023 à 17:17 Modifié par gaston


Bonjour,

Bonjour,

Désolé pour ma réponse tardive, je finissais autre chose...

Je pense qu'on ne gagne pas beaucoup de temps en faisant comme tu dis, mais toujours parce que à chaque itération de la boucle, on revient à chaque fois à la feuille pour mettre à jour la ligne: ça fait beaucoup d'allers et venues.
je me disais qu'on pourrait peut-être, toujours à l'aide d'une boucle, stocker dans un 3ème tableau dataTableau3 toutes les valeurs combinées de la quatrième colonne (donc dataTableau1[3] et dataTableau2[3] )

parce qu'ainsi, il suffit, après, de faire :

sheet1.getRange("D2:D"+lastRow1). setValues(dataTableau3)

pour coller toutes les valeurs de cette colonne en 1 seule fois

mon seul problème est que je ne sais pas comment créer un tel tableau avec une boucle [;(]

j'ai bien une piste ici mais je ne sais comment l'adapter, parce que la différence est qu'il ne faudrait pas simplement les lignes qui ont la même id dans les 2 tableaux, mais toutes les lignes du tableau le plus grand, dont les valeurs manquante soient dûment complété avec les valeurs du plus petit



carpe diem
Bipbipcoyote Bipbipcoyote
4 287 contributions
Membre depuis le 06/03/2001
Envoyé le 16/11/2023 à 23:41


Bonjour,
1. En simplifié car il faudra sans doute faire plus de contrôles pour être sûr de modifier la bonne ligne du tableau
2. il faudra aussi prévoir un code pour ajouter les lignes qui n'existeraient pas dans un tableau et le flusher en fin de tableau principal ( ou appendRow) et éventuellement refiltrer ce tableau
3. et il n'est pas sûr que ce script sera plus rapide si les lignes sont nombreuses, il ne faut pas oublier que le traitement ne se passe pas sur l'ordinateur local mais sur le serveur de Google, l'ordinateur local ne reçoit qu'un affichage du résultat
4. une solution à explorer serait de remplacer le S1.getRange... par un 3ème tableau en mémoire et d'en faire un flush
Documentation mais là cela signifie écraser tout le tableau existant, c'est peut être dangereux

5. bref c'est à tester sur un grand tableau

function copieDeValeurs() {
var app = SpreadsheetApp; // Objet Application
var ss = app.getActiveSpreadsheet(); // le classeur en cours d'utilisation, attention aux parenthèses
var s1 = ss.getSheetByName('Feuille 1');
var s2 = ss.getSheetByName('Feuille 2');
// 1. Retrouve les valeurs dans les feuilles, ",
var values1 = s1.getDataRange().getValues(); // c'est un tableau en mémoire qui reprend toute la feuille 1
var values2 = s2.getDataRange().getValues(); // c'est un tableau en mémoire qui reprend toute la feuille 2

for (i = 1; i < values1.length; i++) {
var id1 = values1[i][0];
for (j = 1; j < values2.length; j++) {
var id2 = values2[j][0];
if (id2 == id1){
s1.getRange("A"+(i + 1)).offset(0, 3).setValue(values2[j][3]);
}
}
}
}
Visitez mon Site Google est mon ami, il répond mieux que moi, posez lui d'abord vos questions
gaston gaston
1 618 contributions
Membre depuis le 01/03/2001
Envoyé le 17/11/2023 à 02:29 Modifié par gaston


Bonjour,


2. il faudra aussi prévoir un code pour ajouter les lignes qui n'existeraient pas dans un tableau


Dans mon cas, ce n'est pas nécessaire, vu que le deuxième tableau est forcément un sous-ensemble du premier, même si pour lui toutes les colonnes sont renseignées

Pour le reste, je crois que je me suis mal expliqué:


4. une solution à explorer serait de remplacer le S1.getRange... par un 3ème tableau en mémoire


Voilà: c'est exactement ça que je voudrais faire ! un 3ème tableau en mémoire, dont on extrairait la colonne voulue pour ensuite la coller dans la feuille au bon endroit (ça ne me gêne pas d'écraser les valeurs existantes) et à ce moment-là pas besoin de se compliquer: comme je disais plus haut un setValues suffira pour cela: on a juste besoin que la colonne cible soit de la même taille que la colonne source

mais donc comment faire pour remplir ce 3ème tableau ?
carpe diem
Bipbipcoyote Bipbipcoyote
4 287 contributions
Membre depuis le 06/03/2001
Envoyé le 17/11/2023 à 15:16


Bonjour,
Ce que tu veux est difficile finalement, car il faut injecter les données dans ce 3ème tableau, pour le 1er tableau, pas de souci, mais ensuite, il faut injecter le 2eme tableau et là ça coince parce qu'il y a déjà des cellules du 3ème tableau qui sont remplies et on ne peut pas les écraser et comment alors trouver une ligne vide dans le 3ème tableau et qui ne crée pas un doublon ??

Mais, on se complique la tâche car on peut réduire le souci en utilisant une formule
il faut créer une colonne qui va regrouper les données
J'ai 2 feuilles identiques qui contiennent chacune un tableau
les tableaux de mon exemple se posent ainsi
En A1 : Id
En B1: Noms
En C1 ; Prénoms
En D1 : Age
Et ensuite un certain nombre de données (ici 5 lignes, la colonne Age n'est pas remplie partout.
Il est noté soit dans une feuille soit dans l'autre

Sur la feuille 1 en colonne F je pose la formule

=SI(D2<>"";D2;RECHERCHEV(A2;'Feuille 2'!$A$2:$D$5;4;FAUX))

ce qui signifie Si D n'est pas vide, je garde D, sinon je vais rechercher la valeur dans la Feuille 2 et FAUX parce que mes données ne sont pas triées sur la feuille 2

Ici les données 44 et 36 viennent de la Feuille 2 et Reno n'a pas d'âge ni sur Feuille 1 ni sur Feuille 2

Ensuite à partir de là, tu peux régulièrement remettre la colonne D de la feuille 1 à jour avec un script qui ira effacer la colonne correspondante dans la feuille 2, éventuellement masquer la colonne F de la feuille 1
Visitez mon Site Google est mon ami, il répond mieux que moi, posez lui d'abord vos questions
gaston gaston
1 618 contributions
Membre depuis le 01/03/2001
Envoyé le 17/11/2023 à 16:20 Modifié par gaston


Bonjour,

oups non, désolé cette solution ne me va pas du tout.
En fait, j'utilise un autre système à l'heure actuelle, et je voulais simplement voir s'il était possible de l'améliorer en vitesse, puisque d'après la doc, dans une feuille, pour faire une mise à jour par apps script, il vaut mieux que ce soit en bloc plutôt que ligne par ligne, comme je fais actuellement.

actuellement, donc j'utilise ce code qui fonctionne bien...sauf que c'est lent::

function myfonction() {
const modePai = FeuillRemiseT.getRange("L1").getValue();
const totalP = FeuillRemiseT.getRange("J3").getValue();
//test pour savoir le nb de cases cochées
const nbChcoches = FeuillRemiseT.getRange("H3").getValue();
if (nbChcoches == 0) //si aucune ligne n'est cochée
{ Browser.msgBox('Soit vous n\'avez pas formaté la liste, soit vous avez oublié de cocher au moins 1 ligne'); }
else
{
/**** je n'ai pas mis le code, mais ici je copie les données cochées pour les mettre dans un autre classeur ***/
/** maintenant je vais mettre à jour la feuille données **/
// calcule le nombre de lignes du fichier source = num dernière ligne - 4, car les 1ères données sont en ligne 6
const nbLignesSource = FeuillRemiseT.getLastRow()-4;
// met dans une variable les données de la ligne li, colonne 1 (A) jusqu'à la colonne 7 (G)
const paiements = FeuillRemiseT.getRange(5,1,nbLignesSource,7).getValues();
//filtre les données : la case doit être cochée
const paiemtsTrue = paiements.filter(dataRow => dataRow[6] === true);
const nbLiTrue = paiemtsTrue.length; //nb de lignes du tableau
if (nbLiTrue>0) //si le tableau n'est pas vide
{
//trouve la colonne de date d'Ech dans la feuille Donnees (varie selon l'Ech)
const cellDateEch = FeuillRemiseT.getRange("E2").getValue();
//parcours des lignes du tableau filtré afin de mettre à jour les valeurs cochées
for (li=0; li<nbLiTrue; li++)
{
//dans chaque ligne du tableau, l'id adhérent est la 1ère valeur:
let idAdherent = paiemtsTrue[li][0];
//pour chaque id, retrouve le n° de ligne de l'adhérent dans la feuille Donnees
const numeroLigne = trouverAdherentParSonId(idAdherent);
//test pour savoir si l'adhérent est trouvé
if (numeroLigne > 0)
{
//va dans la feuille Données, sur la cellule de la col qui est indiquée en E2 sur RemiseT (ex:Z pour Ech1)
cellCol = FeuilleDonnees.getRange(cellDateEch+numeroLigne);
// met la date du jour dans cette cellule de date d'Ech1, 2 ou 3
cellCol.setValue(new Date());
}
} //fin boucle
/* réinitialisation de la feuille source:
je supprime les cases à cocher, car les lignes étant issues d'une requête sur la feuille données, ayant pour condition qu'il n'y ait pas de date de remise, les lignes cochées ayant à présent une date, elles ne s'affichent plus */
FeuillRemiseT.getRange('G5:G').activate();
FeuillRemiseT.getActiveRangeList().clear({contentsOnly: true, validationsOnly: true});
FeuillRemiseT.getRange('G4').activate();
Browser.msgBox(nbChcoches +' paiements ont été remis');
}
}
}


mais donc, si on ne peut pas créer en mémoire ce troisième tableau dont on parlait plus haut, et faire un setValues(Tableau3) dans la colonne voulue, je laisse tel que c'est, pas la peine de se casser la tête [;)]
carpe diem
Bipbipcoyote Bipbipcoyote
4 287 contributions
Membre depuis le 06/03/2001
Envoyé le 17/11/2023 à 19:42 Modifié par Bipbipcoyote


Bonjour,
Je pense qu'il faut en revenir aux premiers principes de gestion d'une base de données.

Déjà mon fichier expliqué dans mon post précédent est une aberration, car on ne crée jamais 2 tableaux identiques, il faut respecter ce que l'on nomme en informatique le "single data entry" (entrée unique de données) donc retrouver un âge encodé sur 2 feuilles différentes ne doit jamais arriver...

Ici on pourrait imaginer que l'administrateur ait reçu 2 fichiers différents et qu'il prépare une fusion mais cela ne doit se passer qu'à l' initiation de l'application. Par la suite, l'encodeur NE PEUT PAS encoder une même donnée à 2 endroits différents, il faut lui fournir une interface où il peut effectuer les 4 opérations le plus souvent utilisées sur une base de données (Afficher, Ajouter, Modifier, Effacer) c'est ce que nous avons vu dans l'exemple étudiant

Ici on travaille dans un tableur et ce type d'application n'est pas fait à l'origine pour gérer une base de données mais bon, il faut donc l'imaginer ainsi, chaque ligne correspond à un enregistrement et chaque cellule correspond à un champ
l'encodeur entre des données au cas par cas en général, et même s'il devait en entrer par lot, les données ne doivent atterrir que dans un seul champ

Donc ta manœuvre franchement je ne la comprend pas, pour moi il y a une erreur de conception de l'application, surtout qu'elle a l'air de devoir être répétée, ce n'est pas normal

Si je reprends mon exemple, on peut faire ceci (je t'ai partagé un fichier)

function recuperationDeValeurs(){
var app = SpreadsheetApp; // Objet Application
var ss = app.getActiveSpreadsheet(); // le classeur en cours d'utilisation, attention aux parenthèses
var s1 = ss.getSheetByName('Feuille 1');
// 1. Retrouve les valeurs dans la feuille,
var values1 = s1.getDataRange().getValues(); // c'est un tableau en mémoire qui reprend toute la feuille 1

var tableau = Create2DArray(5); // c'est un tableau de 5 lignes

for (i = 1; i < values1.length; i++) {
if(values1[i][5]!== null){
tableau[i-1][0]=values1[i][5]; // on récupère la colonne F
}
}
// Logger.log(tableau);
s1.getRange('D2:D6').setValues(tableau);

}


function Create2DArray(rows) {
var arr = [];
for (var i=0;i<rows;i++) {
arr[i] = [];
}
return arr;
}
Visitez mon Site Google est mon ami, il répond mieux que moi, posez lui d'abord vos questions
gaston gaston
1 618 contributions
Membre depuis le 01/03/2001
Envoyé le 18/11/2023 à 20:32 Modifié par gaston


Bonjour,

heu, je n'ai pas tout compris de ta dernière réponse. Je crois qu'on n'était pas sur la même longueur d'onde
entre temps, une solution est apparue et plutôt simple (qui n'est pas de moi, mais je n'étais pas loin):

function TestMaj() {
var tableau1 = [[ 1, 'b01', 'c01', 'd01' ],
[ 2, 'b02', , 'd01' ],
[ 3, 'b03', , 'd03' ],
[ 4, 'b04', , 'd04' ],
[ 5, 'b05', , 'd05' ],
[ 6, 'b06', , 'd06' ],
[ 7, 'b07', 'c07', 'd07' ],
[ 8, 'b08', , 'd08' ],
[ 9, 'b09', , 'd09' ],
[ 10, 'b10', 'c10', 'd10' ] ]
Logger.log('tab1 au début: '+tableau1)
tableau2 = [[2, 'X03'],
[3, 'X03'],
[6, 'X03'],
[9, 'X03']]
for (let i = 0; i < tableau2.length; i++) {
for (let j = 0; j < tableau1.length; j++) {
if (tableau1[j][0] === tableau2[i][0]) {
tableau1[j][2] = tableau2[i][1];
break;
}
}
}
Logger.log('tab1 à la fin: '+tableau1)
}

carpe diem
Bipbipcoyote Bipbipcoyote
4 287 contributions
Membre depuis le 06/03/2001
Envoyé le 18/11/2023 à 20:56


Bonjour,
oui mais les tableaux ne viennent pas des feuilles du classeur et cela correspond à peu près à mes premières propositions, c'est bien une double boucle imbriquée
en adaptant un peu ce que j'avais proposé cela fonctionne aussi
Visitez mon Site Google est mon ami, il répond mieux que moi, posez lui d'abord vos questions
gaston gaston
1 618 contributions
Membre depuis le 01/03/2001
Envoyé le 19/11/2023 à 00:51


Bonjour,

Alors c'est moi qui n'avait pas compris. Ce qui m'a trompé par rapport à tes première propositions, c'est que tu revenais systématiquement à une feuille, alors que justement, je ne le voulais pas, c'est d'ailleurs pour cela que nulle part, je n'ai parlé de feuilles, et que mon exemple montrait des tableaux JavaScript

Mais en fait, les tableaux que j'utilise, viennent bien de feuilles du classeur: je les obtiens avec des getvalues

Là où j'ai un peu galéré en plus, (pendant 2h ! ) c'est après quand j'ai voulu isoler les données qui m'intéressaient : j'ai obtenu une ligne au lieu d'une colonne et j'ai dû transposer, avant de pouvoir faire mon setValues.
Il me reste à faire des tests sur de grosses données pour voir si je gagne en durée.
Je verrai ça demain parce que là je suis sur smartphone : j'ai éteint l'ordi, et apparemment, il vient d'installer une mise à jour et ça rame...
carpe diem
Bipbipcoyote Bipbipcoyote
4 287 contributions
Membre depuis le 06/03/2001
Envoyé le 19/11/2023 à 22:07 Modifié par Bipbipcoyote


Bonjour,
pff après moult essais, je suis parvenu à un résultat qui recopie tout le tableau
La manipulation des arrays est assez complexe
Bref, je suis arrivé à ceci
Tu peux tester le bidule dans le fichier que je t'ai partagé qui se nomme Comparer2Tableaux, avec mon petit tableau c'est rapide maintenant sur les grands tableaux ??? Le traitement est sans doute accéléré si les tableaux sont triés (function sort) car ainsi on peut utiliser l'instruction break.
Par contre, il faut ajouter encore un traitement si une personne existait dans le tableau2 et pas dans le tableau1

function copieDeValeurs() {
// initialisation du premier array
var s1 = SpreadsheetApp.getActive().getSheetByName("Feuille 1");
var lastRow1 = s1.getLastRow();
var tableau1 = s1.getRange("A2:D"+lastRow1).getValues();

// initialisation du deuxième array
var s2 = SpreadsheetApp.getActive().getSheetByName("Feuille 2");
var lastRow2 = s2.getLastRow();
var tableau2 = s2.getRange("A2:D"+lastRow2).getValues();
tableau2.sort();

// On parcourt les 2 tableaux - boucles imbriquées
for (i = 0; i < tableau1.length; i++){
for (j = 0; j < tableau2.length; j++){

//if faut que les index soient identiques
if(tableau1[i][0] == tableau2[j][0]){

//il faut que la cellule soit vide
if(tableau1[i][3] == ''){
tableau1[i][3] = tableau2[j][3];
}
else{

//sinon on garde la valeur
tableau1[i][3] = tableau1[i][3];
}
break;
}
}
}
//On recopie TOUT le tableau, y compris les noms, prénoms etc ...
s1.getRange("A2:D"+lastRow1).setValues(tableau1);
}
Visitez mon Site Google est mon ami, il répond mieux que moi, posez lui d'abord vos questions

Participer à cette discussion

« Retour sur la liste des messages de ce forum