Tema representativo de entrevista

Entrevista de código: Implementar un filtro de Bloom

CodingDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Implementa un filtro de Bloom con add(value) y mightContain(value). Explica cómo elegir la longitud del arreglo de bits m y la cantidad de hashes k a partir de la cardinalidad esperada n y la tasa objetivo de falsos positivos p, y analiza eliminación, redimensionamiento, concurrencia y pruebas.

Prompt y contexto

Implementa un filtro de Bloom con add(value) y mightContain(value). Es un prefiltro antes de una búsqueda costosa: false significa que el valor definitivamente está ausente, mientras que true significa que aún se debe consultar el almacén autoritativo. Cubre n, p, m, k, eliminación, redimensionamiento, concurrencia y pruebas.

Qué está evaluando el entrevistador

Semántica correcta de pertenencia

Un filtro de Bloom estándar permite falsos positivos pero no falsos negativos. El nombre mightContain debería evitar que quienes lo llamen traten true como prueba de pertenencia.

Dimensionamiento explicable

La longitud del arreglo de bits m y la cantidad de hashes k controlan la memoria, la velocidad y la tasa de error. Pregunta por la cardinalidad esperada y un p aceptable antes de elegir los parámetros.

Límites completos

Una respuesta sólida establece que la estructura estándar no puede eliminar un valor de forma segura, la saturación eleva la tasa de falsos positivos y el crecimiento requiere reconstruir o un diseño en capas escalable.

Preguntas para aclarar primero

  • ¿Cuántos valores se esperan y qué tasa de falsos positivos p es aceptable?
  • ¿El valor se serializa en una secuencia de bytes estable entre procesos y versiones?
  • ¿El filtro es de solo adición (append-only) o debe admitir eliminaciones y actualizaciones?
  • ¿Cuáles son los presupuestos de memoria, latencia y escrituras concurrentes?
  • Cuando se alcance la capacidad, ¿el filtro debería reconstruirse, rechazar escrituras o agregar una capa?
  • ¿Cómo se medirán los falsos positivos y las confirmaciones autoritativas?

Una respuesta de 30 segundos

“Usaría un arreglo de m bits y k posiciones suficientemente independientes. add activa esos k bits; un bit en cero durante una consulta demuestra ausencia, mientras que todos en uno significan posible presencia. Para un n esperado y un p objetivo, usa m=-n ln(p)/(ln2)^2 y k=(m/n)ln2. Un filtro estándar no puede eliminar de forma segura, por lo que la eliminación necesita contadores por celda; los cambios de capacidad requieren una reconstrucción o capas. Probaría la ausencia de falsos negativos, la tasa muestreada de falsos positivos, la saturación y las garantías de concurrencia”.

Respuesta detallada paso a paso

Establecer el invariante y la API

Todos los bits comienzan en cero. Funciones hash estables producen k índices para cada valor; la inserción solo cambia bits de cero a uno. Si una consulta observa un cero en cualquier índice requerido, ese valor no pudo haber insertado este conjunto exacto de posiciones.

Calcular m y k

Para n elementos esperados y una tasa objetivo de falsos positivos p, usa m = -n * ln(p) / (ln(2)^2) y k = (m/n) * ln(2). Con n=1,000,000 y p=1%, m es aproximadamente 9.6M de bits, cerca de 1.14 MiB, y k es aproximadamente 7.

Elegir hashes y operaciones de bits

El hashing doble puede derivar posiciones como h_i(x) = h1(x) + i*h2(x) módulo m, evitando k implementaciones completas de hash. Fija la codificación de bytes, el endianness y las semillas; cambiarlos hace que los filtros persistidos sean incompatibles.

Explicar eliminación y redimensionamiento

Varios valores pueden compartir un bit, por lo que limpiarlo para una sola eliminación puede crear un falso negativo. Por lo tanto, un filtro de Bloom estándar no tiene una operación de eliminación segura. Los filtros de Bloom por conteo agregan contadores por celda a costa de más memoria. Cuando la capacidad esperada cambia, reconstruye un filtro más grande o usa múltiples capas con límite de capacidad.

Manejar concurrencia y ciclo de vida

Las lecturas concurrentes suelen ser sencillas. Las escrituras concurrentes no deben perder operaciones de activación de bits; un OR atómico, arreglos de bits particionados o un bloqueo de escritura son opciones posibles. Persiste la capacidad, m, k, el algoritmo de hash, las semillas y la versión del formato juntos.

