¿Cómo se implementan las sugerencias automáticas utilizando la nueva API AnalyzingInfixSuggester de Lucene?

Soy Greenhand en Lucene, y quiero implementar la sugerencia automática, al igual que Google, cuando ingreso un personaje como ‘G’, me daría una lista, puedes probar tu auto.

Tengo búsqueda en toda la red. Nadie ha hecho esto, y nos da algunas herramientas nuevas en el paquete sugieren

Pero necesito un ejemplo para decirme cómo hacer eso

¿Hay alguien que pueda ayudar?

Le daré un ejemplo bastante completo que le muestra cómo usar AnalyzingInfixSuggester . En este ejemplo, simularemos que somos Amazon y queremos completar automáticamente un campo de búsqueda de productos. Aprovecharemos las características del sistema de sugerencias de Lucene para implementar lo siguiente:

  1. Resultados clasificados: primero sugeriremos los productos que coincidan más populares.
  2. Resultados restringidos a la región: solo sugeriremos productos que vendamos en el país del cliente.
  3. Fotos del producto: almacenaremos las URL de las fotografías de los productos en el índice de sugerencias para que podamos mostrarlas en los resultados de búsqueda, sin tener que realizar una búsqueda adicional en la base de datos.

Primero definiré una clase simple para contener información sobre un producto en Product.java:

 import java.util.Set; class Product implements java.io.Serializable { String name; String image; String[] regions; int numberSold; public Product(String name, String image, String[] regions, int numberSold) { this.name = name; this.image = image; this.regions = regions; this.numberSold = numberSold; } } 

Para indexar los registros con el método de build AnalyzingInfixSuggester , debe pasarle un objeto que implemente la interfaz org.apache.lucene.search.suggest.InputIterator . Un InputIterator da acceso a la clave , contextos , carga y peso para cada registro.

La clave es el texto que realmente desea buscar y completar automáticamente. En nuestro ejemplo, será el nombre del producto.

Los contextos son un conjunto de datos arbitrarios adicionales que puede usar para filtrar registros. En nuestro ejemplo, los contextos son el conjunto de códigos ISO para los países a los que enviaremos un producto en particular.

La carga útil es datos arbitrarios adicionales que desea almacenar en el índice para el registro. En este ejemplo, en realidad serializaremos cada instancia de Product y almacenaremos los bytes resultantes como la carga útil. Luego, cuando realicemos búsquedas más adelante, podemos deserializar la carga útil y acceder a la información en la instancia del producto, como la URL de la imagen.

El peso se usa para ordenar resultados de sugerencias; los resultados con un mayor peso se devuelven primero. Usaremos la cantidad de ventas de un producto dado como su peso.

