Wyszukiwanie wektorowe [cz. 1]

Pod mądrą nazwą wyszukiwania wektorowego kryje się odpowiedź na proste pytanie:

Jak najlepiej dopasować wyniki wyszukania do zapytania,
w sytuacji gdy nie mamy elementów pasujących idealnie?

W życiu pytanie to pojawia się na wszelkich stronach z wyszukiwarką produktów, czy to mieszkań, czy aut, czy komputerów. Gdy produkty te są opisane wyłącznie za pomocą liczb, sprawa jest prosta. Np.: auto o przebiegu 200 000 km, rok produkcji – 2006, średnie spalanie 8 l/100 km. Wiadomo, że auta o mniejszym przebiegu są lepsze, niż te spalające więcej, że im dawniej wyprodukowano auto, tym gorzej, że lepiej żeby paliło mniej niż więcej. Czy dzięki temu wiemy, jakie auta będą na pewno lepsze, a jakie gorsze od tego z przykładu? Prawie tak. Prawie, ponieważ nie wiemy, jak przeliczyć jeden rok wieku auta na kilometry przebiegu i litry paliwa.

Zacznijmy więc od sprawy najprostszej, czyli od bazy aut o tylko jednej cesze, czyli opisane rokiem produkcji. Powiedzmy, że w bazie znajdą się dwa auta:

  1. A, rok prod. 2001
  2. B, rok prod 2009

Gdy dla szukającego auta do zaakceptowania jest pojazd maksymalnie z 2004 roku, sprawa jest jasna nawet intuicyjnie: wybieramy to z 2009, czyli B.

Zbiór aut pasujących do pytania o niestarsze niż z 2004 roku

Zbiór aut pasujących do pytania o niestarsze niż z 2004 roku

Jednak jeśli szukający postawi sprawę nieco inaczej, czyli zamiast «chcę auto maksymalnie z 2004 roku» powie: «chcę auto wyprodukowane mniej-więcej w 2004 roku», to najbliżej jego ideału jest samochód A, z 2001.

Auto najlepiej pasujące do pytania o "z mniej więcej 2004 roku"

Można mieć wątpliwości, czy wyszukiwarka powinna pokazać auto bliższe zadanym kryteriom, czy też o korzystniejszych parametrach. Rok produkcji to cecha, której wartość wprost przekłada się na jakość auta, zmieńmy ją na taką, która jest neutralna, np. długość pojazdu. Nasza baza zmienia się na taką:

  1. A, długość 390 cm
  2. B, długość 450 cm

Jeśli teraz poszukiwane auto ma mieć 400 cm długości, bardzo blisko tej wartości znajdzie się długość pojazdu A. Gdyby ułożyć samochody z bazy wg dopasowania do zadanych parametrów, A byłoby przed B. To właśnie najprostszy przykład wektorowego opisu wyniku wyszukiwania. Wektor dopasowania to odległość między punktem zadanym przez zapytanie (Z:400 cm) a punktami opisującymi rekordy bazy (A:390 cm i B:450 cm). Wektory dopasowania dla obu aut mają odpowiednio:

  1. Z – A: abs(390 – 400) = 10 cm
  2. Z – B: abs(450 – 400) = 50 cm

funkcja abs(x), czyli wartość bezwzględna ;-)

Dopasowanie aut o długości około 400 cm

Dopasowanie aut o długości około 400 cm

Dla A długość wektora dopasowania jest 5-krotnie mniejsza niż dla B, więc A jest dużo lepiej dopasowany do zadanych parametrów od B. Kiedy do długości dodamy szerokość, sprawa skomplikuje się o drugi wymiar. Wektor dopasowania będziemy wtedy obliczać jako złożenie składowych, 1-wymiarowych wektorów długości i szerokości. To znana zależność, kojarzona z Pitagorasem. ;) Długość wektora dopasowania to pierwiastek kwadratowy z sumy kwadratów obu wektorów składowych.

Przekład na SQL

Przekładając problem na SQL, zaczynamy od takiego pytania:

SELECT * FROM samochody
WHERE dlugosc = 400;

W naszej bazie nie daje ono oczywiście żadnych elementów, czyli zwraca zbiór pusty. Dla naszego poszukiwacza aut to dość frustrująca odpowiedź.
Możemy to nieco poprawić, dodając tolerancję:

SELECT * FROM samochody
WHERE dlugosc > 400 - tolerancja AND
dlugosc < 400 + tolerancja;

