Reprezentacja tagu

Koncepcja ogólna

Tagi są w zasadzie zbiorami wartości z tagsetu, obecnie w bibliotece Corpus2 reprezentowane jako "maski bitowe", rozdzielone na:
  • część opisującą klasę gramatyczną (POS) oraz
  • część opisującą wartości atrybutów.

Pierwsza maska, pos_, w normalnych warunkach ma zapalony jeden bit, identyfikujący "klasę gramatyczną". Pusty pos_ oznacza pusty tag. Każda klasa gramatyczna ma też określony indeks (maska to 1 << indeks). Druga maska może mieć zapaloną różną liczbę bitów, zależnie od wartości, jakie ma dany tag. Każdy bit maski odpowiada dokładnie jednej możliwej wartości -- "wartość" to maska z dokładnie jednym zapalonym bitem. Bity wartości jednego atrybutu są sąsiadujące i tworzą razem maskę atrybutu. W normalnych warunkach oczekujemy, że co najwyżej jeden bit z każdego poszczególnego atrybutu jest zapalony. Maski poszczególnych atrybutów są ułożone według kolejności alfabetycznej nazw atrybutów, kolejność ta wyznacza indeksy atrybutów.

Tagset

Klasa Tag nie zawiera informacji o tym, które bity przyporządkowane do których atrybutów. Informacje te przechowuje klasa Tagset. Klasa Tagset udostępnia mapowania nazw i indeksów w maski i vice versa, np. get_pos_mask(string), get_value_name(mask_t), a także funkcje do iterowania po wszystkich maskach POSów, atrybutów i wartości: foreach(mask_t m, Tagset::all_[pos|attribute|value]_masks()).

Tagset odpowiada także za parsowanie tagów zapisanych w postaci tekstowej, czyli także za interpretację symboli z tagsetu.

Nie powinno być konieczne bezpośrednie operowanie na maskach bitowych. Klasa Tag udostępnia funkcje do pobierania wartości danego atrybutu, ustawiania wartości atrybutu, łączenia tagów itd. które wykonują odpowiednie operacje, ale mają jawne nazwy. API tagsetu i tagu jest opisane w stylu doxygena, w miarę możliwości opisywane są tam dokładnie działania i założenia funkcji.

Szczegóły techniczne

Wewnętrznie Tag zawiera dwie maski bitowe typu Corpus2::mask_t, obecnie jest to std::bitset, czyli takie standardowe opakowanie na jeden lub więcej unsigned longów z ułatwionym dostępem do poszczególnych bitów i wystawionymi (w odróżnieniu od np. std::vector< bool >) operacjami bitowymi. Ważnym powodem dla zastosowania typu std::bitset zamiast np. int64_t (ekwiwalentu DLONGa) jest chęć uniknięcia potencjalnie katastrofalnych konwersji z i do typu całkowitego int. Konwersje między wbudowanymi typami całkowitymi zachodzą często automatycznie, bez ostrzeżeń, a w sytuacji, gdy przypisujemy znaczenie do reprezentacji bitowej, powodują utratę danych bądź pojawianie się danych błędnych. Natomiast std::bitset nie konwertuje się automatycznie do typów całkowitych; wprawdzie nadal zachodzi konwersja w drugą stronę z typu unsigned int, ale wydaje się to mniej tragiczne. Można rozważyć wydzielenie „naszego” bitsetu, gdzie konwersja ta nie będzie automatyczna.

Alternatywami dla std::bitsetu są realnie (ostatecznie nie zdecydowaliśmy się na żadną z nich):
  • int64_t (DLONG) --- w.w. wady, plus np. wyrażenie (1 << 33) jest błędne, konieczne jest, by jedynka była zrzutowana na typ int64_t
  • boost::dynamic_bitset --- bardzo podobny, ale o zmiennym rozmiarze, co jest nam niepotrzebne, a spowalniałoby
  • opakowanie na jedno z powyższych --- najprawdopodobniej na std::bitset

Zbiory (multi-tagi)

Tag może jednocześnie reprezentować zbiór tagów, co wynika z reprezentacji bitowej --- gdy np. dla danego atrybutu zapalony jest więcej niż jeden bit, lub gdy zapalony jest więcej niż jeden bit POS. Tag nie jest wtedy jednostkowy, wyraża jakiś zbiór tagów lub symboli z tagsetu. Nie jest to zawsze pożądane, można to sprawdzać funkcją tag_is_singular z klasy Tagset (Tag sam nie może tego sprawdzić, gdyż nie zawiera informacji o tym, gdzie są granice atrybutów). Interpretacja takiego zbioru nie jest jednoznaczna, funkcja tag_size zwraca rozmiar "pokrywanego" zbioru tagów, a tag_split rozbija tag na tyleż tagów jednostkowych. Przez pokrywany zbiór tagów rozumie się tu tagi takie, że:
  • są jednostkowe
  • są (bitowo) podzbiorami tagu bazowego
  • dla niepustych atrybutów tagu bazowego, mają atrybuty niepuste
    Przykładowo dla multi-tagu subst:nom.acc:sg.pl można wyodrębnić cztery pokrywane tagi jednostkowe: subst:nom:sg, subst:nom:pl, subst:acc:sg i subst:acc:pl.

Nie da się oczywiście stworzyć multitagu, a potem rozbić go jednoznacznie na tagi jednostkowe — nie wiadomo, które wartości należą do jednego tagu, powyższy przykład mogł np. powstać ze złączenia subst:nom:sg z subst:acc:pl.