Faire correspondre des intervalles

Fermé
mslider - 30 mars 2009 à 11:43
mslider Messages postés 109 Date d'inscription jeudi 21 octobre 2004 Statut Membre Dernière intervention 7 mars 2010 - 3 avril 2009 à 21:17
Bonjour,

je cherche à faire correspondre des intervalles entre eux, je m'explique:

- soit dans un premier fichier texte, disons file1.txt une suite d'intervalles de la forme:

id1 3455 3463
id2 3535 3544
id3 3601 3608
id4 3622 3630
id5 3631 3913
id6 3631 3759
id7 3631 5899
id8 3760 3913
id9 3760 5630
id10 3996 4276
id11 4486 4605
id12 4706 5095
id13 5174 5326
.../...

- dans un second fichier texte, disons file2.txt une aure suite d'intervalles de la forme:

acc_2419732 3268 3285
acc_3041065 3565 3583
acc_362358 3640 3656
acc_3279485 3793 3813
acc_3091017 3794 3811
acc_2807380 3832 3848
acc_3105138 3832 3848
acc_3105139 3832 3848
acc_3105140 3832 3848
acc_3116450 3832 3848
acc_86708 3832 3848
acc_1987802 3922 3938
acc_1679660 4113 4129
acc_891489 4113 4129
acc_2829973 4299 4318
acc_298381 4603 4619
.../...

la question est la suivante: quels sont les intervalles de mon fichier 2 (file2.txt) qui se retrouvent inclus parmi ceux du fichier 1 (file1.txt) ?
en écrivant un simple script en awk (syntaxe assez proche du C):

#!/usr/bin/awk -f
#usage: myprog.awk file2.txt

BEGIN {
while((getline < "file1.txt") > 0){
cpt++
descr[cpt]=$1
start[cpt]=$2
end[cpt]=$3
}
close("test.txt")
}
{
j=1
while(start[j]<=$2 && j<=cpt){
if(end[j]>=$3){print $1,$2,$3,descr[j],start[j],end[j];j++}
else{j++}
}
}

on obtient comme résultat:

acc_362358 3640 3656 id5 3631 3913
acc_362358 3640 3656 id6 3631 3759
acc_362358 3640 3656 id7 3631 5899
acc_3279485 3793 3813 id5 3631 3913
acc_3279485 3793 3813 id7 3631 5899
acc_3279485 3793 3813 id8 3760 3913
acc_3279485 3793 3813 id9 3760 5630
acc_3091017 3794 3811 id5 3631 3913
acc_3091017 3794 3811 id7 3631 5899
acc_3091017 3794 3811 id8 3760 3913
acc_3091017 3794 3811 id9 3760 5630
acc_2807380 3832 3848 id5 3631 3913
acc_2807380 3832 3848 id7 3631 5899
acc_2807380 3832 3848 id8 3760 3913
acc_2807380 3832 3848 id9 3760 5630
acc_3105138 3832 3848 id5 3631 3913
acc_3105138 3832 3848 id7 3631 5899
acc_3105138 3832 3848 id8 3760 3913
acc_3105138 3832 3848 id9 3760 5630
acc_3105139 3832 3848 id5 3631 3913
acc_3105139 3832 3848 id7 3631 5899
acc_3105139 3832 3848 id8 3760 3913
acc_3105139 3832 3848 id9 3760 5630
acc_3105140 3832 3848 id5 3631 3913
acc_3105140 3832 3848 id7 3631 5899
acc_3105140 3832 3848 id8 3760 3913
acc_3105140 3832 3848 id9 3760 5630
acc_3116450 3832 3848 id5 3631 3913
acc_3116450 3832 3848 id7 3631 5899
acc_3116450 3832 3848 id8 3760 3913
acc_3116450 3832 3848 id9 3760 5630
acc_86708 3832 3848 id5 3631 3913
acc_86708 3832 3848 id7 3631 5899
acc_86708 3832 3848 id8 3760 3913
acc_86708 3832 3848 id9 3760 5630
acc_1987802 3922 3938 id7 3631 5899
acc_1987802 3922 3938 id9 3760 5630
acc_1679660 4113 4129 id7 3631 5899
acc_1679660 4113 4129 id9 3760 5630
acc_1679660 4113 4129 id10 3996 4276
acc_891489 4113 4129 id7 3631 5899
acc_891489 4113 4129 id9 3760 5630à
acc_891489 4113 4129 id10 3996 4276
acc_2829973 4299 4318 id7 3631 5899
acc_2829973 4299 4318 id9 3760 5630
acc_298381 4603 4619 id7 3631 5899.
acc_298381 4603 4619 id9 3760 5630

ce qui est excellent. Sauf que lorsque mes deux fichiers dépassent les 100000 lignes le temps de calcul
grandit de manière exponentielle, de telle sorte qu'il faut plus de 24h avant d'avoir le résultat.

Y'aurait-il pas une méthode plus élégante de manière à obtenir une comparaison plus linéaire et non quadratique comme c'est le cas ici ?

Merci.

19 réponses

Salut,

