swift - Firestore: cómo obtener documentos aleatorios en una colección
database firebase (5)
Acabo de hacer que esto funcione en Angular 7 + RxJS, así que comparte aquí con personas que quieren un ejemplo.
Usé la respuesta de @Dan McGrath, y elegí estas opciones: versión entera aleatoria + enjuague y repetición para múltiples números. También usé las cosas explicadas en este artículo: RxJS, ¿dónde está el operador If-Else? para hacer declaraciones if / else en el nivel de transmisión (solo si alguno de ustedes necesita una cartilla sobre eso).
También tenga en cuenta que usé angularfire2 para una fácil integración de Firebase en Angular.
Aquí está el código:
import { Component, OnInit } from ''@angular/core'';
import { Observable, merge, pipe } from ''rxjs'';
import { map, switchMap, filter, take } from ''rxjs/operators'';
import { AngularFirestore, QuerySnapshot } from ''@angular/fire/firestore'';
@Component({
selector: ''pp-random'',
templateUrl: ''./random.component.html'',
styleUrls: [''./random.component.scss'']
})
export class RandomComponent implements OnInit {
constructor(
public afs: AngularFirestore,
) { }
ngOnInit() {
}
public buttonClicked(): void {
this.getRandom().pipe(take(1)).subscribe();
}
public getRandom(): Observable<any[]> {
const randomNumber = this.getRandomNumber();
const request$ = this.afs.collection(''your-collection'', ref => ref.where(''random'', ''>='', randomNumber).orderBy(''random'').limit(1)).get();
const retryRequest$ = this.afs.collection(''your-collection'', ref => ref.where(''random'', ''<='', randomNumber).orderBy(''random'', ''desc'').limit(1)).get();
const docMap = pipe(
map((docs: QuerySnapshot<any>) => {
return docs.docs.map(e => {
return {
id: e.id,
...e.data()
} as any;
});
})
);
const random$ = request$.pipe(docMap).pipe(filter(x => x !== undefined && x[0] !== undefined));
const retry$ = request$.pipe(docMap).pipe(
filter(x => x === undefined || x[0] === undefined),
switchMap(() => retryRequest$),
docMap
);
return merge(random$, retry$);
}
public getRandomNumber(): number {
const min = Math.ceil(Number.MIN_VALUE);
const max = Math.ceil(Number.MAX_VALUE);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}
Es crucial para mi aplicación poder seleccionar múltiples documentos al azar de una colección en firebase.
Dado que no hay una función nativa integrada en Firebase (que yo sepa) para lograr una consulta que haga exactamente esto, mi primer pensamiento fue usar cursores de consulta para seleccionar un índice de inicio y final aleatorio siempre que tenga la cantidad de documentos en la colección.
Este enfoque funcionaría, pero solo de manera limitada, ya que cada documento se entregaría en secuencia con sus documentos vecinos cada vez; sin embargo, si pudiera seleccionar un documento por su índice en su colección principal, podría lograr una consulta de documento aleatoria, pero el problema es que no puedo encontrar ninguna documentación que describa cómo puede hacer esto o incluso si puede hacerlo.
Esto es lo que me gustaría poder hacer, considere el siguiente esquema de almacén de incendios:
root/
posts/
docA
docB
docC
docD
Luego, en mi cliente (estoy en un entorno Swift) me gustaría escribir una consulta que pueda hacer esto:
db.collection("posts")[0, 1, 3] // would return: docA, docB, docD
¿Hay alguna forma de que pueda hacer algo en este sentido? ¿O hay una forma diferente de seleccionar documentos aleatorios de manera similar?
Por favor ayuda.
Para aquellos que usan Angular + Firestore, basándose en las técnicas de @Dan McGrath, aquí está el fragmento de código.
A continuación, el fragmento de código devuelve 1 documento.
getDocumentRandomlyParent(): Observable<any> {
return this.getDocumentRandomlyChild()
.pipe(
expand((document: any) => document === null ? this.getDocumentRandomlyChild() : EMPTY),
);
}
getDocumentRandomlyChild(): Observable<any> {
const random = this.afs.createId();
return this.afs
.collection(''my_collection'', ref =>
ref
.where(''random_identifier'', ''>'', random)
.limit(1))
.valueChanges()
.pipe(
map((documentArray: any[]) => {
if (documentArray && documentArray.length) {
return documentArray[0];
} else {
return null;
}
}),
);
}
1) .expand () es una operación rxjs para la recursión para garantizar que definitivamente obtengamos un documento de la selección aleatoria.
2) Para que la recursión funcione como se espera, necesitamos tener 2 funciones separadas.
3) Utilizamos EMPTY para terminar el operador .expand ().
import { Observable, EMPTY } from ''rxjs'';
Publicar esto para ayudar a cualquiera que tenga este problema en el futuro.
Si está utilizando identificaciones automáticas, puede generar una nueva identificación automática y consultar la identificación automática más cercana como se menciona en la respuesta de Dan McGrath .
Recientemente creé una API de cotización aleatoria y necesitaba obtener cotizaciones aleatorias de una colección de tiendas de fuego.
Así es como resolví ese problema:
var db = admin.firestore();
var quotes = db.collection("quotes");
var key = quotes.doc().id;
quotes.where(admin.firestore.FieldPath.documentId(), ''>'', key).limit(1).get()
.then(snapshot => {
if(snapshot.size > 0) {
snapshot.forEach(doc => {
console.log(doc.id, ''=>'', doc.data());
});
}
else {
var quote = quotes.where(admin.firestore.FieldPath.documentId(), ''<'', key).limit(1).get()
.then(snapshot => {
snapshot.forEach(doc => {
console.log(doc.id, ''=>'', doc.data());
});
})
.catch(err => {
console.log(''Error getting documents'', err);
});
}
})
.catch(err => {
console.log(''Error getting documents'', err);
});
La clave de la consulta es esta:
.where(admin.firestore.FieldPath.documentId(), ''>'', key)
Y volver a llamarlo con la operación invertida si no se encuentran documentos.
¡Espero que esto ayude!
Si está interesado, puede encontrar esta parte específica de
mi API
en
GitHub
Tengo una forma de obtener un documento de lista al azar en Firebase Firestore, es realmente fácil. Cuando subo datos en Firestore, creo un nombre de campo "posición" con un valor aleatorio de 1 a 1 millón. Cuando obtenga datos de Fire store, estableceré Ordenar por campo "Posición" y actualizaré el valor, muchos datos de carga de usuario y datos siempre se actualizan y será un valor aleatorio.
Usando índices generados aleatoriamente y consultas simples, puede seleccionar aleatoriamente documentos de una colección o grupo de colecciones en Cloud Firestore.
Esta respuesta se divide en 4 secciones con diferentes opciones en cada sección:
- Cómo generar los índices aleatorios
- Cómo consultar los índices aleatorios
- Seleccionar múltiples documentos aleatorios
- Re-replanteo para aleatoriedad continua
Cómo generar los índices aleatorios
La base de esta respuesta es crear un campo indexado que, cuando se ordena ascendente o descendente, da como resultado que todo el documento se ordene aleatoriamente. Hay diferentes formas de crear esto, así que echemos un vistazo a 2, comenzando con el más fácilmente disponible.
Versión de identificación automática
Si está utilizando los identificadores automáticos generados aleatoriamente provistos en nuestras bibliotecas cliente, puede usar este mismo sistema para seleccionar aleatoriamente un documento. En este caso, el índice ordenado al azar es la identificación del documento.
Más adelante en nuestra sección de consultas, el valor aleatorio que genera es una nueva identificación automática (
iOS
,
Android
,
Web
) y el campo que consulta es el campo
__name__
, y el ''valor bajo'' mencionado más adelante es una cadena vacía.
Este es, con mucho, el método más fácil para generar el índice aleatorio y funciona independientemente del idioma y la plataforma.
De forma predeterminada, el nombre del documento (
__name__
) solo se indexa de forma ascendente, y tampoco puede cambiar el nombre de un documento existente sin eliminarlo ni volver a crearlo.
Si necesita cualquiera de estos, aún puede usar este método y simplemente almacenar una identificación automática como un campo real llamado
random
lugar de sobrecargar el nombre del documento para este propósito.
Versión entera aleatoria
Cuando escriba un documento, primero genere un entero aleatorio en un rango acotado y configúrelo como un campo llamado
random
.
Dependiendo de la cantidad de documentos que espere, puede usar un rango limitado diferente para ahorrar espacio o reducir el riesgo de colisiones (lo que reduce la efectividad de esta técnica).
Debe considerar qué idiomas necesita, ya que habrá diferentes consideraciones. Si bien Swift es fácil, JavaScript puede tener un problema:
- Entero de 32 bits: ideal para conjuntos de datos pequeños (~ 10K poco probable que tengan una colisión )
- Entero de 64 bits: conjuntos de datos grandes (nota: JavaScript aún no es compatible de forma nativa)
Esto creará un índice con sus documentos ordenados al azar. Más adelante en nuestra sección de consultas, el valor aleatorio que genere será otro de estos valores, y el ''valor bajo'' mencionado más adelante será -1.
Cómo consultar los índices aleatorios
Ahora que tiene un índice aleatorio, querrá consultarlo. A continuación, observamos algunas variantes simples para seleccionar un documento aleatorio 1, así como opciones para seleccionar más de 1.
Para todas estas opciones, querrá generar un nuevo valor aleatorio en la misma forma que los valores indexados que creó al escribir el documento, denotado por la variable
random
continuación.
Usaremos este valor para encontrar un lugar aleatorio en el índice.
Envolver alrededor
Ahora que tiene un valor aleatorio, puede consultar un solo documento:
let postsRef = db.collection("posts")
queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: random)
.order(by: "random")
.limit(to: 1)
Verifique que esto haya devuelto un documento.
Si no es así, consulta nuevamente pero usa el ''valor bajo'' para tu índice aleatorio.
Por ejemplo, si hiciste números enteros aleatorios, entonces
lowValue
es
0
:
let postsRef = db.collection("posts")
queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: lowValue)
.order(by: "random")
.limit(to: 1)
Mientras tenga un solo documento, se le garantizará que devolverá al menos 1 documento.
Bidireccional
El método envolvente es simple de implementar y le permite optimizar el almacenamiento con solo un índice ascendente habilitado. Una desventaja es la posibilidad de que los valores estén protegidos injustamente. Por ejemplo, si los primeros 3 documentos (A, B, C) de 10K tienen valores de índice aleatorio de A: 409496, B: 436496, C: 818992, entonces A y C tienen una probabilidad de menos de 1 / 10K de ser seleccionados, mientras que B está efectivamente protegido por la proximidad de A y solo aproximadamente una probabilidad de 1 / 160K.
En lugar de consultar en una sola dirección y ajustar si no se encuentra un valor, puede seleccionar aleatoriamente entre
>=
y
<=
, lo que reduce la probabilidad de valores injustamente protegidos a la mitad, a costa de duplicar el almacenamiento del índice.
Si una dirección no devuelve resultados, cambie a la otra dirección:
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)
Seleccionar múltiples documentos aleatorios
A menudo, querrá seleccionar más de 1 documento aleatorio a la vez. Hay 2 formas diferentes de ajustar las técnicas anteriores según las compensaciones que desee.
Enjuague y repita
Este método es sencillo. Simplemente repita el proceso, incluida la selección de un nuevo entero aleatorio cada vez.
Este método le dará secuencias aleatorias de documentos sin preocuparse de ver los mismos patrones repetidamente.
La compensación es que será más lento que el siguiente método, ya que requiere un viaje de ida y vuelta por separado al servicio para cada documento.
Sigue viniendo
En este enfoque, simplemente aumente el número en el límite de los documentos deseados.
Es un poco más complejo ya que puede devolver
0..limit
documentos
0..limit
en la llamada.
Luego, deberá obtener los documentos que faltan de la misma manera, pero con el límite reducido solo a la diferencia.
Si sabe que hay más documentos en total que el número que está solicitando, puede optimizarlo ignorando el caso extremo de nunca recuperar suficientes documentos en la segunda llamada (pero no en la primera).
La compensación con esta solución está en secuencias repetidas. Si bien los documentos se ordenan al azar, si alguna vez terminas superponiendo rangos, verás el mismo patrón que viste antes. Hay formas de mitigar esta preocupación que se discuten en la siguiente sección sobre reenvío.
Este enfoque es más rápido que ''Enjuagar y repetir'', ya que solicitará todos los documentos en el mejor de los casos, una sola llamada o el peor de los casos, 2 llamadas.
Re-replanteo para aleatoriedad continua
Si bien este método le brinda documentos al azar si el conjunto de documentos es estático, la probabilidad de que cada documento sea devuelto también será estática. Este es un problema ya que algunos valores pueden tener probabilidades injustamente bajas o altas basadas en los valores aleatorios iniciales que obtuvieron. En muchos casos de uso, esto está bien, pero en algunos, es posible que desee aumentar la aleatoriedad a largo plazo para tener una posibilidad más uniforme de devolver 1 documento.
Tenga en cuenta que los documentos insertados terminarán entretejidos, cambiando gradualmente las probabilidades, al igual que la eliminación de documentos. Si la tasa de inserción / eliminación es demasiado pequeña dada la cantidad de documentos, existen algunas estrategias para abordar esto.
Multi-aleatorio
En lugar de preocuparse por la reposición, siempre puede crear múltiples índices aleatorios por documento, luego seleccionar aleatoriamente uno de esos índices cada vez.
Por ejemplo, haga que el campo sea
random
como un mapa con los subcampos 1 a 3:
{''random'': {''1'': 32456, ''2'':3904515723, ''3'': 766958445}}
Ahora consultará al azar.1, random.2, random.3 al azar, creando una mayor dispersión de aleatoriedad. Esto esencialmente intercambia un mayor almacenamiento para ahorrar un mayor cómputo (escritura de documentos) de tener que reiniciar.
Reseed en escrituras
Cada vez que actualice un documento, vuelva a generar los valores
random
campo
random
.
Esto moverá el documento en el índice aleatorio.
Reseed en lecturas
Si los valores aleatorios generados no están distribuidos de manera uniforme (son aleatorios, por lo que se espera), entonces el mismo documento podría seleccionarse una cantidad de tiempo inadecuada. Esto se contrarresta fácilmente actualizando el documento seleccionado aleatoriamente con nuevos valores aleatorios después de leerlo.
Dado que las escrituras son más caras y pueden
if random(0,100) === 0) update;
, puede elegir actualizar solo al leer un subconjunto del tiempo (por ejemplo,
if random(0,100) === 0) update;
)