Tableaux, décaler des valeurs

Bonjour, à la suite d'un petit projet, je dois faire une fonction qui prend en paramètre un tableau d'entier et une valeur (un entier). Le but est de décaler nos valeurs de une place, en d'autre terme, Tab[i] = Tab[i+1] et lors de ma dernière valeur de tableau Tab[i] = valeur.
Voici le code que j'ai pu faire :

int Indice_max_Tableau = 5;

void setup() {
  Serial.begin(9600);
  int Tab[5] = {1,2,3,4,5};

  for (int i=0; i < Indice_max_Tableau;i++){
    Serial.println(Tab[i]);
  }

  int NewTab[5];
  int Valeur = 3;
  NewTab = Decalage(Tab[], Valeur);
  for (int j=0; j < Indice_max_Tableau;j++){
    Serial.println(NewTab[j]);
  }
}

void loop(){
}

int Decalage(int Tableau[], int Valeur){
  for (int i=0; i<Indice_max_Tableau;i++){
    Tableau[i] = Tableau[i+1];
  }
    Tableau[Indice_max_Tableau] = Valeur;
    return Tableau;
} 

Lors de ma compilation, on me dit que je dois mettre quelque chose entre mes crochets, mais pas moyen de comprendre pourquoi et comment résoudre ce problème.

NewTab = Decalage(Tab[], Valeur);

en C++ on ne passe pas un tableau sans donner sa taille. Si on ne veut pas passer la taille en paramètre, il faut passer un pointeur sur le premier élément du tableau (et ici vous connaissez la taille)

void Decalage(int* Tableau, int Valeur){
  for (int i=0; i<Indice_max_Tableau;i++){
    Tableau[i] = Tableau[i+1];
  }
    Tableau[Indice_max_Tableau] = Valeur;
} 

:warning: vérifiez bien vos indices... quand i vaut 4....

vous ne retournez pas non plus un nouveau tableau, donc le mieux c'est de en rien retourner

l'appel se fait par Decalage(Tab, Valeur);

1 Like

@A1one ,
J'espère que @J-M-L ne m'en voudra pas trop d'intervenir sur ce fil où il vient de vous donner la réponse à votre question sans vous donner les détails du code.

1ère version :

int Indice_max_Tableau = 5;
int Valeur = 3;

void setup() {
  Serial.begin(9600);
  int Tab[Indice_max_Tableau] = {1, 2, 3, 4, 5};

  for (int i = 0; i < Indice_max_Tableau  ; i++) Serial.print(Tab[i]);
  Serial.println("\n");
  
  Decalage(Tab, Valeur);
  
  for (int j = 0; j < Indice_max_Tableau  ; j++) Serial.println(Tab[j]);

}

void loop() {}

void Decalage(int *Tableau, int Valeur) {
    for (int i = 0; i < Indice_max_Tableau - 1   ; i++) {
    Tableau[i] = Tableau[i + 1];
  }
  Tableau[Indice_max_Tableau - 1] = Valeur;
}

2ème version :

int Indice_max_Tableau = 5;
int Valeur = 3;


void setup() {
  Serial.begin(9600);
  int Tab[Indice_max_Tableau] = {1, 2, 3, 4, 5};

  for (int i = 0; i < Indice_max_Tableau  ; i++) Serial.print(Tab[i]);
  Serial.println("\n");
  
  Decalage(Tab, Valeur);
  
  for (int j = 0; j < Indice_max_Tableau  ; j++) Serial.println(Tab[j]);
  
}

void loop() {}
void Decalage(int* t, int Valeur) {
  int i;
  for (i = 0; i < Indice_max_Tableau - 1 ; i++) {
    //   *(t+i) =  *(t+i) + 1;
    (*(t + i))++;
  }
  *(t + i) =  Valeur;
}

Bonne journée.

PS : c'est une façon pour moi de m'exercer.

c'est un forum libre, chacun fait comme il veut dans le respect de l'esprit du forum.
je ne suis pas là pour dire ce qui est bien ou pas !!

C'est vrai mais vous n'avez pas fourni le code final et c'est ce qu'il faut faire :

  • Le demandeur cherche la solution selon vos directives ;
  • on apprend et on retient mieux de cette façon.

Dans la mesure où vous aviez pris la main sur cette question, considérez ma remarque comme une marque de respect à votre égard.

Après je vois tout ça à mon petit niveau car il est vrai que vous lui avez fourni tous les éléments...

Bonne journée.

c'est vrai que je voulais le guider tout en le laissant chercher parce que ça ressemble à un devoir scolaire.

Parfois je donne aussi plus de code... chaque contributeur apporte sa pierre à l'édifice à sa façon.