J'ai une petite idée, à essayer : Commences par trier la liste d'intervalles de file1 dans l'ordre croissant de la borne inférieure (temps n*log(n)).
Ensuite, pour chaque intervalle [i..j] de file2.txt :
-Tu fais une recherche dichotomique, pour trouver l'intervalle de file1 qui a la borne inf la plus proche de i, tout en étant plus petite,
-Tant que l'intervalle trouvé a une borne sup inférieure à i, tu regarde le précédent dnas la liste.

Au pire cas, la complexité sera de n^2 * log(n), mais dans la pratique, ça devrait pas mal accélérer les choses je pense...

A voir...
0
mslider Messages postés 109 Date d'inscription jeudi 21 octobre 2004 Statut Membre Dernière intervention 7 mars 2010
30 mars 2009 à 11:59
en fait j'avais oublié de dire que les fichiers sont déjà triés de manière croissante sur les intervalles sur la borne inférieure.
0
mslider Messages postés 109 Date d'inscription jeudi 21 octobre 2004 Statut Membre Dernière intervention 7 mars 2010
30 mars 2009 à 12:05
"-Tu fais une recherche dichotomique, pour trouver l'intervalle de file1 qui a la borne inf la plus proche de i, tout en étant plus petite,"

c'est ce que fait la boucle while dans le bout de code awk que j'ai donné.
0
Ben non, là tu fais une recherche linéaire... (ton while part de j=1 et s'incrémente à chaque tour de boucle...)

Par contre, je n'avais pas vu que tu recherchais tous les intervalles de file1 incluant [i,j] (j'avais dans l'idée de trouver un seul intervalle)... Donc, ma première solution ne résout pas grand chose.

Je ne pense pas qu'il soit possible d'améliorer grandement ton algo : au pire cas, tu dois générer M*N lignes (M étant le nombre d'intervalles de file1 et N celui de file2), donc ça ne sera pas possible de décendre en dessous d'une complexité quadratique dans le cas général.

Reste à voir après où ton script passe le plus de temps : peut être que si tu stockes tous les résultats en mémoire et que tu les écris d'un seul coup dans un fichier, ça accélèrera un peu les choses... (ça dépends de comment l'interpréteur AWK gère les entrées-sorties. A tester...)
0
ben moi je stocke le file 1 en mémoire vive, le file 1 sert à annoter le fichier 2. Le fichier 2 est 8 fois plus gros que
le fichier 1.
ensuite je parcours les lignes du fichier 2 en bouclant de j=1 à j<=cpt pour chaque ligne.
j'ai essayer de stocker le fichier 2 en mémoire vive, et boucler dessus, çà change pas grand chose en terme
de vitesse, ya toujours MxN itérations à faire.
Peut être qu'en C ou C++ çà irait plus vite je sais pas. AwK c'est qd même un cousin du C.
0
Hmmm, peut être qu'en C, ça irait plus vite étant donné que awk reste un langage interprété, quoique les primitives de comparaison doivent être faites en dur... Là aussi, à tester...

Pour le fichier à stocker en mémoire, je ne pensais pas au fichier 2 mais aux lignes que tu génère. Quelque chose du genre :

cpt2=1
j=1
while(start[j]<=$2 && j<=cpt){
if(end[j]>=$3){a1[cpt2]=$1;a2[cpt2]=$2;a3[cpt2]=$3;a4[cpt2]=descr[j];a5[cpt2]=start[j];a6[cpt2]=end[j];j++;cpt2++}
else{j++}
}

et ensuite, soit faire un print des lignes générées, soit les écrire directement dans un fichier si awk le permet (je mise plus sur cette seconde solution pour accélérer les choses. L'idée étant de réduire le temps passé à faire des entrées sorties).

Ca ne réduit pas la complexité générale, mais ça peut aider à repousser un peu la limite de l'algo en divisant son temps d'exécution par une petite constante... (tout comme le fait de le coder en C).
0
awk est un langage interprété écrit en C par en grosse partie Brian Kernighan qui n'est autre que le papa du langage C avec Denis Ritchie.
Awk utilise des appels système en C.
oui effectivement awk peut ecrire directement dans un fichier à partir du code:

if(end[j]>=$3){print $1,$2,$3,descr[j],start[j],end[j] >> "sortie.txt" ;j++}

une autre parade serait aussi de découper le fichier 2, çà diminuerait la taille de la boucle, mais bon c vraiment
une solution empirique.
Et moi qui pensait que ce cas d'école serait vite résolu...c peut être pas un cas d'école tout compte fait.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 7 812
31 mars 2009 à 21:03
Le mieux c'est de faire un segment tree.
https://en.wikipedia.org/wiki/Segment_tree

1) Tu lis le premier fichier tu en construis (build) un segment tree (O(n.log(n)) pour n intervalles dans le 1er fichier.
2) Ensuite tu lis le second fichier et tu interroges (query) ton arbre (O(m.log(n))) pour m intervalles dans le 2e fichier.

Bonne chance
0
merci beaucoup pour ton renseignement,
à l'heure actuelle j'ai essayé en utilisant une fonction dichotomie, çà accélère pas mal mais pas assez, vu que le
nombre de lignes est assez important.
Les segments tree je connais pas, j'ai jamais utilisé, tu pourrais me donner un exemple en utilisant les deux listes
d'intervalles que j'ai donné dans mon premier post ?

merci encore.
0
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 7 812
31 mars 2009 à 21:37
Regarde sur wikipedia il y a un exemple et l'algorithme.
0
j'y suis allé, ya un joli cours sur la structure de données des segments tree, mais pas vu d'exemple appliqué.
0
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 7 812
31 mars 2009 à 21:44
Euh... Et ça ?
http://upload.wikimedia.org/wikipedia/en/e/e5/Segment_tree_instance.gif

Les intervalles s_i sont dessinés en vert (6 intervalles dans l'exemple).
Les bornes des intervalles p_i sont en noir.
Les feuilles (leaf) de l'arbre sont en bleus, les autre noeuds (node) en rouge.

Bonne chance
0
oui je te l'accorde, mais c trop imagé,
je parlais d'un exemple concrêt un peu plus parlant et surtout plus facile à transcrire pour quelqu'un qui ne vient
pas d'une formation informatique
0
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 7 812
31 mars 2009 à 22:03
Tu n'y mets pas du tien mon poussin. Imagine que dans ton premier fichier tu aies les intervalles
s1 = ]-infini,1]
s2 = [1,3]
s3 = [1,7]
s4 = [3,4]
s5 = [4,5]
s6 = [3,6]

