Générateurs : appels successifs pour "continuer" une opération

Lecture de données binaires bit à bit ou par octet

Le problème exposé dans ce sujet a été résolu.

Bonjour à tous !

Je bidouille avec les QR codes ces temps. J’en suis au point où je tente de lire de manière binaire les données, que j’ai sous forme de matrice. A terme, j’aimerais pouvoir lire le(s) bit(s) ou octet(s) suivant(s) au clic sur l’un ou l’autre bouton.

L’algorithme de parcours n’est pas vraiment le problème (même s’il est certainement très naïf et sous-optimisé), c’est plus ma compréhension des générateurs en JavaScript qui doit être erronée, et leur utilisation peut-être incohérente.

Soit le code suivant.

Code de test
class QRcode {
	version = 1;
	matrix = [];
	reader = null;

	constructor (version) {
		if (typeof version === 'undefined') {
			version = 1;
		}
		this.version = version;
		this.matrix = [
			[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
			[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1],
			[1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1],
			[1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1],
			[1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1],
			[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1],
			[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
			[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
			[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
			[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
			[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
			[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
			[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
			[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
			[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
			[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
			[1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
			[1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
			[1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
			[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
			[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
		];
		this.reader = this.readBit();
	}

	get size() {
		return this.version * 4 + 16;
	}

	readBit = function* () {
		console.log('start');
		for (let column = this.size; column >= 0; column -= 2) {
			const upwards = (((column / 2) % 2) == 0);
			for (let row = (upwards ? this.size : 0); row >= 0 && row <= this.size; (upwards ? row-- : row++)) {
				console.log(row, column, this.matrix[column][row]);
				yield this.matrix[row][column];
				console.log(row, column - 1, this.matrix[row][column - 1]);
				yield this.matrix[row][column - 1];
			}
		}
		console.log('end');
		return;
	}
	readBits = function (bitNumber) {
		const result = Array();
		for (const bit of this.reader) {
			console.log(bitNumber);
			result.push(bit);
			if (--bitNumber <= 0) break;
		}
		return result;
	}
	readOctet = function () {
		let result = 0;
		for (const bit of this.readBits(8)) {
			result = (result << 1) | bit;
		}
		console.log(result);
		return result.toString();
	}
};
qrCode = new QRcode(1);
console.log(qrCode.readOctet(), qrCode.readOctet());

Ce que je ne comprends pas, c’est que ce code fournit comme résultat la sortie ci-après.

Sortie en console avec le code de test ci-avant
start		L42:11
20 20 1		L46:13
8		L58:12
20 19 0		L48:13
7		L58:12
19 20 0		L46:13
6		L58:12
19 19 1		L48:13
5		L58:12
18 20 0		L46:13
4		L58:12
18 19 0		L48:13
3		L58:12
17 20 0		L46:13
2		L58:12
17 19 1		L48:13
1		L58:12
179		L69:11
0		L69:11
179 0		L74:9

Je constate que le générateur n’est pas du tout utilisé alors que je m’attendrais à avoir la sortie de débogage correspondant à la lecture du second octet.

Si, plutôt que de mettre le générateur dans une propriété de la classe, j’utilise ligne 57 directement la méthode readBit(), j’ai bien deux sorties de lecture, mais c’est toujours le premier octet — une logique que je peux comprendre.

Apparemment, ma méthode QRcode.readBit() n’est pas directement le générateur, mais un constructeur pour celui-ci. Du coup, je suis en train de m’embrouiller.

  1. Y a-t’il moyen d’éviter cette propriété pour l’instance du générateur, donc faire en sorte que readBit() soit le générateur ? Serait-ce juste une question de syntaxe ?
  2. Afin de pouvoir lire un nombre arbitraire de bits à la fois, mais en séquence, est-ce que ma méthode readBits() doit elle aussi être un générateur ? Le cas échéant, je n’arrive pas à voir les transformations nécessaires.
    La question sera similaire pour readOctet() et une méthode à venir readOctets().

Merci d’avance  :)

+0 -0

Hello,

readBit() est bien un générateur, tu peux le voir en faisant console.dir(this.reader) dans le constructeur.

Tu peux récupérer toutes ses valeurs avec console.dir([...qrCode.reader]); pour vérifier ce qu’il renvoie (j’ai 462 valeurs de mon côté).


Je pense plutôt que dans l’usage, comme tu utilises des boucles for (qui ont besoin de connaître la taille de l’itérable avant de tourner), ça parcoure tout l’itérable dès le premier appel à readBits(), qui est donc déjà terminé pour le second appel (il ne reste alors rien à parcourir, le résultat est donc vide).

Essaie peut-être d’utiliser la référence globale de reader et de naviguer avec next() pour récupérer chaque valeur individuelle.

Par exemple :

	readBits = function (bitNumber) {
		const result = [];

		let bit;

		while(true) {
			bit = this.reader.next().value;
			result.push(bit);
			bitNumber--;
			if (bitNumber <= 0) break;
		}

		return result;
	}

[…] (j’ai 462 valeurs de mon côté) […]

viki53

Il faudra que je creuse, pour un carré de 21×21, on ne devrait pas en avoir plus de 441  :-°

Je pense plutôt que dans l’usage, comme tu utilises des boucles for […], ça parcoure tout l’itérable dès le premier appel à readBits(), qui est donc déjà terminé pour le second appel […].

viki53

Je ne dois pas comprendre de quel itérable il est question, parce que je m’attendais naïvement à ce qu’on ait les traces d’exécution de tout le parcours, notamment le end de la ligne 52, or le code fourni dans le premier message ne montre rien de cela, c’est probablement ce qui m’a fourvoyé.

En tout cas, ta solution fonctionne, merci beaucoup !

A l’occasion, je reprendrai le projet de rétro-ingénierie pour ce vieux truc qui traîne

+0 -0

L’itérable du générateur (un générateur étant à la fois un itérateur et un itérable).

En gros quand tu fais un for(let value in this.reader) ça récupère toutes les valeurs de la matrice d’un seul coup, plutôt que de récupérer juste celles que tu souhaites vraiment lire, puisque le for lit tout l’itérable (puis boucle dessus) au lieu de la partie demandée.

D’accord, c’est logique.

Et du coup, cela masque une éventuelle sortie avec console.log() ? Parce que je n’ai pas tronqué la sortie que j’ai fourni, il n’y avait vraiment que cela.

+0 -0

Je suppose que tant que les valeurs ne sont pas vraiment lues, il n’exécute pas la suite du code.

En faisant console.dir([...qrCode.reader]); j’ai bien l’ensemble de logs car ça récupère toutes les valeurs. Mais que ce soit avec le for(… of …) ou le while(), j’ai effectivement juste les logs des valeurs réellement lues.

Je connaît pas trop les générateurs, mais faut croire qu’il y a un peu de magie sous le capot 😅


D’après le MDN c’est un peu ça :

Lorsqu’on appelle une fonction génératrice, son corps n’est pas exécuté immédiatement, c’est un itérateur qui est renvoyé pour la fonction. Lorsque la méthode next() de l’itérateur est appelée, le corps de la fonction génératrice est utilisé jusqu’à ce que la première expression yield soit trouvée. Cette expression définira la valeur à renvoyer pour l’itérateur.

Mozilla Developer Network
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte