PhpIrbis

Форк
0
/
Teapot.php 
399 строк · 12.2 Кб
1
<?php
2

3
/** @noinspection PhpUnused */
4

5
namespace Irbis;
6

7
require_once 'Search.php';
8

9
static $_stopWords = array(
10
    'a', 'about', 'after', 'against', 'all', 'als', 'an', 'and', 'as', 'at', 'auf', 'aus', 'aux', 'b', 'by', 'c', 'd',
11
    'da','dans', 'das', 'de', 'der', 'des', 'di', 'die', 'do', 'du', 'e', 'ein', 'eine', 'einen', 'eines', 'einer',
12
    'el', 'en', 'et', 'f', 'for', 'from', 'fur', 'g', 'h', 'i', 'ihr', 'ihre', 'im', 'in', 'into', 'its', 'j', 'k',
13
    'l', 'la', 'las', 'le', 'les', 'los', 'm', 'mit', 'mot', 'n', 'near', 'non', 'not', 'o', 'of', 'on', 'or', 'over',
14
    'out', 'p', 'par', 'para', 'qui', 'r', 's', 'some', 'sur', 't', 'the', 'their', 'through', 'till', 'to', 'u',
15
    'uber', 'und', 'under', 'upon', 'used', 'using', 'v', 'van', 'w', 'when', 'with', 'x', 'y', 'your','z', 'а',
16
    'ая', 'б', 'без', 'бы', 'в', 'вблизи', 'вдоль','во', 'вокруг', 'всех', 'г', 'го', 'д', 'для', 'до', 'е','его',
17
    'ее', 'ж', 'же', 'з', 'за', 'и', 'из', 'или', 'им', 'ими', 'их', 'к', 'как', 'ко', 'кое', 'л', 'летию', 'ли',
18
    'м', 'между', 'млн', 'н', 'на', 'над', 'не', 'него', 'ним', 'них', 'о', 'об', 'от', 'п', 'по', 'под', 'после',
19
    'при', 'р', 'с', 'со', 'т', 'та', 'так', 'такой', 'также', 'то', 'тоже', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ы',
20
    'ые', 'ый', 'э', 'этих', 'этой', 'ю', 'я',
21
);
22

23
/**
24
 * Проверка, является ли заданный текст стоп-словом.
25
 *
26
 * @param $text string Текст для проверки.
27
 * @return bool Результат.
28
 */
29
function isStopWord($text)
30
{
31
    global $_stopWords;
32

33
    if (!$text) {
34
        return true;
35
    }
36

37
    foreach ($_stopWords as $word) {
38
        if (!strcasecmp($word, $text)) {
39
            return true;
40
        }
41
    }
42

43
    return false;
44
}
45

46
/**
47
 * Коэффициент релевантности.
48
 */
49
final class RelevanceCoefficient
50
{
51
    /**
52
     * @var array Массив меток полей, для которых действует данный коэффициент.
53
     */
54
    public $fields;
55

56
    /**
57
     * @var double Значение коэффициента.
58
     */
59
    public $value;
60

61
    /**
62
     * Конструктор.
63
     */
64
    public function __construct($value, $tags)
65
    {
66
        $this->fields = $tags;
67
        $this->value = $value;
68
    }
69
}
70

71
/**
72
 * Настройки для оценки релевантности.
73
 */
74
final class RelevanceSettings
75
{
76
    /**
77
     * @var array Массив коэффициентов релевантности.
78
     */
79
    public $coefficents;
80

81
    /**
82
     * @var double Релевантность для упоминаний в посторонних полях.
83
     */
84
    public $extraneous;
85

86
    /**
87
     * @var double Мультипликатор для случая полного совпадения.
88
     */
89
    public $multiplier;
90

91
    /**
92
     * Конструктор.
93
     */
94
    public function __construct()
95
    {
96
        $this->coefficents = array();
97
        $this->extraneous = 1.0;
98
        $this->multiplier = 2.0;
99
    }
100

101
    /**
102
     * @return RelevanceSettings Настройки по умолчанию для базы IBIS.
103
     */
104
    public static function forIbis()
105
    {
106
        $result = new RelevanceSettings();
107
        $result->extraneous = 1.0;
108
        $result->multiplier = 2.0;
109
        $result->coefficents = [
110

111
            // заглавие или авторы
112
            new RelevanceCoefficient (10,
113
                [
114
                    200, // основное заглавие
115
                    700, 701, // индивидуальные авторы
116
                    710, 711, 971, 972, // коллективные авторы
117
                    923, // выпуск, часть
118
                    922, // статья сборника
119
                    925, // несколько томов в одной книге
120
                    961, // индивидуальные авторы общей части
121
                    962, // коллективы общей части
122
                    461, // заглавие общей части
123
                    463 // издание, в котором опубликована статья
124
                ]),
125

126
            // редакторы
127
            new RelevanceCoefficient(7, [702]),
128

129
            // прочие заглавия
130
            new RelevanceCoefficient(6, [
131
                510, // параллельное заглавие
132
                517, // разночтение заглавия
133
                541, // перевод заглавия
134
                924, // "другое" заглавие
135
                921 // транслитерированное заглавие
136
            ]),
137

138
            // содержание
139
            new RelevanceCoefficient(6, [
140
                330, // оглавление
141
                922 // статья из журнала
142
            ]),
143

144
            // рубрики
145
            new RelevanceCoefficient(5, [
146
                606, // предметная рубрика
147
                607, // географическая рубрика
148
                600, 601, // персоналия
149
                965 // дескриптор
150
            ]),
151

152
            // серия
153
            new RelevanceCoefficient(4, [225]),
154

155
            // ключевые слова и аннотации
156
            new RelevanceCoefficient(3, [
157
                610, // ненормированное ключевое слово
158
                331 // аннотация
159
            ])
160
        ];
161

162
        return $result;
163
    }
164

165
    /**
166
     * Загрузка настроек из указанного файла.
167
     *
168
     * @param $filename string Имя файла.
169
     * @return RelevanceSettings
170
     */
171
    public static function load($filename)
172
    {
173
        $text = file_get_contents($filename);
174
        $text = preg_replace( '![ \t]*//.*[ \t]*[\r\n]!', '', $text );
175
        return json_decode($text, false);
176
    }
177
}
178

179
/**
180
 * Оценщик релевантности найденных библиографических записей.
181
 */
182
final class RelevanceEvaluator
183
{
184
    /**
185
     * @var RelevanceSettings Настройки для оценки.
186
     */
187
    public $settings;
188

189
    /**
190
     * @var array Массив терминов, на которые разбивается поисковый запрос.
191
     */
192
    public $terms;
193

194
    /**
195
     * Оценка содержимого подполя.
196
     *
197
     * @param $text string Содержимое подполя.
198
     * @param $value double Важность поля.
199
     * @return double Оценка, выраженная числом.
200
     */
201
    private function evaluateText($text, $value) {
202
        $result = 0.0;
203

204
        if ($text) {
205
            foreach ($this->terms as $term) {
206
                if (stripos($text, $term) !== false) {
207
                    if (strcasecmp($text, $term) === 0) {
208
                        $result += $value * $this->settings->multiplier;
209
                    } else {
210
                        $result += $value;
211
                    }
212
                }
213
            }
214
        }
215

216
        return $result;
217
    }
218

219
    /**
220
     * Оценка содержимого поля.
221
     *
222
     * @param $field RecordField Поле, подоежащее оценке.
223
     * @param $value double Важность поля.
224
     * @return double Оценка, выраженная числом.
225
     */
226
    private function evaluateField ($field, $value) {
227
        $result = $this->evaluateText($field->value, $value);
228

229
        foreach ($field->subfields as $subfield) {
230
            $result += $this->evaluateText($subfield->value, $value);
231
        }
232

233
        return $result;
234
    }
235

236
    /**
237
     * Оценка реалеватности записи.
238
     *
239
     * @param $record MarcRecord Запись, подлежащая оценке.
240
     * @return double Оценка, выраженная числом.
241
     */
242
    public function evaluate($record)
243
    {
244
        $result = 0.0;
245

246
        foreach ($this->settings->coefficents as $coefficent) {
247
            foreach ($coefficent->fields as $tag) {
248
                $fields = $record->getFields($tag);
249
                foreach ($fields as $field) {
250
                    $result += $this->evaluateField($field, $coefficent->value);
251
                }
252
            }
253
        }
254

255
        foreach ($record->fields as $field) {
256
            $result += $this->evaluateField($field, $this->settings->extraneous);
257
        }
258

259
        return $result;
260
    }
261
}
262