To zwiększa szanse na dobry wynik, ale tylko czasem. Tolerancja może być za mała albo za duża, a zawsze jest kolejnym utrudnieniem dla użytkownika.
Moim zdaniem lepiej będzie tak:

SELECT * FROM samochody
ORDER BY abs(400 - dlugosc) ASC;

Wydajnościowo to nienajlepsze rozwiązanie, ale pokazuję tu tylko zasadę. Nienajlepsze, bo spowoduje posortowanie całej bazy aut, w dodatku wg obliczanego, a nie przechowywanego, parametru. W realnych zastosowaniach można uniknąć sortowania wprowdzając nieco więcej logiki przed wykonaniem zapytania.

w roli pojazdu A udział wziął Citroen 1968 Dyane Luxe, w roli pojazdu B - Honda CR-V

porównywanie baz MySQL w PHP

Często przy pracy nad aplikacjami z bazą SQL zdarza się sytuacja, kiedy baza ‘deweloperska’ uległa nieudokumentowanym dokładnie zmianom, i wreszcie trzeba je wdrożyć w wersji produkcyjnej. Oczywiście lepiej byłoby prowadzić dziennik zmian :) Kiedy jednak już jest za późno, przydaje się poniższy skrypt do porównywania baz. Wychwytuje on różnice na poziomie definicji tabel i pól, proponując stosowne polecenia typu CREATE, ALTER, itp. Propozycje te nie są doskonałe, trzeba zwracać uwagę zwłaszcza na klucze i wartości domyślne, ale na pewno ułatwia synchronizację.

Skrypt napisany na podstawie MySQL Database Diff Script, ale znacznie rozszerzony i zmodyfikowany. Enjoy! :)

<html>
<head>
<style>
.deleted{
	text-decoration:line-through;
	color:red;
}
.added{
	color:green;
}
</style>
</head>
<body>
< ?php
/**
 * original by Adam Young http://adamyoung.net/
 * modified and extended 2009-09-26 by ptrk http://nobigwords.ntxt.net/ 
 * 
 */
$src_host = '';
$src_user = 'root';
$src_pass = '';
$src_db = 'baza32_superskl_emisja';
 
$dst_host = '';
$dst_user = 'root';
$dst_pass = '';
$dst_db = 'baza32_superskl';
 
$src = mysql_connect($src_host, $src_user, $src_pass);
if (!mysql_select_db($src_db, $src)) die("Could not find/USE source database: {$src_db}\n");
 
$src_tables = getTables($src);
 
 
$dst = mysql_connect($dst_host, $dst_user, $dst_pass);
if (!mysql_select_db($dst_db, $dst)) die("Could not find/USE destination database: {$dst_db}\n");
 
$dst_tables = getTables($dst);
 
 
foreach ($src_tables as $t => $table) {
	$i = 0;
	$found = false;
 
	if(isset($dst_tables[$t])){
		$dst_table = $dst_tables[$t];
		if ($dst_table->name == $table->name) {
			$diff = compareDefinitions($table->definition, $dst_table->definition);
			if (count($diff) == 0){
				echo "{$table->name}<br />";
			}else{
				echo "<b>{$table->name} is different</b><br /><ul>";
				$alters = '';
				foreach($diff as $col => $info){
					echo "<li>$col : " . $info['info'] . "</li>";
					$alters .= "alter table `" . $dst_table->name . "` " . $info['alter'] . ";\n";
				}
				echo '</ul>';
				echo "&gt;pre&lt;" . $alters . " &gt;/pre&lt;";
			}
			unset($dst_tables[$i]);
			$found = true;
		}
	}else{
		echo "<span class='deleted'>{$table->name} </span><br />";
		echo "&gt;pre&lt;{$table->create}&gt;/pre&lt;<br />";
	}
}
 
function getTables($link) {
	$rsrc = mysql_query('SHOW TABLES', $link);
	$tables = array();
	while ($row = mysql_fetch_row($rsrc)) {
		$table = new table(
			$row[0],
			getTableDef($link, $row[0]),
			getTableCreate($link, $row[0])
		);
		$tables[$row[0]] = $table;
	}
	return $tables;
}
 
function getTableDef($link, $table) {
	$rsrc = mysql_query("DESCRIBE `{$table}`");
	$result = array();
	while($row = mysql_fetch_row($rsrc)){
		list($name, $type, $null, $key, $default, $extra) = $row;
		$result[$name] = array($type, $null, $key, $default, $extra);
	}
	return $result;
}
 