C'est le mec radin d'octets qui parle. Si j'ai un tableau et que je dois changer d'indice toutes les valeurs, je procède ainsi: j'imagine un tableau "circulaire" dont je donne le point de départ. Ainsi décaler le tableau revient simplement à décaler l'origine. Cela donne:
Je définis mon tableau par

byte indiceMaxTableau = 5; // Taille du tableau
byte indiceZeroTableau = 0; // Décalage de l'origine du tableau
int tab[indiceMaxTableau] = {1, 2, 3, 4, 5};

Pour accéder à l'indice "indice" du tableau (au lieu de tab[indice]):

tab[(indice + indiceZeroTableau) % indiceMaxTableau]

Pour décaler le tableau vers le bas et mettre valeur en fin:

tab[indiceZeroTableau] = valeur
indiceZeroTableau = (indiceZeroTableau + 1) % indiceMaxTableau

Pour 5 valeurs cela ne vaut peut-être pas le coup.

1 Like

Ça consomme plus d’octets en fait, vous n’êtes pas radin (mais moins de cycles CPU)

Ma 2ème version était fausse. Voici la bonne :

int Indice_max_Tableau = 5;
int valeur = 3;


void setup() {
  Serial.begin(9600);
  int Tab[Indice_max_Tableau] = {1,2,3,4,5};

  for (int i = 0; i < Indice_max_Tableau  ; i++) Serial.print(Tab[i]);
  Serial.println("\n");
  
  Decalage(Tab, valeur);
  
  for (int j = 0; j < Indice_max_Tableau  ; j++) Serial.println(Tab[j]);
  
}

void loop() {}
void Decalage(int* t, int vale) {
  int i;
  for (i = 0; i < Indice_max_Tableau - 1 ; i++) {
     // *(t+i) =  *((t+i)+ 1) ;
         *(t+i) =  *(t + i + 1);
   
  }
  *(t + i) =  vale;
}

@dfgh me l'a fait remarquer avec une brillante démonstration.
Bonne journée à lui et à vous tous.

Bonjour @vileroi,

J'avoue qu'un tableau circulaire, ça ne me parle pas du tout !
J'ai plutôt l'habitude des blocs mémoires contigüs.
Je ne vois pas trop ce que viennent faire les modulos dans le code.

Tout au plus j'arrive à tirer ça de vos explications :

byte indiceMaxTableau = 5; // Taille du tableau
byte indiceZeroTableau = 0; // Décalage de l'origine du tableau
byte valeur = 7;
int tab[] = {4, 8, 3, 1, 5};

void setup() {
  Serial.begin(9600);
  for (int j = 0; j < indiceMaxTableau  ; j++) Serial.print(tab[j]);
   Serial.print("\n");
  Decalage(tab, valeur);
  for (int j = 0; j < indiceMaxTableau  ; j++) Serial.println(tab[j]);

}

void loop() {}

void Decalage(int *t, byte Valeur) {

  while (indiceZeroTableau < indiceMaxTableau - 1) {
    t[(indiceZeroTableau) % indiceMaxTableau] = t[(indiceZeroTableau + 1) % indiceMaxTableau];
    indiceZeroTableau = (indiceZeroTableau + 1) % indiceMaxTableau;
    t[indiceZeroTableau] = Valeur;
  }
}

Ce qui revient à ça :

byte indiceMaxTableau = 5; // Taille du tableau
byte indiceZeroTableau = 0; // Décalage de l'origine du tableau
byte valeur = 7;
int tab[] = {4, 8, 3, 1, 5};

void setup() {
  Serial.begin(9600);
  for (int j = 0; j < indiceMaxTableau  ; j++) Serial.print(tab[j]);
   Serial.print("\n");
  Decalage(tab, valeur);
  for (int j = 0; j < indiceMaxTableau  ; j++) Serial.println(tab[j]);

}

void loop() {}

void Decalage(int *t, byte Valeur) {

  while (indiceZeroTableau < indiceMaxTableau - 1) {
    t[indiceZeroTableau] = t[indiceZeroTableau + 1];
    indiceZeroTableau++;
    t[indiceZeroTableau] = Valeur;
  }
}


Voilà en plus @J-M-L vous répond :

Du coup si vous pouviez donner plus de détails, je vous en serai très reconnaissant.
Merci.

Je ne sais pas si c'est à ça que pensait @vileroi , mais je suppose qu'il parle de liste circulaire(ou liste chainée circulaire), qui peut être vue comme un tableau non continue en mémoire d'une certaine manière.
Dans ce cas là tu change la valeur de l'élément précèdent du déplace ton pointeur de début sur cet élément.
Le mieux étant alors d'avoir une liste doublement chainée pour éviter d'avoir trop de déplacement dans la liste.