263
/**
264
 * Простой поиск "для чайников".
265
 */
266
final class Teapot
267
{
268
    /**
269
     * @var array Массив префиксов для терминов.
270
     */
271
    public $prefixes;
272

273
    /**
274
     * @var string Суффикс для терминов.
275
     */
276
    public $suffix;
277

278
    /**
279
     * @var RelevanceSettings Настройки для оценки релевантности.
280
     */
281
    public $settings;
282

283
    /**
284
     * @var int Максимальное количество возвращаемых записей.
285
     */
286
    public $limit;
287

288
    /**
289
     * Конструктор.
290
     */
291
    public function __construct()
292
    {
293
        // поиск по: автору, заглавию, коллективу, ключевым словам
294
        $this->prefixes = array('A=', 'T=', 'M=', 'K=');
295
        $this->suffix = '$';
296
        $this->settings = RelevanceSettings::forIbis();
297
        $this->limit = 500;
298
    }
299

300
    /**
301
     * @var array Массив терминов.
302
     */
303
    private $terms;
304

305
    /**
306
     * Построение поискового выражения по запросу на естественном языке.
307
     *
308
     * @param $query string Запрос на естественном языке.
309
     * @return string Выражение для поиска по словарю.
310
     */
311
    public function buildSearchExpression($query)
312
    {
313
        $this->terms = [];
314

315
        if (!$query) {
316
            return '';
317
        }
318

319
        $query = trim($query);
320
        if (!$query) {
321
            return '';
322
        }
323

324
        $terms = array();
325
        $terms[$query] = 1;
326
        preg_match_all('/\w+/u', $query, $words);
327
        foreach ($words[0] as $word) {
328
            $terms[$word] = 1;
329
        }
330

331
        $result = '';
332
        $first = true;
333
        $terms = array_keys($terms);
334
        sort($terms);
335
        foreach ($terms as $term) {
336
            if (isStopWord($term)) {
337
                continue;
338
            }
339

340
            $this->terms []= $term;
341
            foreach ($this->prefixes as $prefix) {
342
                if (!$first) {
343
                    $result .= ' + ';
344
                }
345

346
                $result .= Search::wrapIfNeeded($prefix . $term . $this->suffix);
347

348
                $first = false;
349
            }
350
        }
351

352
        return $result;
353
    }
354

355
    /**
356
     * Поиск "для чайников" в текущей базе.
357
     *
358
     * @param $connection Connection Активное подключение к серверу.
359
     * @param $query string Запрос на естественном языке.
360
     * @return array Массив найденных MFN.
361
     */
362
    public function search($connection, $query) {
363
        $query = trim($query);
364
        if (!$query) {
365
            return [];
366
        }
367

368
        $expression = $this->buildSearchExpression($query);
369
        if (!$expression) {
370
            return [];
371
        }
372

373
        $found = $connection->searchRead($expression, $this->limit);
374
        if (!$found) {
375
            return [];
376
        }
377

378
        $evaluator = new RelevanceEvaluator();
379
        $evaluator->settings = $this->settings;
380
        $evaluator->terms = $this->terms;
381
        $rating = [];
382
        foreach ($found as $record) {
383
            $item = (object) array (
384
                'record' => $record,
385
                'rating' => $evaluator->evaluate($record)
386
            );
387
            $rating []= $item;
388
        }
389

390
        usort($rating, static function ($first, $second) {
391
            // сортировка по убыванию
392
            return $second->rating - $first->rating;
393
        });
394

395
        return array_map (static function ($item) {
396
            return $item->record->mfn;
397
        }, $rating);
398
    }
399
}
400

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.