function getTableCreate($link, $table) {
	$rsrc = mysql_query("SHOW CREATE TABLE `{$table}`");
	$row = mysql_fetch_row($rsrc);
	$result = $row[1];
	return $result;
}
 
class table {
	var $name;
	var $definition;
	var $create;
	function table($name, $def, $create) {
		$this->name = $name;
		$this->definition = $def;
		$this->create = $create;
	}
}
 
function compareDefinitions($defA, $defB){
	$result = array();
	foreach($defA as $col => $colDefA){
		if(!isset($defB[$col])){
			$result[$col]['info'] = "<span class='deleted'>deleted </span>";
			$result[$col]['alter'] = "drop column `" . $col . "`";
		}else{
			if(implode(',',$colDefA) != implode(',',$defB[$col])){
 
				list($typeA, $nullA, $keyA, $defaultA, $extraA) = $colDefA;
				list($typeB, $nullB, $keyB, $defaultB, $extraB) = $defB[$col];
				$info = '';
				if($typeA != $typeB) $info .= "different type $typeA/$typeB, ";
				if($nullA != $nullB) $info .= "null $nullA/$nullB, ";
				if($keyA != $keyB) $info .= "key: $keyA/$keyB, ";
				if($defaultA != $defaultB) $info .= "default: $defaultA/$defaultB, ";
				if($extraA != $extraB) $info .= "extra: $extraA/$extraB, ";
				$result[$col]['info'] = trim($info,', ');
				$result[$col]['alter'] =	"change `$col` `$col` $typeA ";
				$result[$col]['alter'] .=	($defaultA == '') ? '' : "default '$defaultA' ";
				$result[$col]['alter'] .=	($nullA == 'NO') ? 'NOT NULL ' : '';
				$result[$col]['alter'] .=	"$extraA ";
 
			}
			unset($defB[$col]);
		}
	}
	foreach($defB as $colB => $colDefB){
		list($typeB, $nullB, $keyB, $defaultB, $extraB) = $colDefB;
		$result[$colB]['info'] = "<span class='added'>added</span>";
		$result[$colB]['alter'] = "add `$colB` $typeB ";
 
	}
	return $result;
}
 
 
function errors($link){
	return mysql_errno($link) . ": " . mysql_error($link) . "\n";
}
?>
</body>
</html>

puścić coś w tle

Chcę móc uruchomić proces w tle za pomocą przeglądarki. Docelowo wszystko będzie działać na linkuksie, ale do testów przydałoby się to mieć u siebie. W linkuksie wiem.

costam &

uruchamia proces w tle, potem fajnie mogę sobie zarządzać

jobs

. Ale ja chcę w Windows. Windows Vista, żeby nie było niedomówień. Wywołanie komendy przez exec działa, ale czeka na zakończenie, a nie o to chodzi. Proces ma działać długo, a ja tylko chcę monitorować jego stan, a nie zamulać Firefoksa.

exec("start /b c:/sciezka_do_php/php.exe C:/sciezka_do_skryptu_procesu/bgproces.php > test.txt");

Próbowałem też uruchomić jakiś batch (costam.bat), który z kolei miałby uruchomić skrypt PHP, ale to też nie zadziałało – cały czas przeglądarka czekała na zakończenie całego procesu. Rozwiązanie jest takie, że w PHP na Windows trzeba użyć klasy COM, czyli windowsowych mechanizmów OLE (Object Linking and Embedding). Wygląda to tak:

< ?php
/*
 * skrypt do uruchomienia w tle skryptTla.php
 */
$fname = 'c:/log.html'; 
for($i=0; $i&lt;10; $i++){
	sleep(1);
	file_put_contents($fname, date('H:i:s') . '<br/>', FILE_APPEND);
}
file_put_contents($fname, '<hr />', FILE_APPEND);
?>
< ?php
/**
 * skrypt widziany przez przeglądarkę
 */
echo "uruchamiam " . date('H:i:s ');
 
  if(isset($_SERVER['PWD']))
  {
  	// $cmd = '....';
    $nullResult = "php $cmd > /dev/null &";
  }else{
    $cmd = 'sciezka_do_php/php.exe sciezkaSkryptuDoUruchomieniaWTle/skryptTla.php';
    $ws = new COM("WScript.Shell");
    $oExec = $ws->Run($cmd, 0, false);
  }
 
echo " koniec " . date('H:i:s ');
echo " OK ";
 
 
?>

Next Page »