Porównywanie słów bez uwagi na znaki diaryktyczne

0

Zrobiłem wyszukiwarkę słów w bazie danych. Wykorzystuję FULL TEXT SEARCH w trybie BOOLEAN.

Szukam rozwiązania aby przykładowo ze słowa "wąż" kodowanego w UNICODE pozyskać wszystkie inne kombinacje tego wyrazu napisane częściowo tylko znakami ASCII. Te kombinacje są następujące: waz, wąz, waż. Tak samo dla słowa żółw, są kombinacje : zółw, zołw, zolw, zólw, żólw. Chciałbym aby to rozwiązanie było uniwersalne i działało też dla rumuńskiego słowa giga - gîgă, or hiszpańskiego słowa ołówek - lápiz.

Napisałem prostą procedurę, która rozbija dwa wyrazy "żółw wąż" na dwie tablice liter. Dodatkowo wykrywa czy znak jest ASCII pomiędzy wartościami 0 - 127. Na koniec składam te wyrazy z poszczególnych liter. Jak zmienić litery ze znakami diakrytycznymi na litery bez tych znaków?
Dobrze, aby to działało nie tylko dla języka polskiego.

<?php

$string = "żółw wąż";
$words = explode(' ', $string);
$number=count($words);
$regex = '/[^a-zA-Z]/';
//$regex = '/[^\x20-\x7e]/';  // to też się sprawdza

for ($i=0;$i<$number;$i++)
{ 

  if (!preg_match($regex,$words[$i]))  // wykryj słowa ze znakami ASCII
      {
                      // OK dla słów z  a-zA-Z  
                      // Ale nic nie rób!
                      //$clean[$i] = iconv('UTF-8', 'US-ASCII//TRANSLIT', $words[$i]);
                      //echo " +" . $clean[$i] . " - $i, ";
      }
                          
      else
                              
          {
                                  
              $chars_oryginal[$i] = mb_str_split($words[$i]);
              $length = count($chars_oryginal[$i]);
              for ($j=0;$j<$length;$j++)
              {	
                  
                  if (!preg_match($regex,$chars_oryginal[$i][$j])) // wykryj znak czy a-zA-Z
                          
                  {
                        $char_unicode_value =  mb_ord($chars_oryginal[$i][$j]);   // Wartość integer znaku UNICODE
                            
                        echo " + " . $char_unicode_value . " = " . $chars_oryginal[$i][$j] . " - $j, ";
                                          
                              
                  }
                              
                  else   // tu wykrywa znak diakrytyczny dowolnego języka z alfabetu latin
                                  
                  {
                  $char_unicode_value =  mb_ord($chars_oryginal[$i][$j]);   // Wartość integer znaku UNICODE
                  echo " - $j) " . $char_unicode_value . " = " . $chars_oryginal[$i][$j] . ", " ; // wyświetl wartość i znak
                                   
                                  
                                  
                         ..... TUTAJ BRAKUJE MI POMYSŁU
                        // Podmień Ą na A lub Ę na E lub ż na z lub ą na a
                        // Zapisz cały wyraz "żółw" lub "wąż"z podmienioną literą do tablicy
                         .....
                                       
                  }
              

              }         
                  
          }


   }


    /// skladanie wyrazów
   for ($i=0;$i<$number;$i++)
   {
              $new_word[$i] = implode("", $chars_oryginal[$i]);
              echo " " . $i . ") - " . $new_word[$i];
   
   }
  

?>

Wynikiem tego kodu jest:

- 0) 380 = ż, - 1) 243 = ó, - 2) 322 = ł, + 3) 119 = w, + 4) 105 = i, + 5) 97 = a, + 0) 119 = w, - 1) 261 = ą, - 2) 380 = ż, 0) - żółwia 1) - wąż

Znaki diakrytyczne są poprzedzone znakiem minus, a bez ogonków znakiem plus.
Może da się wykorzystać to, że można wyciągnąć wartości integer znaków UNICODE poszczególnych wyrazów.
Macie jakieś pomysły?

4

iconv(), z opcją //TRANSLIT.

(edit: sorki, dopiero teraz zauważyłem, że już coś takiego w kodzie masz 🤦)

(fwiw, coś takiego jest alright jako ćwiczenie, ale na wszystkie cele praktyczne wynajdujesz koło na nowo - silniki w stylu ES ogarniają nie tylko transliterację, ale również rzeczy w stylu stemming czy n-gramy.)