Le segment tree correspondant est celui que tu as sur wikipedia.

Un noeud modélise l'intervalle délimité par sa feuille la plus à gauche (borne inférieure) et sa borne la plus à droite (borne supérieur). Un intervalle modélisé par un noeud est appelé intervalle canonique.

Un noeud t est taggué comme compris dans un intervalle s_i si
- son père n'est pas inclu dans l'intervalle s_i
- ce noeud modélise un intervalle inclu dans s_i

Un intervalle s_i est une union d'intervalles canoniques. Chacun des intervalles canoniques constituant cette union est estampillée d'un tag s_i.

Tu peux pour chaque noeud écrire un vecteur v de taille n (le nombre d'intervalles stockés dans le segment tree) tel v[i] = 1 si et seulement si s_i est représenté à l'aide de l'intervalle canonique représenté par le noeud.

Essaye déjà de bien comprendre ça mais ne te leur pas, pour améliorer la complexité d'un programme, il faut toujours se gratter la tête en choisissant subtilement sa structure de donnée, et en plus ce n'est pas toujours possible.

Une fois que tu auras compris comment marche la recherche d'un point p dans l'arbre (comment placer p dans l'arbre) on peut attaquer l'inclusion d'un intervalle I = [a,b].
1) on recherche les intervalles qui contiennent a (union des intervalles rencontrés jusqu'à trouver la feuille décrivant la plus grande borne canonique borne inférieure à a),
2) on recherche qui contiennent b (union des intervalles rencontrés jusqu'à trouver la feuille décrivant la plus grande borne canonique borne inférieure à b),
3) on fait l'intersection de ses deux ensembles.

En C des unions d'intervalles se font très facilement avec des masques, ie des champs de bits. Si tu as n intervalles dans ton premier fichier il faut donc un champ de n bits.
- pour faire une union : opérateur |,
- pour faire une intersection : opérateur &.

Supposons qu'on cherche les intervalles s_i inclus dans I, et que chaque ensemble s_i est associé au ième bit. On cherche donc un ensemble d'ensemble et on va le décrire grâce à un champ de bits. Au début cet ensemble est vide, donc chacun des bits est nuls. En d'autres termes :
memset(&x,0,sizeof(x));

Si le type de x est un type de base (par exemple un int si tu as besoin d'un champ de 32 bits soit au plus 32 intervalles s_i) tu peux directement écrire :
x = 0;

Ensuite pour rajouter s_i dans cet ensemble (union) :
x |= 2 << i; // x = x | (2 << i)

Je te rappelle que 2 << i désigne le champ de bit ou tous les bits sont nul sauf le ième. Cette valeur doit être castée dans le type de x.

Pour faire l'intersection de deux ensembles :
z = x & y;

Pour faire l'union de deux ensembles :
z = x | y;

On verra plus en détail quand tu auras compris ce message.

Bonne chance
0
mslider Messages postés 109 Date d'inscription jeudi 21 octobre 2004 Statut Membre Dernière intervention 7 mars 2010
31 mars 2009 à 22:18
tu parles, d'un poussin, je crois que coté programmation je ne suis pas encore sorti de l'oeuf !

tu penses que les segments tree seraient plus rapide que çà:

Intervale_fic_1 = premier intervalle du fichier 1
Intervale_fic_2 = premier intervalle du fichier 2
tant que (Intervale_fic_1!= dernier intervalle du fichier 1) et (Intervale_fic_2!= dernier intervalle du fichier 2)
si Intervale_fic_2 est inclus dans Intervale_fic_1
rajouter Intervale_fic_2 au résultat
++Intervale_fic_2
sinon
si Intervale_fic_2.borne Inferieur < Intervale_fic_1.Borne inferieur
++Intervale_fic_2
sinon
++Intervale_fic_1
fin si
fin si
fin tant que

ou qu'une approche dichotomique ?
0
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 7 812
31 mars 2009 à 22:38
n : nombre d'intervalles du 1er fichier,
m : nombre d'intervalles du 2nd fichier.

Complexité de ton algo : O(n.m).
Complexité de l'algo segment tree : O(n.(log(n) + log(m))).
Réponse : Oui

Par exemple si m = n, O(n^2) est sensiblement moins bon que O(n.log(n)). Trace les deux courbes sur une calculette graphique si tu veux être convaincu. Après à toi de voir.

Bonne chance
0
bon okay je te crois, c toi le pro.
bon si par le plus grand des hazards (encore faut t'il qu'il le soit) tu as 5 nanosecondes de ton temps libre
à m'accorder, un petit coup de pouce coté programmation de mon problème serait le bienvenu.
çà m'éviterais de perdre un mois à essayer de coder ce problème.
merci.
0
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 7 812
31 mars 2009 à 22:50
Désolée mais ça ne va pas être possible, je n'ai déjà pas le temps de coder ce que j'aimerais coder. Je peux te débloquer sur des points de code particulier mais pas plus...

Bonne chance
0
mslider Messages postés 109 Date d'inscription jeudi 21 octobre 2004 Statut Membre Dernière intervention 7 mars 2010
1 avril 2009 à 12:31
bon c pas grave, j'ai pas mal galéré et fini par trouvé une solution en C, certes çà utilise
pas le segment tree mais la performance du code est convenable malgré une erreur à la compilation dont
je ne trouve pas le pourquoi du comment.

voilà le code:

[CODE]
#include <stdio.h>

int SafeOpenFile(char *lpFileName, char *lpMode, FILE **lpFile)
{
*lpFile = fopen(lpFileName, lpMode);
if (! *lpFile)
printf("Cannot open file: \"%s\"\n", lpFileName);

return (int)*lpFile;
}

int main(int argc, char **argv)
{
int nResult;
FILE *lpF1;
FILE *lpF2;
FILE *lpFOut;
char lpF1Desc[50];
int nF1Start;
int nF1End;
char lpF2Desc[50];
int nF2Start;
int nF2End;
long nPosInF2;

nResult = 1;

/* if (argc != 4)
{
printf("Trouve les intervalles inclus dans d'autres intervalles.\n\n");
printf("Intervalles fichier1 fichier2 fichier_de_sortie\n\n");
printf(" fichier1 Fichier contenant des intervalles.\n\n");
printf(" fichier2 Fichier contenant les intervalles qui doivent\n");
printf(" être inclus dans les intervalles de fichier1.\n\n");
printf(" fichier_de_sortie Fichier récupérant les résultats.\n\n");
goto the_end;
} */

if (! SafeOpenFile(argv[1], "r", &lpF1)) goto the_end;
if (! SafeOpenFile(argv[2], "r", &lpF2)) goto close_1;
if (! SafeOpenFile(argv[3], "w", &lpFOut)) goto close_1_and_2;

nPosInF2 = 0;
while (! feof(lpF1))
{
if (fscanf(lpF1, "%s %d %d", lpF1Desc, &nF1Start, &nF1End) != 3) break;
/* printf("%s %d %d\n", lpF1Desc, nF1Start, nF1End); */

fseek(lpF2, nPosInF2, SEEK_SET);
while (! feof(lpF2))
{
if (fscanf(lpF2, "%s %d %d", lpF2Desc, &nF2Start, &nF2End) != 3) break;
/* printf(" %s %d %d\n", lpF2Desc, nF2Start, nF2End); */

/* Pas la peine de relire le fichier depuis le début */
if (nF2Start < nF1Start)
nPosInF2 = ftell(lpF2);

/* On s'arrête quand le début de l'interval 2 est supérieur à la fin du 1 */
if (nF2Start > nF1End) break;

/* Test d'inclusion du deuxième intervalle dans le premier */
if ((nF2Start >= nF1Start) && (nF2End <= nF1End))
fprintf(lpFOut, "%s %d %d %s %d %d\n", lpF1Desc, nF1Start, nF1End, lpF2Desc, nF2Start, nF2End);
}
}

nResult = 0;
fclose(lpFOut);
close_1_and_2:
fclose(lpF2);
close_1:
fclose(lpF1);
the_end:
return nResult;
}
[/CODE]

à la compilation j'ai cette erreur:
cc -o interval interval.c
interval.c: In function `SafeOpenFile':
interval.c:9: warning: cast from pointer to integer of different size

malgré ce, çà tourne bien, je l'ai testé sur deux gros fichiers: fichier1 ~ 100000 lignes et fichier2 ~ 800000 lignes çà a pris 3 secondes.

tu vois une optimisation de ton coté ?
0
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 7 812
1 avril 2009 à 14:56
Je suis en train de coder un segment tree pour un besoin spécifique à mon travail. Je te mettrai le source quand il sera prêt. N'hésite pas à me relancer si j'oublie.
0
merci c cool.

d'après toi c quoi qui coince sur la fonction SafeOpenFile vu le code erreur:

interval.c: In function `SafeOpenFile':
interval.c:26: warning: cast from pointer to integer of different size

int SafeOpenFile(char *lpFileName, char *lpMode, FILE **lpFile)
{
*lpFile = fopen(lpFileName, lpMode);
if (! *lpFile)
printf("Cannot open file: \"%s\"\n", lpFileName);

return (int)*lpFile;
}
0
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 7 812
1 avril 2009 à 15:23
Ca ne va pas du tout. Il faut que tu gères les ouvertures de fichiers comme suit :
#include <stdio.h>

int main(){
  const char* filename = "mon_fichier.txt";
  FILE *fp = fopen(filename,"r");
  if(!fp){
    fprintf(stderr,"%s: can't read %s\n",argv[0],filename);
    return 1; // quitte le programme avec le code d'erreur 1
  }

  // Lire le contenu de fp
  //...

  fclose(fp);
  return 0; // quitte le programme avec le code d'exécution 0 (tout va bien)
}

En outre il est particulièrement important de ne pas fermer (fclose) un FILE * si le fichier ne s'est pas ouvert.

Bonne chance
0
oui c plus sur et correct comme çà.
l'nterêt de ma fonction c'était d'éviter de re-écrire des fopen autant de fois qu'il y a de
fichiers à ouvrir.
0
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 7 812
3 avril 2009 à 20:35
Comme promis, voici une implémentation du segment tree.

Le deal

Le code étant assez chiant à écrire et étant donné que je ne suis pas très fan du récursif, il est possible qu'il y ait quelques bugs. Donc je veux bien :

1) que tu fasses quelques tests
2) si tu trouves des jeux de segments qui se comportent mal, que tu me les reportes (voir que tu me dises les corrections à apporter :p).