on peut le faire avec un tableau aussi, c'est comme cela que le buffer du port série par exemple est géré.

@philippe86220 - en fait l'idée c'est d'éviter les mouvements mémoire qui sont couteux en temps CPU (imaginez que le tableau ait 1000 éléments, pour ajouter un élément à la fin vous allez bouger 999 éléments puis en écrire un) et de simplement déplacer le "début" du tableau pour décaler les éléments . Comme le tableau est un espace "non circulaire" le modulo permet de trouver l'élément suivant quand on arrive à la fin

Merci @J-M-L,
Je comprends mieux le principe.
Reste à trouver les détails.

Bonne soirée.

Merci également @terwal.

voici un exemple

const byte taille = 10;
byte tableau[taille];
byte prochaineCase = 0;
bool tableauPlein = false;

void imprimer() {
  if (tableauPlein) {
    for (byte i = prochaineCase; i < taille; i++) {
      Serial.print(tableau[i]);
      Serial.write(' ');
    }
    for (byte i = 0; i < prochaineCase; i++) {
      Serial.print(tableau[i]);
      Serial.write(' ');
    }
  } else {
    for (byte i = 0; i < prochaineCase; i++) {
      Serial.print(tableau[i]);
      Serial.write(' ');
    }
  }
  Serial.println();
}

void ajouter(byte valeur) {
  tableau[prochaineCase++] = valeur;
  if (prochaineCase >= taille) {
    prochaineCase = 0;
    tableauPlein = true;
  }
}

void setup() {
  Serial.begin(115200); Serial.println();
}

void loop() {
  ajouter(random(10, 100));
  imprimer();
  delay(100);
}

si vous exécutez ce code, vous verrez le tableau se remplir puis une fois plein vous verrez les valeurs se décaler vers la gauche à l'impression

77 
77 89 
77 89 33 
77 89 33 48 
77 89 33 48 50 
77 89 33 48 50 72 
77 89 33 48 50 72 34 
77 89 33 48 50 72 34 18 
77 89 33 48 50 72 34 18 63 
77 89 33 48 50 72 34 18 63 29 
89 33 48 50 72 34 18 63 29 10 
33 48 50 72 34 18 63 29 10 45 
48 50 72 34 18 63 29 10 45 52 
50 72 34 18 63 29 10 45 52 72 
72 34 18 63 29 10 45 52 72 37 
34 18 63 29 10 45 52 72 37 93 
18 63 29 10 45 52 72 37 93 17 
63 29 10 45 52 72 37 93 17 89 
29 10 45 52 72 37 93 17 89 80 
10 45 52 72 37 93 17 89 80 92 
45 52 72 37 93 17 89 80 92 43

on a l'impression qu'on a donc décalé toutes la valeurs du tableau mais en fait je ne maintiens que le bool qui me dit si le tableau a été rempli et la variable prochaineCase qui me dit où j'en suis dans mon buffer

Merci @J-M-L,

Je vais y regarder demain.
Bonne soirée.

Dans un tableau linéaire, il y a un indice de début (0) et un indice de fin (indiceMaxTableau-1)
Si on parcourt le tableau en ajoutant 1 ou -1 à l'indice, on finira par pointer en dehors du tableau.

Pour un tableau circulaire, on l'imagine sur un cercle en collant la dernière case à côté de la première. Si on parcourt le tableau arrivé en fin on passe au début. On peut le représenter ainsi:

image

Si je veux décaler tout le tableau, il suffit d'incrémenter/décrémenter l'indice de départ.

Bien sûr cette représentation n'est pas implantée en C, et on "déroule le tableau pour le coder:
image
Et on utilise une structure de tableau conventionnelle.
Pour accéder à la case d'indice indice du tableau circulaire, on va à l'adresse indice+indiceDeDepart du tableau linéaire du C. Mais si ce nombre dépasse indiceMaxTableau, on a rebouclé et il faut retirer indiceMaxTableau Cela peut se faire avec le modulo qui donne la bonne valeur que l'on dépasse le tableau linéaire du C d'un côté ou de l'autre.

Le gros intérêt de cette représentation est que l'on n'a pas à décaler les données. Par exemple dans le cas d'un envoi de donnée, on aura un indice de départ et un indice de fin du tableau. On peut remplir le tableau sans toucher aux paramètres qui vide le tableau. Un décalage au mauvais moment risque de dupliquer ou de perdre une valeur.

Non, c'est bien un tableau, et on utilise dessous une structure de tableau. L'intérêt d'une liste est que la taille n'est pas fixe.