Pseudocódigo

~~~text add(x): for i in 0..k-1: bits[index(hash1(x), hash2(x), i)] = 1

mightContain(x): for i in 0..k-1: if bits[index(hash1(x), hash2(x), i)] == 0: return false return true ~~~

Complejidad y verificación

Cada operación verifica o establece k posiciones, por lo que el tiempo es O(k) y el espacio adicional es O(m). Prueba que cada valor insertado devuelva true, estima los falsos positivos a partir de muestras aleatorias ausentes, observa la saturación cerca de la capacidad y cubre casos vacíos, duplicados, de semilla/versión y de escritura concurrente.

OperaciónContratoComplejidad
add(x)Establece bits y nunca elimina evidencia de pertenenciaO(k)
mightContain(x)false es ausencia definitiva; true es posible presenciaO(k)
RedimensionarReconstruir o agregar una capa con límite de capacidadDepende de la cantidad de elementos y m

Respuesta modelo

“Un filtro de Bloom es un prefiltro probabilístico de pertenencia. Yo mantendría un arreglo de m bits y k funciones de posición. La inserción establece k bits; una consulta que encuentre cualquier cero devuelve false, mientras que todos en uno devuelve true pero solo como ‘posiblemente presente’, por lo que el almacén de respaldo lo confirma. Calcula m y k a partir de n y p; un millón de elementos al 1% de falsos positivos necesita alrededor de 9.6M de bits y siete posiciones. La estructura estándar no puede eliminar porque los bits se comparten; usa una variante de conteo para la eliminación y reconstruye o aplica capas a los filtros a medida que crece la capacidad. Fijaría la codificación y las semillas, luego probaría la ausencia de falsos negativos y mediría los falsos positivos en muestras ausentes”.

Errores comunes

Tratar true como prueba

Que todos los bits requeridos estén en uno puede ser resultado de otros valores. Quien realiza la llamada aún necesita una búsqueda autoritativa.

Usar un solo hash

Un solo hash puede distorsionar la distribución de bits y la tasa de error diseñada. Usa hashing doble o explica las suposiciones de independencia detrás de múltiples posiciones.

Limpiar bits para eliminar

Limpiar un bit compartido puede hacer que otro valor insertado devuelva false. Usa celdas de conteo o reconstruye en su lugar.

Ignorar la saturación

A medida que más bits se vuelven uno, los falsos positivos aumentan. Rastrea la cardinalidad estimada y la proporción de bits activos, y reconstruye antes de que se exceda el presupuesto.

Probar solo aciertos

Sin muestras ausentes, capacidad límite y pruebas de inserción de duplicados, la implementación no demuestra su contrato de error.

Preguntas de seguimiento y respuestas

¿Por qué los falsos positivos no pueden ser cero?

Diferentes valores pueden mapearse al mismo conjunto finito de bits. Más memoria y un k adecuado reducen la tasa pero no eliminan las colisiones.

¿Cuándo elegirías un filtro de Cuckoo?

Compáralo cuando la eliminación, el almacenamiento de huellas digitales (fingerprints) o el comportamiento de búsqueda sean importantes. Evalúa el rendimiento de memoria, escrituras y eliminaciones mediante benchmarks en lugar de elegir solo por el nombre.

¿Cómo lo persistirías?

Almacena el arreglo de bits con m, k, el algoritmo de hash, las semillas, la versión de codificación y la estimación de capacidad. Valida la versión al cargar.

¿Cómo monitorearías la calidad?

Mide los falsos positivos confirmados por el backend, la proporción de bits activos, la cardinalidad estimada, la latencia de búsqueda y la cantidad de reconstrucciones. Activa una reconstrucción o una nueva capa ante un umbral definido.

¿Qué suposiciones fundamentan la fórmula?

Asume un hashing casi uniforme, un volumen de inserción cercano a n y suficiente independencia entre las posiciones. Calibra con muestras de datos reales.

¿Cómo evitas escrituras concurrentes perdidas?

Usa activación atómica de bits o bloqueos particionados, y asegúrate de que los lectores observen escrituras completas. Si la consistencia eventual es aceptable, publica instantáneas inmutables fusionadas.

Fuentes públicas

Preguntas relacionadas

Herramienta de entrevista relacionada

Usa Captura para un ejercicio de código

Captura el problema y luego aborda en orden las restricciones, la solución, el código, los casos extremos y la complejidad.

Ver la herramienta