Aquí está el contenido de ProductIterator.java:

 import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.UnsupportedEncodingException; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.apache.lucene.search.suggest.InputIterator; import org.apache.lucene.util.BytesRef; class ProductIterator implements InputIterator { private Iterator productIterator; private Product currentProduct; ProductIterator(Iterator productIterator) { this.productIterator = productIterator; } public boolean hasContexts() { return true; } public boolean hasPayloads() { return true; } public Comparator getComparator() { return null; } // This method needs to return the key for the record; this is the // text we'll be autocompleting against. public BytesRef next() { if (productIterator.hasNext()) { currentProduct = productIterator.next(); try { return new BytesRef(currentProduct.name.getBytes("UTF8")); } catch (UnsupportedEncodingException e) { throw new Error("Couldn't convert to UTF-8"); } } else { return null; } } // This method returns the payload for the record, which is // additional data that can be associated with a record and // returned when we do suggestion lookups. In this example the // payload is a serialized Java object representing our product. public BytesRef payload() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); out.writeObject(currentProduct); out.close(); return new BytesRef(bos.toByteArray()); } catch (IOException e) { throw new Error("Well that's unfortunate."); } } // This method returns the contexts for the record, which we can // use to restrict suggestions. In this example we use the // regions in which a product is sold. public Set contexts() { try { Set regions = new HashSet(); for (String region : currentProduct.regions) { regions.add(new BytesRef(region.getBytes("UTF8"))); } return regions; } catch (UnsupportedEncodingException e) { throw new Error("Couldn't convert to UTF-8"); } } // This method helps us order our suggestions. In this example we // use the number of products of this type that we've sold. public long weight() { return currentProduct.numberSold; } } 

En nuestro progtwig de controladores, haremos las siguientes cosas:

  1. Crea un directorio de índice en la RAM.
  2. Crea un StandardTokenizer .
  3. Crea un AnalyzingInfixSuggester usando el directorio RAM y tokenizer.
  4. Indique una cantidad de productos utilizando ProductIterator .
  5. Imprima los resultados de algunas búsquedas de muestra.

Aquí está el progtwig del controlador, SuggestProducts.java:

 import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.search.suggest.analyzing.AnalyzingInfixSuggester; import org.apache.lucene.search.suggest.Lookup; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.Version; public class SuggestProducts { // Get suggestions given a prefix and a region. private static void lookup(AnalyzingInfixSuggester suggester, String name, String region) { try { List results; HashSet contexts = new HashSet(); contexts.add(new BytesRef(region.getBytes("UTF8"))); // Do the actual lookup. We ask for the top 2 results. results = suggester.lookup(name, contexts, 2, true, false); System.out.println("-- \"" + name + "\" (" + region + "):"); for (Lookup.LookupResult result : results) { System.out.println(result.key); Product p = getProduct(result); if (p != null) { System.out.println(" image: " + p.image); System.out.println(" # sold: " + p.numberSold); } } } catch (IOException e) { System.err.println("Error"); } } // Deserialize a Product from a LookupResult payload. private static Product getProduct(Lookup.LookupResult result) { try { BytesRef payload = result.payload; if (payload != null) { ByteArrayInputStream bis = new ByteArrayInputStream(payload.bytes); ObjectInputStream in = new ObjectInputStream(bis); Product p = (Product) in.readObject(); return p; } else { return null; } } catch (IOException|ClassNotFoundException e) { throw new Error("Could not decode payload :("); } } public static void main(String[] args) { try { RAMDirectory index_dir = new RAMDirectory(); StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_48); AnalyzingInfixSuggester suggester = new AnalyzingInfixSuggester( Version.LUCENE_48, index_dir, analyzer); // Create our list of products. ArrayList products = new ArrayList(); products.add( new Product( "Electric Guitar", "http://sofes.miximages.com/java/electric-guitar.jpg", new String[]{"US", "CA"}, 100)); products.add( new Product( "Electric Train", "http://sofes.miximages.com/java/train.jpg", new String[]{"US", "CA"}, 100)); products.add( new Product( "Acoustic Guitar", "http://sofes.miximages.com/java/acoustic-guitar.jpg", new String[]{"US", "ZA"}, 80)); products.add( new Product( "Guarana Soda", "http://sofes.miximages.com/java/soda.jpg", new String[]{"ZA", "IE"}, 130)); // Index the products with the suggester. suggester.build(new ProductIterator(products.iterator())); // Do some example lookups. lookup(suggester, "Gu", "US"); lookup(suggester, "Gu", "ZA"); lookup(suggester, "Gui", "CA"); lookup(suggester, "Electric guit", "US"); } catch (IOException e) { System.err.println("Error!"); } } } 

Y aquí está la salida del progtwig del controlador:

 -- "Gu" (US): Electric Guitar image: http://images.example/electric-guitar.jpg # sold: 100 Acoustic Guitar image: http://images.example/acoustic-guitar.jpg # sold: 80 -- "Gu" (ZA): Guarana Soda image: http://images.example/soda.jpg # sold: 130 Acoustic Guitar image: http://images.example/acoustic-guitar.jpg # sold: 80 -- "Gui" (CA): Electric Guitar image: http://images.example/electric-guitar.jpg # sold: 100 -- "Electric guit" (US): Electric Guitar image: http://images.example/electric-guitar.jpg # sold: 100 

Apéndice

Hay una manera de evitar escribir un InputIterator completo que pueda encontrar más fácil. Puede escribir un trozo de InputIterator que devuelve null de su next método de payload y contexts . Pase una instancia de este al método de build AnalyzingInfixSuggester :

 suggester.build(new ProductIterator(new ArrayList().iterator())); 

Luego, para cada elemento que desee indexar, llame al método de add AnalyzingInfixSuggester :

 suggester.add(text, contexts, weight, payload) 

Después de indexar todo, llame a refresh :

 suggester.refresh(); 

Si está indexando grandes cantidades de datos, es posible acelerar significativamente la indexación usando este método con varios hilos: build llamadas, luego use varios hilos para add elementos, y finalmente llame a refresh .

[Editado el 23/04/2015 para demostrar la deserialización de la información de la carga útil de LookupResult ].