Búsqueda Fuzzy sobre un Array Object en Javascript con Underscore

En ocasiones necesitamos realizar una búsqueda Fuzzy dentro de una colección de objetos, para determinar cuales de los objetos de la colección posee el valor que estamos buscando.

Generalmente esta técnica es utilizada para filtrar tablas, como lo hace DataTables, pero … porque no tener esta funcionalidad de manera nativa para todos los Array Object que tengas en tu Aplicación.

Antes que nada, recordemos que necesitamos Underscore para su implementación, si bien, con un poco más de esfuerzo podemos prescindir de esta librería.

Lo primero que vamos a necesitar es crear un método que nos permita realizar una búsqueda similar a lo que ya realiza indexOf pero en este caso permitiéndonos aplicar Regexp, el cual vamos a llamar regIndexOf, ahora veamos como es su implementación:

/**
* Regular Expresion IndexOf for Arrays
* This little addition to the Array prototype will
* iterate over array and return the index of the
* first element which matches the provided regular
* expresion.
*
* Note: This will not match on objects.
*
* @param {RegEx} rx The regular expression to
* test with. E.g. /-ba/gim
* @return {number} -1 means not found
*/
Array.prototype.regIndexOf = function (rx) {
for (var i in this) {
if (this[i].toString().match(rx)) {
return i;
}
}

return -1;
};

Este método, por el momento nos permite buscar un valor dentro de una Array simple, del estilo de:

var arr = ['German', 'Dario', 'Guadalupe', 'Sofia'];
arr.regIndexOf(/germ/i);

El ejemplo anterior nos devolvería 0 (cero), la posición del primer elemento del Array, que es el contiene el nombre “German”.

¿ Entonces para que nos ayudaría este método, si en realidad estamos pensando realizar una búsqueda Fuzzy dentro de una colección de objetos ?

Bien, si logramos hacer que los objetos dentro de la colección se aplanen y por un momento desaparezcan los nombres de sus propiedades, esto nos permitiría aplicar el método que acabamos de crear (regIndexOf). ¿ Porque hacemos esto ? porque en realidad lo que nos interesa el ubicar si el valor que estamos buscando se encuentra entre los valores de las propiedades de cada objeto de la colección, una vez que obtenemos la posición del objeto que contiene el valor en al menos una de sus propiedades, con la cual podemos obtener el objeto de la colección original, logrando así nuestro objetivo.

Para lograr aplanar un objeto, del cual no conocemos su estructura y mucho menos su profundidad, necesitaremos crear un método recursivo, con la capacidad de visitar cada una de las propiedades del objeto, obtener su valor y apilarlo.

Veamos como es la implementación de lo que acabamos de describir:

/**
* Fuzzy Search in a Collection
*
* @param search Regex that represents what is going
* to be searched
*
* @return {Array} ArrayObject with an object of what
* we are looking for
*/
Array.prototype.fuzzy = function(search) {
var _return = [];
    /**
* Runs deep the object, to his last nodes and
* returns an array with all the values.
*
* @param object Object that is going to be
* analized
* @return {Array} with all the values of the
* object at the same level
*/
var recursive = function(object) {
return _.map(object, function (obj, key) {
if (typeof obj !== ‘object’) {
return obj.toString();
} else {
return recursive(obj);
}
});
};
     // Search inside the flatten array which was 
// returned by recursive
_.each(recursive(this), function (obj, key) {
if (obj.regIndexOf(search) > -1) {
_return.push(this[key]);
}
}, this);
    return _return;
};

Con estos dos métodos (regIndeOf y fuzzy) ya estamos listos. Bien, ahora podemos realizar una búsqueda Fuzzy en una Colección, para esto vamos a crear una de ejemplo:

var collection = [{
name: 'German',
language: [{
name: 'Javascript',
expertise: 8
}, {
name: 'Node.js',
expertise: 7
}]
}, {
name: 'Diego',
language: [{
name: 'HTML',
expertise: 9
}, {
name: 'CSS',
expertise: 10
}]
}];

Una vez que tenemos nuestra colección de ejemplo solo nos resta jugar con el la búsqueda Fuzzy:

// Busca la palabra "node" insensitive
collection.fuzzy(/node/i);
// También podemos buscar un texto
collection.fuzzy('Node');

El siguiente es un ejemplo de la implementación descripta para que lo puedan probar con solo copiar y pegar:

Like what you read? Give Germán Peraferrer a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.