2017-05-26 2 views
2

J'ai plusieurs corps de texte, et pour chacun d'entre eux, je veux extraire tous les unigrammes, bigrammes et trigrammes (mots et non caractères) et insérer les longueurs des nombres et des ngrammes dans une autre table.Quel est le moyen le plus rapide d'extraire tous les n-grammes des longueurs 1, 2 et 3 à partir d'un corps de texte dans PostgreSQL?

En ce moment, je pense à la suppression d'un corps de texte regexp-split en utilisant WITH ORDINALITY, puis en utilisant plusieurs sous-requêtes pour les bigrams et les trigrams, mais cela nécessite un ordre. Cependant, je pense que cela pourrait être un moyen inefficace d'y arriver, puisque ce genre de données positionnelles devrait normalement être accessible par index.

Je suis actuellement en train de l'implémenter en Python, et un énorme goulot d'étranglement est l'insertion de dictionnaire et la recherche de dictionnaires/ensembles pour les mots vides.

Voici un exemple très basique:

Entrée:

This is a small, small sentence.

Sortie

ngram    | count | length 
------------------------------------- 
this     | 1 | 1 
is     | 1 | 1 
a     | 1 | 1 
small    | 2 | 1 
sentence    | 1 | 1 
this is    | 1 | 2 
is a     | 1 | 2 
a small    | 1 | 2 
small small   | 1 | 2 
small sentence  | 1 | 2 
this is a   | 1 | 3 
is a small   | 1 | 3 
a small small  | 1 | 3 
small small sentence | 1 | 3 

Stripping la ponctuation/manipulation minuscules n'est pas un problème ici, mais obtenir le bon le nombre est important. En tant qu'étape préliminaire ou intermédiaire, j'éliminerai également les mots vides qui, dans ce cas, sont this, a et is

ngram    | count | length 
-------------------------------------- 
small    | 2 | 1 
sentence    | 1 | 1 
small small   | 1 | 2 
small sentence  | 1 | 2 
small small sentence | 1 | 3 

Dans l'exemple ci-dessus

+1

Pouvez-vous partager un petit échantillon de votre table/champ et vos résultats souhaités de unigrammes, bigrammes et trigrammes. Je pense que cela nous aiderait à comprendre exactement ce que vous recherchez. – JNevill

Répondre

2

Utilisez la fonction de fenêtre lead() pour générer bigrammes et trigrammes, et les syndicats à placer tous ngrams dans une seule liste. En fait, le plus difficile était de garder l'ordre dans le résultat comme dans la phrase de départ.

with my_table(sentence) as (
    values ('This is a small, small sentence.') 
    ), 

words as (
    select id, word 
    from my_table, 
    regexp_split_to_table(lower(sentence), '[^a-zA-Z]+') with ordinality as t(word, id) 
    where word <> '' 
    ) 

select ngram, count(*), length 
from (
    select distinct on(id, ngram) id, ngram, length 
    from (
     select id, word as ngram, 1 as length 
     from words 
     union all 
     select id, concat_ws(' ', word, lead(word, 1) over w), 2 
     from words 
     window w as (order by id) 
     union all 
     select id, concat_ws(' ', word, lead(word, 1) over w, lead(word, 2) over w), 3 
     from words 
     window w as (order by id) 
     ) s 
    order by id, ngram, length 
    ) s 
group by ngram, length 
order by length, min(id); 

     ngram   | count | length 
----------------------+-------+-------- 
this     |  1 |  1 
is     |  1 |  1 
a     |  1 |  1 
small    |  2 |  1 
sentence    |  1 |  1 
this is    |  1 |  2 
is a     |  1 |  2 
a small    |  1 |  2 
small small   |  1 |  2 
small sentence  |  1 |  2 
this is a   |  1 |  3 
is a small   |  1 |  3 
a small small  |  1 |  3 
small small sentence |  1 |  3 
(14 rows) 
0

Vous pouvez le faire avec une requête récursive:

with recursive words as ( 
    select id, translate(word, '.,', '') as word 
    from my_table, 
     regexp_split_to_table(lower(sentence), '\s+') with ordinality as t(word, id) 
    where word <> '' 
), ngrams (id, ngram) as (
    select id, array[word] 
    from words 
    where word not in ('this', 'a', 'is') -- remove stop words 
    union all 
    select c.id, p.ngram||c.word 
    from words c 
    join ngrams p on p.id + 1 = c.id 
    and cardinality(p.ngram) <= 2 -- limit to 3 words 
) 
select array_to_string(ngram, ' '), 
     count(*) over (partition by ngram) as "count", 
     cardinality(ngram) as length 
from ngrams 
order by cardinality(ngram); 

Pour l'échantillon 'This is a small, small sentence.' cela retourne:

ngram    | count | length 
---------------------+-------+------- 
a     |  1 |  1 
is     |  1 |  1 
sentence    |  1 |  1 
small    |  2 |  1 
small    |  2 |  1 
this     |  1 |  1 
this is    |  1 |  2 
small small   |  1 |  2 
is a     |  1 |  2 
small sentence  |  1 |  2 
a small    |  1 |  2 
is a small   |  1 |  3 
this is a   |  1 |  3 
a small small  |  1 |  3 
small small sentence |  1 |  3 

Et avec des mots d'arrêt enlevés:

ngram    | count | length 
---------------------+-------+------- 
sentence    |  1 |  1 
small    |  2 |  1 
small    |  2 |  1 
small sentence  |  1 |  2 
small small   |  1 |  2 
small small sentence |  1 |  3 

Je ne sais pas à quelle vitesse va être bien.

exemple en ligne: http://rextester.com/CPPU86582