Un exemple

On commence par insérer un jeu de segment dans l'arbre. Ici je prends des segments dont les bornes sont des char, mais en fait tu peux utiliser des entiers ou n'importe quel type de base.

Ensuite on regarde à quels segments appartiennent les caractères allant de 'a' à 'i' et on retourne le résultat sous forme d'un tableau (test de la fonction get_intervals). Puis on cherche un intervalle dans lequel est contenu les caractères allant de 'a' à 'i' (test de get_interval).
segment[0] = [c,e]
segment[1] = [a,b]
segment[2] = [b,d]
segment[3] = [f,h]
segment[4] = [e,f]
segment[5] = [h,i]
  | 0 1 2 3 4 5 |
a | 0 1 0 0 0 0 |
b | 0 1 1 0 0 0 |
c | 1 0 1 0 0 0 |
d | 1 0 1 0 0 0 |
e | 1 0 0 0 1 0 |
f | 0 0 0 1 1 0 |
g | 0 0 0 1 0 0 |
h | 0 0 0 1 0 1 |
i | 0 0 0 0 0 1 |
a belongs to segment [a,b]
b belongs to segment [b,d]
c belongs to segment [c,e]
d belongs to segment [c,e]
e belongs to segment [e,f]
f belongs to segment [f,h]
g belongs to segment [f,h]
h belongs to segment [h,i]
i belongs to segment [h,i]


