À l'aide d'index générés de manière aléatoire et de requêtes simples, vous pouvez sélectionner de manière aléatoire des documents dans une collection ou un groupe de collections dans Cloud Firestore.
Cette réponse est divisée en 4 sections avec différentes options dans chaque section :
- Comment générer les index aléatoires
- Comment interroger les index aléatoires
- Sélection de plusieurs documents aléatoires
- Le réensemencement pour un aléatoire permanent
Comment générer les index aléatoires
La base de cette réponse est la création d'un champ indexé qui, lorsqu'il est ordonné de manière ascendante ou descendante, donne lieu à un classement aléatoire de tous les documents. Il existe différentes façons de créer un tel champ. Nous allons donc en examiner deux, en commençant par la plus facilement accessible.
Version Auto-Id
Si vous utilisez les identifiants automatiques générés de manière aléatoire fournis dans nos bibliothèques clients, vous pouvez utiliser ce même système pour sélectionner un document de manière aléatoire. Dans ce cas, l'index aléatoirement ordonné est l'identifiant du document.
Plus loin dans notre section sur les requêtes, la valeur aléatoire que vous générez est un nouvel auto-id ( iOS , Android , Web ) et le champ que vous interrogez est le __name__
et la "faible valeur" mentionnée plus loin est une chaîne vide. Cette méthode est de loin la plus simple pour générer l'indice aléatoire et fonctionne quels que soient la langue et la plate-forme.
Par défaut, le nom du document ( __name__
) n'est indexé que de manière ascendante, et vous ne pouvez pas non plus renommer un document existant sans le supprimer et le recréer. Si vous avez besoin de l'un ou l'autre de ces éléments, vous pouvez toujours utiliser cette méthode et stocker un auto-id comme un champ réel appelé random
plutôt que de surcharger le nom du document à cette fin.
Version de Random Integer
Lorsque vous écrivez un document, générez d'abord un nombre entier aléatoire dans une plage délimitée et définissez-le comme un champ appelé random
. En fonction du nombre de documents attendus, vous pouvez utiliser une plage délimitée différente pour gagner de la place ou réduire le risque de collisions (qui réduisent l'efficacité de cette technique).
Vous devez réfléchir aux langues dont vous avez besoin, car les considérations sont différentes. Alors que Swift est facile, JavaScript notamment peut avoir un problème :
- Entier de 32 bits : Idéal pour les petits (~10K peu de chances d'avoir une collision%5En),%20m%3D2%5E32,%20n%3D10000) ) des ensembles de données
- Entier de 64 bits : Grands ensembles de données (note : JavaScript ne supporte pas nativement, mais )
Cela créera un index avec vos documents triés de manière aléatoire. Plus tard, dans la section consacrée aux requêtes, la valeur aléatoire que vous générerez sera une autre de ces valeurs, et la "faible valeur" mentionnée plus loin sera -1.
Comment interroger les index aléatoires
Maintenant que vous avez un index aléatoire, vous allez vouloir l'interroger. Nous examinons ci-dessous quelques variantes simples pour sélectionner un document aléatoire, ainsi que des options pour en sélectionner plus d'un.
Pour toutes ces options, vous voudrez générer une nouvelle valeur aléatoire de la même forme que les valeurs indexées que vous avez créées lors de l'écriture du document, désignée par la variable random
en dessous. Nous allons utiliser cette valeur pour trouver un endroit aléatoire sur l'index.
Wrap-around
Maintenant que vous avez une valeur aléatoire, vous pouvez demander un document unique :
let postsRef = db.collection("posts")
queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: random)
.order(by: "random")
.limit(to: 1)
Vérifiez que cette opération a renvoyé un document. Si ce n'est pas le cas, relancez la requête en utilisant la "faible valeur" de votre index aléatoire. Par exemple, si vous avez utilisé l'option "Random Integers", alors lowValue
est 0
:
let postsRef = db.collection("posts")
queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: lowValue)
.order(by: "random")
.limit(to: 1)
Tant que vous n'avez qu'un seul document, vous aurez la garantie de renvoyer au moins un document.
Bi-directionnel
La méthode wrap-around est simple à mettre en œuvre et permet d'optimiser le stockage avec seulement un index ascendant activé. Un inconvénient est la possibilité que des valeurs soient injustement protégées. Par exemple, si les trois premiers documents (A, B, C) sur 10 000 ont des valeurs d'index aléatoires de A:409496, B:436496, C:818992, alors A et C ont un peu moins de 1/10 000 de chances d'être sélectionnés, tandis que B est protégé par la proximité de A et n'a que 1/160 000 de chances.
Plutôt que d'effectuer une requête dans une seule direction et de tourner autour si une valeur n'est pas trouvée, vous pouvez sélectionner aléatoirement entre >=
y <=
qui réduit de moitié la probabilité de valeurs injustement protégées, au prix d'un stockage double de l'index.
Si une direction ne donne aucun résultat, passez à l'autre direction :
queryRef = postsRef.whereField("random", isLessThanOrEqualTo: random)
.order(by: "random", descending: true)
.limit(to: 1)
queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: random)
.order(by: "random")
.limit(to: 1)
Sélection de plusieurs documents aléatoires
Souvent, vous voudrez sélectionner plus d'un document aléatoire à la fois. Il existe deux façons différentes d'ajuster les techniques ci-dessus en fonction des compromis que vous souhaitez obtenir.
Rincer et répéter
Cette méthode est simple. Il suffit de répéter le processus, en sélectionnant à chaque fois un nouvel entier aléatoire.
Cette méthode vous permettra d'obtenir des séquences aléatoires de documents sans craindre de voir les mêmes motifs se répéter.
La contrepartie est qu'elle sera plus lente que la méthode suivante puisqu'elle nécessite un aller-retour distinct vers le service pour chaque document.
Continuez comme ça
Dans cette approche, il suffit d'augmenter le nombre dans la limite jusqu'aux documents souhaités. C'est un peu plus complexe, car vous pourriez rendre 0..limit
documents dans l'appel. Vous devrez alors obtenir les documents manquants de la même manière, mais avec une limite réduite à la seule différence. Si vous savez qu'il y a plus de documents au total que le nombre que vous demandez, vous pouvez optimiser en ignorant le cas limite de ne jamais récupérer assez de documents au deuxième appel (mais pas au premier).
La contrepartie de cette solution réside dans la répétition des séquences. Bien que les documents soient ordonnés de manière aléatoire, si vous finissez par chevaucher des plages, vous verrez le même schéma que précédemment. Il existe des moyens d'atténuer ce problème, comme nous le verrons dans la section suivante sur le réensemencement.
Cette approche est plus rapide que le "rinçage et la répétition", car vous demanderez tous les documents en un seul appel dans le meilleur des cas ou en deux appels dans le pire des cas.
Le réensemencement pour un aléatoire permanent
Bien que cette méthode vous donne des documents de manière aléatoire, si l'ensemble de documents est statique, la probabilité que chaque document soit retourné sera également statique. C'est un problème car certaines valeurs peuvent avoir des probabilités injustement faibles ou élevées en fonction des valeurs aléatoires initiales qu'elles ont obtenues. Dans de nombreux cas d'utilisation, cela convient, mais dans certains cas, vous voudrez peut-être augmenter le caractère aléatoire à long terme pour avoir une chance plus uniforme de renvoyer un document.
Notez que les documents insérés finiront par s'intercaler, modifiant progressivement les probabilités, tout comme les documents supprimés. Si le taux d'insertion/suppression est trop faible compte tenu du nombre de documents, il existe quelques stratégies pour y remédier.
Multi-Random
Plutôt que de vous soucier du réensemencement, vous pouvez toujours créer plusieurs index aléatoires par document, puis sélectionner aléatoirement un de ces index à chaque fois. Par exemple, faites en sorte que le champ random
soit une carte avec les sous-champs 1 à 3 :
{'random': {'1': 32456, '2':3904515723, '3': 766958445}}
Maintenant, vous ferez des requêtes sur random.1, random.2, random.3 de manière aléatoire, créant ainsi une plus grande dispersion de l'aléa. Cela revient à échanger une augmentation du stockage contre une augmentation du calcul (écritures de documents) pour avoir à réensemencer.
Réensemencement sur les écritures
Chaque fois que vous mettez à jour un document, vous devez générer à nouveau la ou les valeurs aléatoires de l'attribut random
champ. Cela déplacera le document dans l'index aléatoire.
Répétition de la lecture
Si les valeurs aléatoires générées ne sont pas uniformément distribuées (elles sont aléatoires, donc on s'y attend), alors le même document peut être choisi un nombre disproportionné de fois. Il est facile de contrer ce phénomène en mettant à jour le document sélectionné au hasard avec de nouvelles valeurs aléatoires après sa lecture.
Étant donné que les écritures sont plus coûteuses et qu'elles peuvent avoir des points chauds, vous pouvez choisir de ne mettre à jour en lecture qu'un sous-ensemble du temps (par ex, if random(0,100) === 0) update;
).
1 votes
Une façon simple de récupérer des documents aléatoires est de rassembler toutes les clés des messages dans un tableau (
docA
,docB
,docC
,docD
) puis mélange le tableau et récupère les trois premières entrées, de sorte que le mélange puisse retourner quelque chose commedocB
,docD
,docA
.1 votes
Ok, c'est une bonne idée ! Mais comment obtenir les clés de la poste ? Merci pour la réponse.
0 votes
J'espère que ce lien sera utile d'un point de vue logique : stackoverflow.com/a/58023128/1318946