Cela devrait donner quelque chose comme (non testé):

byte indiceMaxTableau = 5; // Taille du tableau
byte indiceZeroTableau = 0; // Décalage de l'origine du tableau
byte valeur = 7;
int tab[] = {4, 8, 3, 1, 5};

void setup() {
  Serial.begin(9600);
  for (int j = 0; j < indiceMaxTableau  ; j++) Serial.print(tab[(j+indiceZeroTableau)%indiceMaxTableau]);
   Serial.print("\n");
  Decalage(tab, valeur);
  for (int j = 0; j < indiceMaxTableau  ; j++) Serial.println(tab[(j+indiceZeroTableau)%indiceMaxTableau]);

}

void loop() {}

void Decalage(int *t, byte Valeur) {
  tab[indiceZeroTableau] = valeur
  indiceZeroTableau = (indiceZeroTableau + 1) % indiceMaxTableau
}

Dernière chose, si le tableau à comme dimension 256 valeur, on n'a plus besoin du modulo car le type byte est déja "circulaire": 255+1 donne 0.
Si le tableau est une puissance de 2, on remplace le modulo par un ET bit à bit. par exemple pour un tableau de 8 valeurs:
tab[(j+indiceZeroTableau) && 7]

ça fonctionne pour ce que vous faites mais attention à ne pas tomber dans le piège courant où l'on pense qu'une opération sur un byte va rester sur un byte

essayez ce code

void test(byte v) {
  Serial.print("indice direct = "); Serial.println(v + 1);
  byte r = v + 1;
  Serial.print("indice stocké = "); Serial.println(r);
}

void setup() {
  Serial.begin(115200);
  byte b = 100;
  test(b); // ==> imprime 101 OK

  b = 255;
  test(255); // ==> imprime 256 !!! on n'a plus un byte !
}

void loop() {}

la norme C++ définit que lorsque l'on a une opération mathématique avec une donnée tenant sur un octet (un byte signé ou pas) alors le byte est promu en entier (donc sur 2 octets ou 4 suivant la plateforme) et l'opération réalisée en entier. C'est pour cela que

Serial.println(v + 1);

va déborder.

Il faut bien s'assurer de stocker le résultat dans un byte avant de faire quoi que ce soit.

Bonjour @vileroi,
Bonjour @J-M-L,

Avec le bout de code de @vileroi , on visualise parfaitement le concept de tableau circulaire :

  • Au lieu de faire démarrer le tableau à l'indice zéro, j'utilise l'indice 4 indiceZeroTableau = 4; ;
  • J'ai augmenté la taille du tableau à 10 éléments indiceMaxTableau = 10; ;
  • le tableau de 10 éléments devient : int tab[] = {4, 8, 3, 1, 5, 9, 7, 2, 6, 0};.
byte indiceMaxTableau = 10; // Taille du tableau
byte indiceZeroTableau = 4; // Décalage de l'origine du tableau
byte valeur = 7;
int tab[] = {4, 8, 3, 1, 5, 9, 7, 2, 6, 0};

void setup() {
 Serial.begin(9600);
 for (int j = 0; j < indiceMaxTableau  ; j++) Serial.print(tab[(j+indiceZeroTableau)%indiceMaxTableau]);
  Serial.print("\n");
 Decalage(tab, valeur);
 for (int j = 0; j < indiceMaxTableau  ; j++) Serial.println(tab[(j+indiceZeroTableau)%indiceMaxTableau]);

}

void loop() {}

void Decalage(int *t, byte Valeur) {
 t[indiceZeroTableau] = Valeur;
 indiceZeroTableau = (indiceZeroTableau + 1) % indiceMaxTableau;
}

Du coup, un nouveau tableau est affiché dans le moniteur série mais cette fois-ci, il démarre à l'indice 4 : ce qui donne : "5972604831". Toujours 10 éléments mais lorsqu'on arrive à l'indice de fin le tableau : "0" grâce au modulo, on repart au premier indice et les 4 derniers éléments sur 10 du nouveau tableau virtuel deviennent les 4 premiers du tableau réel !
Ce nouveau tableau virtuel devient la base de travail :

Naturellement la procédure void Decalage(int *t, byte Valeur) donne :
9
7
2
6
0
4
8
3
1
7 (byte valeur = 7;)

Merci beaucoup à tous les deux pour le partage. J'ai bien compris ce qu'était le concept de tableau circulaire.

Bonne journée.

PS : j'ai bien noté aussi que :

Notez aussi que modulo est coûteux en temps de calcul sur petit MCU et donc c’est plus efficace de tester si on atteint une extrémité et remettre le compteur à l’autre bout

Merci @J-M-L
Bonne journée.