Le code
#include <stdio.h>
#include <stdlib.h>


//========================================================================
// Implémentation du segment tree
// Voir https://en.wikipedia.org/wiki/Segment_tree
//
// - Un segment tree est un arbre binaire équilibré dont chaque feuille
// décrit une borne (unique) étant donné un jeu d'intervalles (segment).
//
// - Chaque noeud décrit un intervalle canonique (tous les intervalles étant
// décrit par une union d'intervalles canoniques). Chaque noeud maintient
// un masque indiquant quels intervalles incluent leur intervalle canonique.
//
//========================================================================

typedef char bound_t;       /**< Le type des bornes décrivant un segment */
typedef long bit_field_t;   /**< Le type du masque maintenu dans un noeud */

#define BOUND_MIN 0        /**< - infini */
#define BOUND_MAX 255      /**< + infini */

/**
 * \class segment_t Décrit un intervalle
 */
typedef struct segment_t{
   bound_t lb;                    /**< La borne inférieure de l'intervalle */
   bound_t ub;                    /**< La borne supérieure de l'intervalle */
} segment_t;

/**
 * \class node_t Noeud de l'arbre canonique
 */
typedef struct node_t{
   unsigned          index;       /**< L'index du noeud dans l'arbre  */
   struct node_t *   l;           /**< Fils gauche */
   struct node_t *   r;           /**< Fils droit */
   struct segment_t  segment;     /**< L'intervalle canonique couvert par le noeud */
   bit_field_t       intervals;   /**< Le bit 1 << i vaut 1 ssi 'segment' décrit un intervalle canonique maximal inclu dans l'intervalle i */
} node_t;

/**
 * \class tree_t Le segment tree
 */
typedef struct tree_t{
   struct node_t    * nodes;       /**< Les noeuds stockés dans l'arbre. &nodes[0] correspond au noeud racine */
   struct segment_t * segments;    /**< Les segments stockés dans l'arbre. segments[i] décrit l'intervalle correspondant au bit 'i' dans le masque intervals d'un noeud. */
   unsigned           nb_segments; /**< Le nombre de segment installés */
} tree_t;


// Usage interne au segment tree
static tree_t prepare_tree(unsigned,const bound_t *,unsigned);
static int    install_segment(tree_t *,const segment_t *);
static void   init_tree_rec(tree_t *,node_t *,unsigned *,unsigned,unsigned *,const bound_t *,unsigned);
static void   install_interval_rec(tree_t *,node_t *,unsigned,const segment_t *);
static void   get_intervals_rec(tree_t *,node_t *,bound_t,bit_field_t *);
static void   get_interval_rec (tree_t *,node_t *,bound_t,bit_field_t *);
static int    sort_growing(const void *x,const void *y);