2

Moim zdaniem:

  1. Utwórz funkcję o nazwie na przykład RemoveDiacritic(str), która na wejściu przyjmuje dowolny string, a na wyjściu zwraca ten sam string z podmienionymi znakami. Jeśli chodzi o samą zamianę, to utwórz tablicę, albo lepiej słownik,mapę,tablice mieszającą, jak zwał, tak zwał. Nie wiem, czy w PHP coś takiego jest, ale w C# to jest Dictionary, w C++ to map, a w JavaScript nie ma nazwy, ale to też można zrobić. Chodzi o to, że kluczem będzie litera diakrytyczna, np. ą, a wartością będzie litera łacińska, np. a. Funkcja działa tak, że jeżeli znak jest kluczem w tablicy, to podstawić wartość, a jeżeli nie ma w tablicy, to zostawić, jak jest. Ta funkcja może mieć też dodatkowe działanie, np. zmiana na same małe litery, jeżeli ostatecznie nie chcesz ich rozróżniać.

  2. Wyszukiwanie zrobić inaczej. W danych do przeszukiwania dorobić wersję bez polskich znaków, czyli to samo, co chcesz przeszukiwać, ale przepuszczone przez wyżej opisaną funkcję RemoveDiacritic(str). Następnie, przy wyszukiwaniu, wpisywane hasło również przepuszczać przez tą funkcję. Wg Twojego pomysłu, np. słowo żółtodziób miałoby aż 8 wersji, trochę to bez sensu.

  3. Nie wiem, cy tablice zamiany istnieją w internecie, ze wystarczy poszukać i jakoś przerobić na wersję do Twoich zastosowań. Jeżeli nie znajdziesz, to możesz wykorzystać dowolną przeglądarkę znaków Unicode. Ja kiedyś zbudowałem swoją , którą też opisałem. Trochę czasu zejdzie, ale do każdego znaku łacińskiego możesz wypisać wszystkie znaki diakrytyczne, czyli z kropkami, kreskami, umlautami, macronam itp. Jednorazowa praca i będzie działać z większością międzynarodowych tekstów. Większość, o ile nawet nie wszystkie znaki mieszczą się w puli od 0 do 65535. Przeglądając stronami po 256 znaków, łatwiej przejrzeć, a i tak na wielu stronach nie ma znaków bazujących na literach łacińskich.

1

Sprawdziłem funckję iconv za pomocą Ideone i widzę, ze działa tak sobie w tym zastosowaniu:

link

Przykład

2

To może od początku, w innym poście była mowa o wielojezykowości w tym podany przykład to cyrylica. Czy mówimy o języku polskim tylko? Bo robić tablice dla koreańskiego to nie ma najmniejszego sensu.
Jakie języki chcesz obsłużyć? Ile będzie tych danych wejściowych.
Bo tu wymieniasz rumuński i hiszpański jeszcze.
Powoli jak to widzę to jednak skłaniałbym się do użycia Elastic, bo zaczynasz odbijać się od ściany.

2

Odpowiadaj w postach. Elastic to oddzielna baza danych, tak jak powiedzmy MySQL tylko dedykowana do usprawnienia wyszukiwania. Czyli musisz taką bazę zainstalować i skonfigurować. To oczywiście skrót w dwóch zdaniach.

1
jurek1980 napisał(a):

Odpowiadaj w postach. Elastic to oddzielna baza danych, tak jak powiedzmy MySQL tylko dedykowana do usprawnienia wyszukiwania. Czyli musisz taką bazę zainstalować i skonfigurować. To oczywiście skrót w dwóch zdaniach.

Nie od rzeczy będzie powiedzieć: jeśli mowa o PHP na bieda-hostingu na wirtualce, będzie to niemożliwe

0
jurek1980 napisał(a):

Odpowiadaj w postach. Elastic to oddzielna baza danych, tak jak powiedzmy MySQL tylko dedykowana do usprawnienia wyszukiwania. Czyli musisz taką bazę zainstalować i skonfigurować. To oczywiście skrót w dwóch zdaniach.

