Idea
SPIP almacena en las tablas index_* la información sobre la idexación de los artículos. Por el momento, estas tablas sólo se utilizan para calcular los resultados de una búsqueda.
La estructura es simple :
- hay una tabla diccionario central, que lista todas las palabras importantes encontradas en el sitio,
- para cada tipo de objeto hay una tabla que vincula el ID de las palabras que se encuentran en este objeto. Este vínculo se pondera con un número de puntos (que corresponde a la relevancia de la palabra en el texto). (ver « ¿Cómo funciona el motor de búsqueda de SPIP ? » en spip.net)
En lugar de hacer una búsqueda partiendo de una palabra como se hace habitualmente con el criterio {recherche}
, se quiere encontrar los artículos similares a otro (contando el número de palabras que comparten).
Existe una forma simple para hacer esto : Se puede analizar los « documentos » (artículos, breves, etc) como vectores de N dimensiones, cada una correspondiendo a una palabra en el diccionario. Los valores almacenados en el vector de un documento representan el peso de cada dimensión (cada palabra) en este documento.
Por ejemplo :
dico | doc1 | doc2 |
libertad | 0 | 3 |
wiki | 10 | 3 |
spip | 3 | 10 |
sueño | 4 | 0 |
Una medida de la semejanza entre los dos documentos es el producto vectorial entre los dos vectores :
S = 0*3+10*3+3*10+4*0 = 60
Eso es lo que hace esta consulta sobre las tablas de indexación de SPIP :
SELECT
primaire.id_article,
secondaire.id_article,
SUM(primaire.points*secondaire.points) AS similarite
FROM
spip_index_articles AS primaire,
spip_index_articles AS secondaire
WHERE
primaire.hash = secondaire.hash AND
primaire.id_article = 2
GROUP BY secondaire.id_article
Critério
Vamos a implementar un nuevo criterio para los bucles [1] :
{similaire}
:
function critere_similaire($idb, &$boucles, $param, $not) {
$boucle = &$boucles[$idb];
if ($param != 'similaire' OR $not)
erreur_squelette(_T('info_erreur_squelette'), $param);
$boucle->select[] = 'SUM(primaire.points*secondaire.points) AS similarite';
$boucle->from[] = 'spip_index_'.$boucle->id_table.' AS primaire';
$boucle->from[] = 'spip_index_'.$boucle->id_table.' AS secondaire';
$boucle->where[] = 'primaire.'.$boucle->primary.'=\'".'.calculer_argument_precedent($idb,
$boucle->primary,
$boucles).'."\'';
$boucle->where[] = 'primaire.hash = secondaire.hash';
$boucle->where[] = $boucle->id_table.'.'.$boucle->primary.' = secondaire.'.$boucle->primary;
$boucle->group = 'secondaire.'.$boucle->primary;
}
- Se añade un nuevo campo en el select
de la consulta. Se trata del cálculo del producto escalar entre los dos vectores. Hay que señalar que el orden será del menos similar al más similar, por lo que generalmente será necesario utilizar este criterio con {inverse}
.
- Se añaden tres nuevas tablas sobre que hacer el empalme :
- 2 tablas index_TYPE, para comparar el objeto actual con todos los otros indexados. Se puede observar que se utiliza la variable id_table para conocer el tipo de objeto del bucle
- se añaden cuatro condiciones a WHERE :
- la tabla primaria recupera el objeto en cuestión. Obtiene su ID gracias a la función calculer_argument_precedent,
- se seleccionan todos los objeto que tengan las mismas palabras dentro (se comprueba con el hash de la palabra),
- la última condición está en relación a la lista de artículo similar con la tabla del objeto principal (aquí se utiliza
$boucle->id_table.'.'.$boucle->primary
como índice),
- se agrupa la columna secundaria sólo para no tener los objetos similares duplicados (es en ese momento que se calcula el producto escalar),
Baliza
El nuevo compilador de spip permite añadir sus propias balizas. Aquí se quiere poder recuperar la nueva columna calculada por nuestra petición : la « similiridad ».
He aquí el código, [2] :
function balise_SIMILARITE($p) {
return rindex_pile($p, 'similarite', 'similaire');
}
Se puede utilizar esta baliza en cualquier bucle que tenga el criterio {similaire}
en sus criterios o bien en los de un bucle englobante [3].
Se puede a continuación recuperar el valor de esta columna en la pila proporcionada por el compilador. Todo eso es hecho automáticamente por la función rindex_pile.
Bucle de ejemplo
Un bucle muy simple para utilizar este criterio sobre los artículos :
<html>
<body>
<BOUCLE_article(ARTICLES) {id_article}>
blabla blabla<br/>
#TITRE:
<B_sim>
Los articulos similares a este son:
<ul>
<BOUCLE_sim(ARTICLES) {similaire} {par similarite} {inverse} {0,5}>
<li><a href="#URL_ARTICLE">#TITRE</a></li>
</BOUCLE_sim>
</ul>
</B_sim>
</BOUCLE_article>
</body>
</html>
Se puede también indicarle a SPIP que clasifique por un campo calculado dinámicamente en una consulta [4]. Así, se clasifica por semejanza y uego se invierte, para tener los artículos clasificados en un orden decreciente de semejanza (el más parecido, aparecerá en primer lugar).
Este criterio va a devolver todos los artículos que tengan al menos unas palabras en común con el artículo en curso, lo que puede dar muchos resultados. Se limita entonces el bucle a 5.
Optimización
La consulta SQL hecha por este criterio hace tres empalmes [5] sobre tablas muy grandes . Por ejemplo para un bucle de artículos :
- la tabla articles contiene todos los artículos,
- la tabla index_articles contiene una fila por cada palabra indexada, por artículo. Eso quiere decir que si el artículo X tiene 10 palabras indexadas, entonces ocupará 10 filas en esta tabla. Imagínese el tamaño de esta tabla si hay muchos artículos largos.
Por ello es necesario evitar absolutamente hacer todos estos empalmes. Desgraciadamente, el empalme de index_loquesea sobre si mismo es obligatorio. Por el contrario, el empalme la tabla loquesea no es obligatoria.
Efectivamente, es obligatorio si se quiere aplicar este criterio directamente sobre la tabla artículos. Es más intuitivo y permite recuperar directamente la información sobre el artículo.
Aquí, se va a exponer un método que evita este empalme sobre la tabla artículo. Este nuevo criterio se aplicará directamente sobre una tabla index_loquesea y permitirá recuperar cada ID de los artículos similares.
Es necesario decir en primer lugar a SPIP que se puede ’buclear’ las tablas índice describiéndole las columnas disponibles :
include_ecrire("inc_auxbase.php");
global $tables_principales;
$tables_principales['spip_index_articles'] =
array('field' => &$spip_index_articles, 'key' => &$spip_index_articles_key);
$tables_principales['spip_index_breves'] =
array('field' => &$spip_index_breves, 'key' => &$spip_index_breves_key);
$tables_principales['spip_index_forums'] =
array('field' => &$spip_index_forums, 'key' => &$spip_index_forums_key);
$tables_principales['spip_index_auteurs'] =
array('field' => &$spip_index_auteurs, 'key' => &$spip_index_auteurs_key);
$tables_principales['spip_index_mots'] =
array('field' => &$spip_index_mots, 'key' => &$spip_index_articles_key);
$tables_principales['spip_index_signatures'] =
array('field' => &$spip_index_signatures, 'key' => &$spip_index_signatures_key);
$tables_principales['spip_index_syndic'] =
array('field' => &$spip_index_syndic, 'key' => &$spip_index_syndic_key);
es necesario también declarar las funciones boucle_... para inicializar las consultas sobre estas tablas
function boucle_INDEX_ARTICLES($id_boucle, &$boucles) {
$boucle = &$boucles[$id_boucle];
$id_table = $boucle->id_table;
$boucle->from[] = "spip_index_articles AS $id_table";
return calculer_boucle($id_boucle, $boucles);
}
function boucle_INDEX_BREVES($id_boucle, &$boucles) {
$boucle = &$boucles[$id_boucle];
$id_table = $boucle->id_table;
$boucle->from[] = "spip_index_BREVES AS $id_table";
return calculer_boucle($id_boucle, $boucles);
}
function boucle_INDEX_FORUMS($id_boucle, &$boucles) {
$boucle = &$boucles[$id_boucle];
$id_table = $boucle->id_table;
$boucle->from[] = "spip_index_forums AS $id_table";
return calculer_boucle($id_boucle, $boucles);
}
function boucle_INDEX_AUTEURS($id_boucle, &$boucles) {
$boucle = &$boucles[$id_boucle];
$id_table = $boucle->id_table;
$boucle->from[] = "spip_index_auteurs AS $id_table";
return calculer_boucle($id_boucle, $boucles);
}
function boucle_INDEX_MOTS($id_boucle, &$boucles) {
$boucle = &$boucles[$id_boucle];
$id_table = $boucle->id_table;
$boucle->from[] = "spip_index_mots AS $id_table";
return calculer_boucle($id_boucle, $boucles);
}
function boucle_INDEX_SIGNATURES($id_boucle, &$boucles) {
$boucle = &$boucles[$id_boucle];
$id_table = $boucle->id_table;
$boucle->from[] = "spip_index_signatures AS $id_table";
return calculer_boucle($id_boucle, $boucles);
}
function boucle_INDEX_SYNDIC($id_boucle, &$boucles) {
$boucle = &$boucles[$id_boucle];
$id_table = $boucle->id_table;
$boucle->from[] = "spip_index_syndic AS $id_table";
return calculer_boucle($id_boucle, $boucles);
}
Se declara así cual es la tabla principal sobre la que se harán las consultas para cada una de estos bucles.
Ahora se puede declarar el criterio :
function critere_similaire($idb, &$boucles, $param, $not) {
$boucle = &$boucles[$idb];
$table = $boucle->id_table;
if (!ereg("index_(.*[^s])s?$",$table,$m) OR $not)
erreur_squelette(_T('info_erreur_squelette'), $param);
$id = 'id_' . $m[1];
$boucle->select[] = 'SUM(' . $table . '.points*secondaire.points) AS similarite';
$boucle->select[] = 'secondaire.i'.$id.' AS id_similaire';
$boucle->from[] = 'spip_'.$table .' AS secondaire';
$boucle->where[] = $table .'.hash = secondaire.hash';
$boucle->group = 'secondaire.'.$id;
}
El principio del criterio es el mismo que antes, sólo que SPIP va a hacer el SELECT inicial directamente desde una tabla index_loquesea. Por eso se tiene que añadir el empalme sobre esta tabla y los criterios en consecuencia
Desgraciadamente, no se puede directamente utilizar #ID_ARTICLE dentro de un bucle sobre index_loquesea. Debemos entonces añadir una baliza para recuperarse el ID que nos interesa :
function balise_SIMILARITE($p) {
return rindex_pile($p, 'similarite', 'similaire');
}
function balise_ID_SIMILAIRE($p) {
return rindex_pile($p, 'id_similaire', 'similaire');
}
El boucle a realizar es un poco más complejo que antes. Por ejemplo para un los artículos :
- se debe anidar un bucle articles dentro de un bucle index_articles,
- se debe indicar a este bucle que seleccione el artículo que tiene el mismo id que #ID_SIMILAIRE : {id_article=#ID_SIMILAIRE}
- se puede excluir el artículo en curso con el criterio {exclus}
.
<BOUCLE_article(ARTICLES) {id_article}>
#TITRE:<br>
<B_amis>
Aqui la lista de articulos similares:
<ul>
<BOUCLE_amis(index_articles){similaire}{id_article}{par similarite}{inverse} {0,7}>
<BOUCLE_selection(ARTICLES){id_article=#ID_SIMILAIRE}{exclus}>
<li><a href="#URL_ARTICLE">#TITRE:#SIMILARITE</a></li>
</BOUCLE_selection>
</BOUCLE_amis>
</ul>
</B_amis>
ningun articulo
<//B_amis>
</BOUCLE_article>
Aucune discussion
Ajouter un commentaire
Avant de faire part d’un problème sur un plugin X, merci de lire ce qui suit :
Merci d’avance pour les personnes qui vous aideront !
Par ailleurs, n’oubliez pas que les contributeurs et contributrices ont une vie en dehors de SPIP.
Suivre les commentaires : |