// Utilisation du segment tree
tree_t      make_tree(const segment_t *segments,unsigned nb_segments,unsigned *nb_bounds);
void        release_tree(tree_t *);
int         bit_field2index(bit_field_t *);
bit_field_t get_intervals(tree_t *,bound_t);
segment_t * get_interval(tree_t *,bound_t);


/**
 * \brief Construit l'arbre, initialise tous les champs de chaque noeuds
 * \param tree Le segment tree
 * \param node Le noeud courant. Pour un appel récursif de partant de la racine, passer &tree->nodes[0]
 * \param cur_index L'index du noeud dans le vecteur
 * \param floor L'étage du noeud courant (0 pour les feuilles, ..., E(log2(nb_leaves)) pour la racine, avec nb_leaves = nb_bounds)
 * \param premaining_bounds Pointe sur le nobre de borne qu'il faut encore installer
 * \param bounds Les bornes ordonnées des intervalles stockés dans l'arbre
 * \param nb_bounds Le nombre de bornes
 */
void init_tree_rec(
   tree_t        * tree,
   node_t        * node,
   unsigned      * cur_index,
   unsigned        floor,
   unsigned      * premaining_bounds,
   const bound_t * bounds,
   unsigned        nb_bounds
){
   node->index = (*cur_index)++;
   node->intervals = 0;
   if(floor){
       // Construire le fils gauche
       if(*premaining_bounds){
           node->l = &tree->nodes[*cur_index];
          init_tree_rec(tree,node->l,cur_index,floor-1,premaining_bounds,bounds,nb_bounds);
          if(floor == 1){
               node->l->segment.lb = node->l->segment.ub = bounds[nb_bounds - *premaining_bounds];
               node->l->l = node->l->r = NULL;
               --*premaining_bounds;
          }
       }else node->l = NULL;

       // Construire le fils droit
       if(*premaining_bounds){
           node->r = &tree->nodes[*cur_index];
          init_tree_rec(tree,node->r,cur_index,floor-1,premaining_bounds,bounds,nb_bounds);
          if(floor == 1){
               node->r->segment.lb = bounds[nb_bounds - *premaining_bounds - 1];
               node->r->segment.ub = bounds[nb_bounds - *premaining_bounds];
               node->r->l = node->r->r = NULL;
          }
       }else node->r = NULL;

       // Installer les bornes
       node->segment.lb = node->l->segment.lb;
       node->segment.ub = node->r ? node->r->segment.ub : node->l->segment.ub;
   }
}

/**
 * \brief Construit l'arbre binaire équilibré
 * \param bounds Les bornes à installer dans l'arbre
 * \param nb_bounds Le nombre de borne
 * \return L'arbre créé
 */
tree_t prepare_tree(unsigned nb_segments,const bound_t *bounds,unsigned nb_bounds){
   tree_t   tree;
   unsigned highest_floor, remaining_bounds, index ;

   // Vérifier que le masque des noeuds est suffisamment grand par rapport au nombre d'intervalles à stocker
   if(nb_segments > 8*sizeof(bit_field_t)){
       printf("prepare_tree: too many segments (%d) (max = %d). last segments will be ignored\n",nb_segments,8*sizeof(bit_field_t));
       nb_segments = 8*sizeof(bit_field_t);
   }

   // Le nombre de noeuds dans un arbre est borné par 2 fois le nombre de feuilles (nb feuilles = 2 fois le nombre de bornes + 1)
   tree.nodes    = (node_t *)    malloc(2*(2 * nb_bounds + 1)*sizeof(node_t));
   tree.segments = (segment_t *) malloc(nb_segments*sizeof(segment_t));
   tree.nb_segments = 0;

   // Les étages sont indexés (de 0 pour les feuilles à highest_floor = E(log2(nb_leaves) pour la racine)
   for(highest_floor = 0;(unsigned) (1 << highest_floor) < (2 * nb_bounds + 1); ++highest_floor);

   // Construire le maillage, installer les index et remettre à zéro intervals
   index = 0;
   remaining_bounds = nb_bounds;
   init_tree_rec(&tree,&tree.nodes[0],&index,highest_floor,&remaining_bounds,bounds,nb_bounds);
   return tree;
}


/**
 * \brief Désalloue l'arbre
 * \param tree L'arbre à désallouer
 */
void release_tree(tree_t *tree){
   if(tree->nodes)    { free(tree->nodes);    tree->nodes    = NULL; }
   if(tree->segments) { free(tree->segments); tree->segments = NULL; }
   tree->nb_segments = 0;
}

/**
 * \brief Installe un intervalle
 * \param tree Le segment tree
 * \param node Le noeud courant. Pour un appel récursif de partant de la racine, passer &tree->nodes[0]
 * \param idx_interval La position du bit qui identifie l'intervalle seg dans chaque noeud de l'arbre
 * \param seg Pointeur sur l'intervalle à installer
 */
void install_interval_rec(
   tree_t          * tree,
   node_t          * node,
   unsigned          idx_interval,
   const segment_t * seg
){
   if(seg->lb <= node->segment.lb && node->segment.ub <= seg->ub){
       // Si l'intervalle canonique du noeud courant est inclu dans seg
       node->intervals |= (1 << idx_interval);
   }else{
       // Si l'intervalle canonique d'un fils recouvre partiellement seg, plonger dedans
       if(node->l && (node->l->segment.lb <= seg->ub && node->l->segment.ub > seg->lb)){
           install_interval_rec(tree,node->l,idx_interval,seg);
       }
       if(node->r && (node->r->segment.lb <= seg->ub && node->r->segment.ub > seg->lb)){
           install_interval_rec(tree,node->r,idx_interval,seg);
       }
   }
}