Na moim hostingu chyba nie ma tej bazy niestety, ale muszę zapytać administarcji. Zobaczę z ciekawości jak to działa na localhost bo mam serwer APACHE i MYSQL, mogę się w to pobawić i czegoś może się nauczę nowego.
Ale w kwestii mojego projektu i wyszykiwania słów FULL TEXT SEARCH w trybie BOOLEAN przydała by się tylko prosta procedura do konwersji znaków i składania takich wariacji wyrazów.
Znalezłem coś takiego, ewventualnie zamiast funkcji iconv(), która niby działa ale przekonania do niej nie mam.

function toASCII( $str )
{
    return strtr(utf8_decode($str), 
        utf8_decode(
        'ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ'),
        'SOZsozYYuAAAAAAACEEEEIIIIDNOOOOOOUUUUYsaaaaaaaceeeeiiiionoooooouuuuyy');
}

Ale kiedy dodam polskie znaki to niepoprawnie zamienia.

function toASCII2( $str )
{
    return strtr(utf8_decode($str), 
        utf8_decode(
        'ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿŻŹĆŃĄŚŁĘÓżźćńąśłęó'),
        'SOZsozYYuAAAAAAACEEEEIIIIDNOOOOOOUUUUYsaaaaaaaceeeeiiiionoooooouuuuyyZZCNASLEOzzcnasleo');
}

Może jest jednak wyjście aby to usprawnić?

2

Funkcja uff8_dedode
Już nie jest zalecana, poza tym tego co kojarzę to masz już literał w kodowaniu UTF-8 pobrany z bazy. Czyli użycie tej funkcji może tylko prowadzić do błędów. To co musisz zrobić to zwykła zamiana liter na ASCIIIconv powinien to ogarnąć. Jeśli jednak chcesz własnej funkcji to staraj się trzymać wszędzie jednego kodowania znaków, najlepiej UTF-8 czyli baza, PHP, pliki programu( pewnie na dolnej belce edytora masz informacje jak zapisujesz sam kod), no i wyświetlanie w samym HTML

0

Poprawiłem procedurę i zamieszczam do wglądu.
Gdzieś robię błąd wyniki są mało zadowalające.


<?php
$string = "żółw wąż żółtodziób";
$words = explode(' ', $string);
$number = count($words);
$regex = '/[^a-zA-Z]/';
			      
$chars_oryginal = array();
$chars_ascii = array();
$new_words = array();
$k=0;    // index słowa w tablicy $new_words
			

for ($i=0;$i<$number;$i++)
					  
				  
	{ 	
		if (!preg_match($regex,$words[$i]))
			{
												
			// OK FOR A-Z words
									
			    $new_words[$k]=$words[$i];
				$k++;
								        
		
				}
				
				else
					
					{
						//
				
		            $chars_oryginal[$i] = mb_str_split($words[$i]);
					$length = count($chars_oryginal[$i]);
					
					////$length = strlen($words[$i]);
					for ($j=0;$j<$length;$j++)
					{	
					// wykryj znak czy azAZ
					if (!preg_match($regex,$chars_oryginal[$i][$j]))
				
					{
					 $char_unicode_value =  mb_ord($chars_oryginal[$i][$j]);   // ASCII VALUE
						
					echo " + $j) " . $char_unicode_value . " = " . $chars_oryginal[$i][$j] . ", ";
					
					
					$new_words[$k]=$words[$i];
					$k++;
				
					}
				
					else   // wykrywa znak czy diakrytyczny
					
					{
					$char_unicode_value =  mb_ord($chars_oryginal[$i][$j]);   // ASCII VALUE
						
					echo " - $j) " . $char_unicode_value . " = " . $chars_oryginal[$i][$j] . " " ;
					
					$chars_ascii[$j] = preg_replace($regex.'i', '',iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $chars_oryginal[$i][$j]));
					echo $chars_ascii[$j] . ", ";
						 
						if (isset($chars_ascii[$j]))
							{
						
							$pos[$i][$j] = mb_strpos($chars_oryginal[$i][$j],$words[$i]);
							$new_words[$k] = str_replace($chars_oryginal[$i][$j],$chars_ascii[$j],$words[$i]);
							$k++;
						
					
							}
						 
						 
					}
					
					if ($j>= $length-1) $k++;
					
					}         
		
					}
		 }
		 
				 $new_words = array_unique($new_words);  // wyelimunuj powtarzajace sie elementy
				 print_r($new_words);
				
					  
	?>			
					

W wyniku tego kodu dostaję wszystkie 3 kombinajce słowa wąż i tylko 4 kombinacje słowa żółw.