/**
 * \brief Installe un intervalle dans l'arbre
 * \param tree Le segment tree
 * \param seg Pointeur sur l'intervalle à installer
 * \return Le nombre de segments insérés avec succès
 */
int install_segment(
   tree_t          * tree,
   const segment_t * seg
){
   // Vérifier que le pool d'intervalles enregistrés n'est pas plein
   if(tree->nb_segments > 8*sizeof(bit_field_t)) return 0;

   // Installer l'intervalle dans les segments enregistrés
   tree->segments[tree->nb_segments].lb = seg->lb;
   tree->segments[tree->nb_segments].ub = seg->ub;

   // Marquer les noeuds
   install_interval_rec(tree,&tree->nodes[0],(tree->nb_segments)++,seg);
   return 1;
}

/**
 * \brief Retrouve le masque constitué des identifiants d'intervalles contenant un point donné
 * \param tree Le segment tree
 * \param node Le noeud courant. Pour un appel récursif de partant de la racine, passer &tree->nodes[0]
 * \param value Le point
 * \param b Un pointeur vers le masque initialisé à 0
 */
void get_intervals_rec(
   tree_t      * tree,
   node_t      * node,
   bound_t       value,
   bit_field_t * b
){
   *b |= node->intervals;
   if(node->l || node->r){
          if(node->l && (value < node->l->segment.ub || (value == node->l->segment.ub && !node->l->r))){
           // Si le point est situé avant la borne supérieure du fils gauche, ou que ce fils gauche
           // est une feuille contenant la valeur value, on cherche dans le fils gauche.
           get_intervals_rec(tree,node->l,value,b);
       }else if(node->r){
           // Sinon le point est dans le sous-arbre droit.
           get_intervals_rec(tree,node->r,value,b);
       }
   }
}

/**
 * \brief Retrouve le masque constitué des identifiants d'intervalles contenant un point donné.
 * Si on sait qu'au plus un segment correspond, utiliser get_interval (plus efficace)
 * \param tree Le segment tree
 * \param value Le point
 * \return Le masque constitué des identifiants d'intervalles contenant un point donné
 */
bit_field_t get_intervals(
   tree_t      * tree,
   bound_t       value
){
   bit_field_t b;
   if(!tree->nodes) return 0; // arbre vide
   b = 0;
   get_intervals_rec(tree,&tree->nodes[0],value,&b);
   return b;
}

/**
 * \brief Retrouve le premier intervalle qui contient un point
 * \param tree Le segment tree
 * \param node Le noeud courant. Pour un appel récursif de partant de la racine, passer &tree->nodes[0]
 * \param pt Le point
 * \param b Un pointeur vers le masque initialisé à 0
 */
void get_interval_rec(
   tree_t      * tree,
   node_t      * node,
   bound_t       value,
   bit_field_t * b
){
   *b |= node->intervals;
   if(!*b &&(node->l || node->r)){
       if(node->l && (value < node->l->segment.ub || (value == node->l->segment.ub && !node->l->r))) get_interval_rec(tree,node->l,value,b);
       else if (node->r) get_interval_rec(tree,node->r,value,b);
   }
}

/**
 * \brief Cherche un intervalle stocké dans l'arbre contenant un point particulier
 * \param tree Le segment tree
 * \param value Le point
 * \return Un pointeur vers le segment correspondant
 */
segment_t * get_interval(
   tree_t  * tree,
   bound_t   value
){
   bit_field_t b;
   if(!tree->nodes) return NULL; // arbre vide
   b = 0;
   get_interval_rec(tree,&tree->nodes[0],value,&b);
   if(!b) return NULL; // 'value' n'est inclu dans aucun segment canonique
   return &tree->segments[bit_field2index(&b)];
}

/**
 * \brief Convertit un bitfield
 * \param b Pointeur sur le champ de bit
 * \return La position du plus petit bit à 1 (-1 si b ne contient que des 0)
 */
int bit_field2index(bit_field_t *b){
   unsigned i;
   for(i = 0; i < 8 * sizeof(bit_field_t) ; ++i){
       if (*b & (1 << i)) return i;
   }
   return -1;
}

/**
 * \brief Ordonne deux bornes
 * \param x Pointeur sur la première borne
 * \param y Pointeur sur la seconde borne
 * \return une valeur négative si *x inférieur *y, 0 si *x == *y, une valeur positive sinon
 */
int sort_growing(const void *px,const void *py){
   const bound_t *x = px;
   const bound_t *y = py;
   return *x - *y;
}

/**
 * \brief Construit un segment tree étant donné un jeu d'intervalles
 * \param segments Les intervalles
 * \param nb_segments Le nombre d'intervalles
 * \param nb_bounds Un pointeur vers un entier qui contiendra le nombre de bornes distinctes
 * (feuilles) contenu dans l'arbre retourné
 * \return Le segment tree
 */