- 0) 380 = ż z, - 1) 243 = ó o, - 2) 322 = ł l, + 3) 119 = w, + 0) 119 = w, - 1) 261 = ą a, - 2) 380 = ż z, - 0) 380 = ż z, - 1) 243 = ó o, - 2) 322 = ł l, + 3) 116 = t, + 4) 111 = o, + 5) 100 = d, + 6) 122 = z, + 7) 105 = i, - 8) 243 = ó o, + 9) 98 = b,
Array ( [0] => zółw [1] => żołw [2] => żólw [3] => żółw [5] => wąż [6] => waż [7] => wąz [9] => zółtodziób [10] => żołtodziob [11] => żóltodziób [12] => żółtodziób )

Kombinacji słowa żółw powinno być chyba 6 jak dobrze policzyłem. Brakuje słów zolw i zólw.
Kombinacji słowa żółtodziób powinno być chyba 8, asą 4.
Nie wiem jak to naprawić aby były wszystkie kombinacje.
Przynajmniej widać, że funkcja iconv() działa poprawnie i zmienia wszystkie znaki UTF-8 na US-ASCII.

Wyszukiwarka Google bez problemu znajduje słowo żółw nawet gdy wpiszemy zolw. Albo po wpisaniu Lapiz znajdzie hiszpański odpowiednik ze znakiem diakrytycznym.

Przeglądarka Firefox po wciśnieciu CTRL+F umożliwia przeszukiwanie treści stron. Zauważyłem, że program nie zwraca uwagi na znaki diakrytyczne języka można wpisać niepoprawnie, a i tak dopasuje słowo. Nie sądzę aby przegądarka stosowała jakieś systemy typu ElsticSearch z własną bazą danych, bo to oplikacja systemowa. Zatem powstaje pytanie jak w C, albo C++ ewentualnie Rust albo ASM to zostało zaimplementowane, że wyszykiwarka programu pomija znaki diakrytyczne?

Zaproponujcie coś. Męczę temat od kilku dni.

0

Wg mnie najprościej:
translit.test.php

<?php
require('translit.inc.php');

function conv($str)
{
	global $translit;
	$ret='';
	$len=mb_strlen($str,'UTF-8');
	for($i=0;$i<$len;++$i)
	{
		$key=mb_substr($str,$i,1,'UTF-8');
		$ret.=(array_key_exists($key,$translit)?$translit[$key]:$key);
	}
	return $ret;
}

echo conv('Żółw wąż żółtodziób')."\n";
?>

Zaś dla stworzenia 'translit.inc.php':
translit.make.php

<?php
$table=
[
'AÀÁÂÃÄÅÆĀĂĄ',
'aàáâãäåæāăą',
'Bß',
'CÇĆĈĊČŒ',
'cçčćĉċœ',
'DÐĎĐ',
'dðďđ',
'EÈÉÊËĒĔĖĘĚ',
'eèéêëēĕėęě',
'IÌÍÎÏĨĪĬĮİ',
'iìíîïĩīĭįı',
'JIJĴ',
'jijĵ',
'GĜĞĠĢ',
'gĝğġģ',
'HĤ',
'hĥħ',
'KĶ',
'kķĸ',
'LĹĻĽĿŁ',
'lĺļľŀł',
'NÑŃŅŇŊ',
'nñńņňʼnŋ',
'OÒÓÔÕÖØŌŎŐ',
'oòóôõöøōŏő',
'RŔŖŘ',
'rŕŗř',
'ÙÚÛÜŮŰŲ',
'ùúûüůűų',
'WŴ',
'wŵ',
'YÝŶŸ',
'yýŷÿ',
'ZŹŻŽ',
'zźżž'
];
$translit=array();
foreach($table as $line)
{
	$len=mb_strlen($line,'UTF-8');
	$value=mb_substr($line,0,1,'UTF-8');
	for($i=1;$i<$len;++$i) $translit[mb_substr($line,$i,1,'UTF-8')]=$value;
}
$fd=fopen('translit.inc.php','w');
fwrite($fd,'<?php $translit='.var_export($translit,true).'; ?>');
fclose($fd);
?>

translit.make.php odpalamy raz jak znajdziemy kolejną literkę którą pominęliśmy.

1 użytkowników online, w tym zalogowanych: 0, gości: 1