tree_t make_tree(const segment_t *segments,unsigned nb_segments,unsigned *nb_bounds){
   unsigned  i,n;
   bound_t * bounds;
   tree_t    _tree;
      if(!nb_segments || !segments){
       // Arbre vide
       *nb_bounds = _tree.nb_segments = 0;
       _tree.nodes = NULL;
       _tree.segments = NULL;
   }else{
       n = 2 * (nb_segments + 1);
       bounds = (bound_t *) malloc(n * sizeof(bound_t));

       // Stocker les bornes des intervalles, -infini et +infini dans bounds
       bounds[0] = BOUND_MIN;
       for(i=0;i<nb_segments;++i){
           bounds[2 * i + 1] = segments[i].lb;
           bounds[2 * i + 2] = segments[i].ub;
       }
       bounds[n - 1] = BOUND_MAX;

       // Trier les bornes et virer les doublons (les *nb_bounds premières cases contiendront les bornes 2 à 2 distincites)
       qsort(bounds,n,sizeof(bound_t),sort_growing);
       *nb_bounds = 1;
       for(i=1;i<n;++i){
           if(bounds[i] != bounds[*nb_bounds-1]){
               bounds[*nb_bounds] = bounds[i];
               ++*nb_bounds;
           }
       }

       // Construire le maillage
       _tree = prepare_tree(nb_segments,bounds,*nb_bounds);

       // Installer des segments
       for(i=0;i<nb_segments;++i) install_segment(&_tree,&segments[i]);
       free(bounds);
   }
   return _tree;
}

int main(){
   const unsigned    nb_segments = 6;
   unsigned          i,nb_bounds;
   tree_t            _tree;
   segment_t         segments[nb_segments];
   segment_t      *  seg;
   tree_t         *  tree = &_tree;
   bit_field_t       mask;
   char              c;

   // Les segments que l'on va stocker dans l'arbre
   segments[0].lb = 'c'; segments[0].ub = 'e'; // [c,e]
   segments[1].lb = 'a'; segments[1].ub = 'b'; // [a,b]
   segments[2].lb = 'b'; segments[2].ub = 'd'; // [b,d]
   segments[3].lb = 'f'; segments[3].ub = 'h'; // [f,h]
   segments[4].lb = 'e'; segments[4].ub = 'f'; // [e,f]
   segments[5].lb = 'h'; segments[5].ub = 'i'; // [h,i]
   _tree = make_tree(segments,nb_segments,&nb_bounds);

   // afficher les intervalles
   for(i = 0;i<nb_segments;++i){
       printf("segment[%d] = [%c,%c]\n",i,tree->segments[i].lb,tree->segments[i].ub);
   }

   // get_intervals
   printf("  | ");
   for(i = 0;i<tree->nb_segments;++i) printf("%i ",i);
   printf("|\n");
   for(c = 'a';c<'j';++c){
       printf("%c | ",c);
       mask = get_intervals(tree,c);
       for(i=0;i<tree->nb_segments;++i){
           printf("%d ",mask & (1 << i) ? 1 : 0);
       }
       printf("|\n");
   }

   // get_interval
   for(c = 'a';c<'j';++c){
       seg = get_interval(tree,c);
       if (seg) printf("%c belongs to segment [%c,%c]\n",c,seg->lb,seg->ub);
       else     printf("%c does not belong to any stored segment\n",c);
   }
   // Libérer l'arbre
   release_tree(tree);
   return 0;
}
//========================================================================


Principe :

1) La structure tree stocke un jeu d'intervalles (champ segment). Une fois les segments déterminés (voir fonction main) on peut calculer le nombre de feuilles nécessaires pour construire l'arbre (voir fonction make_tree) et construire le maillage. Les intervalles sont marqués aux nœuds correspondant à l'aide d'un bit spécifique dans le masque intervals stocké dans un nœud.

2) Une requête pour trouver quel(s) intervalle(s) correspondent avec une valeur donnée consiste à parcourir les branches pertinentes de l'arbre et examiner le masque des nœuds croisés. Si on cherche juste un intervalle on utilise get_interval qui explore l'arbre tant qu'aucun nœud n'a été trouvé. get_intervals parcours tous les nœuds pertinents susceptibles de marquer un segment enregistré. Le masque fabriqué par get_intervals représente l'union de tous les intervalles contenant la valeur recherchée (voir fonction main pour l'utilisation).

Bonne chance
0
mslider Messages postés 109 Date d'inscription jeudi 21 octobre 2004 Statut Membre Dernière intervention 7 mars 2010
3 avril 2009 à 21:02
déjà de ton coté tu peux tester le code avec le jeu de données que je fournis dans mon premier post, voir tout en haut. Il y a les deux fichiers d'entrée à croiser et le résultat que tu dois obtenir.
Si le résultat renvoyé par ton code est correct c'est que le segment tree ne commet pas d'erreur.
0
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 7 812
3 avril 2009 à 21:09
Pour être complètement honnête j'ai un peu la flemme de parser tes fichiers à 21h :-) Le plus gros est fait, je pense que tu peux faire un effort, c'est un peu pour toi aussi non ?
0
mslider Messages postés 109 Date d'inscription jeudi 21 octobre 2004 Statut Membre Dernière intervention 7 mars 2010
3 avril 2009 à 21:17
ouais moi aussi dodo.
je le ferais demain.
0