Поиск кратчайшего пути по алгоритму Флойда-Уоршелла




Принцип обмена данными из 1С с сайтом (на MySQL) и выдачи (публикации) этих данных по запросу.
PHP-Скрипт автоматической загрузки данных из файла данных в формате CSV в базу данных сайта работающего на WordPress.

В продолжение моей темы: 1С:Альфа-Авто Автосалон Автосервис: обмен с сайтом.
С помощью данного скрипта можно загружать в автоматическом режиме, по расписанию, данные сервисных книжек (ремонтов авто) из 1С:Альфа-Авто Автосалон Автосервис.
Также можно загружать данные в ручном режиме: для этого делается скрытая страница, где размещается специальная кнопка.
Комментарии размещенные внутри скрипта разъяснят логику и порядок действия.
Комментарии с "/////    echo" использовались для отладки.
Дополнительно создана таблица для журналирования результатов загрузки данных.
Скрипт включает в себя защиту от SQL инъекций (думаю безопасность соблюдена в полной мере).
В кратце:
1. Пишется скрипт, который запускает этот.
2. Создается регламентное задание в WordPress, по которому запускается скрипт из п.1. 
3. Этот скрипт осуществляет проверку на существование файла обмена в папке.
4. Если данные не новые, загрузка не производится.
5. Если данные новые, очищается таблица сервисных книжек.
6. Загружаются новые данные.

Собственно сам скрипт:

<?php // Полная загрузка сервисных книжек, создан 2026-01-05 12:44:55

global $wpdb2;
global $failure;
global $file_hist;

/////  echo '<H2><b>Старт загрузки</b></H2><br>';

$failure=FALSE;
//подключаемся к базе
$wpdb2 = include_once 'connection.php'; ; // подключаемся к MySQL
// если не удалось подключиться, и нужно оборвать PHP с сообщением об этой ошибке
if (!empty($wpdb2->error))
{
/////   echo '<H2><b>Ошибка подключения к БД, завершение.</b></H2><br>';
$failure=TRUE;
wp_die( $wpdb2->error );
}

$m_size_file=0;
$m_mtime_file=0;
$m_comment='';
/////проверка существования файлов выгрузки из 1С
////файл выгрузки сервисных книжек
$file_hist = ABSPATH.'/_1c_alfa_exchange/AA_hist.csv';
if (!file_exists($file_hist))
{
/////   echo '<H2><b>Файл обмена с сервисными книжками не существует.</b></H2><br>';
$m_comment='Файл обмена с сервисными книжками не существует';
$failure=TRUE;
}

/////инициируем таблицу лога
/////если не существует файла то возврат и ничего не делаем
if ($failure){
///включает защиту от SQL инъекций и данные можно передавать как есть, например: $_GET['foo']
/////   echo '<H2><b>Попытка вставить запись в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>$m_comment));
wp_die();
/////    echo '<H2><b>Возврат в начало.</b></H2><br>';
return $failure;
}
/////проверка лога загрузки, что бы не загружать тоже самое
$masiv_data_file=stat($file_hist);   ////передаем в массив свойство файла
$m_size_file=$masiv_data_file[7];    ////получаем размер файла
$m_mtime_file=$masiv_data_file[9];   ////получаем дату модификации файла
////создаем запрос на получение последней удачной загрузки
////выбираем по штампу времени создания (редактирования) файла загрузки AA_hist.csv, $m_mtime_file

/////   echo '<H2><b>Размер файла: '.$m_size_file.'</b></H2><br>';
/////   echo '<H2><b>Штамп времени файла: '.$m_mtime_file.'</b></H2><br>';
/////   echo '<H2><b>Формирование запроса на выборку из лога</b></H2><br>';
////препарируем запрос
$text_zaprosa=$wpdb2->prepare("SELECT * FROM `vin_logs` WHERE `last_mtime_upload` = %s", $m_mtime_file);
$results=$wpdb2->get_results($text_zaprosa);

if ($results)
{   foreach ( $results as $r)
{
////если штамп времени и размер файла совпадают, возврат
if (($r->last_mtime_upload==$m_mtime_file) && ($r->last_size_upload==$m_size_file))
{////echo '<H2><b>Возврат в начало, т.к. найдена запись в логе.</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>'Загрузка отменена, новых данных нет, т.к. найдена запись в логе.'));
wp_die();
return $failure;
}
}
}
////если данные новые, пишем в лог запись о начале загрузки
/////echo '<H2><b>Попытка вставить запись о начале загрузки в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>0, 'last_size_upload'=>$m_size_file, 'comment'=>'Начало загрузки'));

////очищаем таблицу
$clear_tbl_zap=$wpdb2->prepare("TRUNCATE TABLE %s", 'vin_history');
$clear_tbl_zap_repl=str_replace("'","`",$clear_tbl_zap);
$results=$wpdb2->query($clear_tbl_zap_repl);
/////   echo '<H2><b>Очистка таблицы сервисных книжек</b></H2><br>';
if (empty($results))
{
/////   echo '<H2><b>Ошибка очистки таблицы книжек, завершение.</b></H2><br>';
//// если очистка не удалась, возврат
$failure=TRUE;
wp_die();
return $failure;
}

////загружаем данные
$table='vin_history';         // Имя таблицы для импорта
//$file_hist Имя CSV файла, откуда берется информация     // (путь от корня web-сервера)
$delim=';';          // Разделитель полей в CSV файле
$enclosed='"';      // Кавычки для содержимого полей
$escaped='\

18 Comments

  1. dablack

    Таблицу в которой около 40 «складов» (не строк) обработает ? пробовали ?

    Reply
  2. Garykom

    (1) dablack,

    нет не пробовал, скиньте пример такой таблицы, попробую и выложу результат

    но до 1000 складов нормально должно работать

    Reply
  3. dablack

    Вот файл таблицы значений через ЗначениеВФайл. Таблица из 32 пунктов.

    Попробуйте пожалуйста.

    Reply
  4. Garykom

    (3) dablack, все пути из Склад1 в (Склад2 — Склад32) в один шаг самые короткие

    P.S. хотя ошибаюсь, сразу не заметил что вместо прямого Склад1 — Склад20 = 310 395

    Начали расчет: 11.01.2016 13:51:46 Склад1 — Склад20

    Окончили расчет: 11.01.2016 13:51:47 Склад1 — Склад20

    Минимальный путь: 310 394

    |СкладОткуда|СкладКуда|Расстояние

    |Склад1|Склад27|243 052

    |Склад27|Склад20|67 342

    на 1 меньше ))

    Reply
  5. Garykom

    Кому интересно есть продвинутая версия.

    Которая умеет считать маршруты когда они не постоянные статичные.

    А разные (периодические) по разным по дням недели.

    Reply
  6. ildarovich

    Попытался сравнить быстродействие приведенного здесь метода с методом из статьи Определение кратчайших путей, критических путей одним запросом. . По идее, алгоритм Флойда должен иметь сравнимое быстродействие. Его оценка трудоемкости ~O(N#k8SjZc9Dxk3), метода «матричного умножения» ~O(Log(K)*N#k8SjZc9Dxk3), где К — диаметр графа. Множитель Log(K) нивелируется тем, что массовые вычисления в запросе делаются «пакетом» и поэтому быстрее в 10-50 раз.

    Однако столкнулся с тем, что на схеме метро функция не работает. Насколько я понял, отрезки пути, найденные алгоритмом, не находятся в исходном графе.

    Прилагаю файл связности станций питерского метро, полученный сохранением в текстовом файле строки, равной ЗначениеВСтрокуВнутр(ТаблицаМаршрутов).

    Reply
  7. Garykom

    (6) ildarovich, боюсь некорректно сравнивать эти два метода.

    Флойд долго думает, но считает сразу одновременно все длины и находит кратчайшие пути между всеми парами вершин.

    Т.е. после расчета и сохранения в массивы результат выдается мгновенно для любой пары вершин.

    Reply
  8. ildarovich

    (7) думаю, вы невнимательно прочитали статью, на которую я неоднократно ссылался. Это метод, решающий ту же задачу «All Pairs Shortest Paths (APSP) problem». Так же находятся кратчайшие расстояния между всеми парами вершин. Поэтому сравнивать эти решения можно и нужно.

    Проблема в том, что практическая реализация метода, сделанная здесь, пока, на мой взгляд, попросту НЕ РАБОТАЕТ. Вообще не находит путь в графе, пример которого приведен в (6) (вываливается с ошибкой).

    Кстати, проблема восстановления кратчайшего пути по кратчайшим расстояниям между всеми парами в статье решается гораздо проще. В кратчайший путь включаются все вершины ПунктПути, обладающие свойством:

    КратчайшееРасстояние(Откуда, ПунктПути) + КратчайшееРасстояние(ПунктПути, Куда) = КратчайшееРасстояние(Откуда, Куда)

    Пункты упорядочиваются по КратчайшееРасстояние(Откуда, ПунктПути). При этом никаких предыдущих вершин вычислять и хранить вообще не нужно. Дополнительно это дает возможность вывода нескольких равнозначных вариантов равной длины. Затраты на проверку лишних вершин, которые потом не войдут путь, очень малы (по отношению к основным затратам метода).

    Reply
  9. ildarovich

    +(6) файлы схемы метро в другом формате (mxl, xls)

    Reply
  10. Garykom

    (9) ildarovich, все работает

    Маршрут считает, что по дням — это с другого более сложного проекта 🙂

    Считал 15 минут потому что 140 станций * 7 дней вершин

    Reply
  11. ildarovich

    (10) я взял функцию из вашей обработки, вставил в свою, получил ошибку (см.скриншоты). — Что я делаю не так?

    Отладчик показал, что отрезок пути, который ищется в ТЗМаршруты, там не находится, из-за этого ТекСтр = Неопределено и функция вылетает.

    Кстати, создавать колонку поиска было необязательно, достаточно было проиндексировать ТЗ по двум колонкам: тзМаршруты.Индексы.Добавить(«СкладОткуда, СкладКуда»).

    Про

    потому что 140 станций * 7 дней вершин

    не понял. В Питере ~65 станций. 140 — это число связей между станциями.

    В общем, хотелось бы разобраться и получить результат сравнения по скорости, не отлаживая самому вашу разработку.

    Reply
  12. Garykom

    (11) ildarovich, проверял на доработанном алгоритме который умеет считать маршруты между складами когда они не по каждым дням недели ездят.

    Причем с ожиданием если в этот день недели когда приехали нет маршрутов отправления.

    для простоты проверил на доработанном, там из екселя как раз загрузка идет, при расчете он каждую вершину (склад, станцию) превращает в 7 по количеству дней в неделе, и даты которые показывает это сколько дней едем

    и да ошибся не 140*7, а 65*7

    насчет ошибки все правильно, восстановление пути для Флойда возможно двумя способами 🙂 http://hci.fenster.name/304y/lab5/

    в выложенном сделан «…В этом случае значение массива C[j][k] после окончания алгоритма будет указывать одну из вершин, через которую проходит путь от j к k …»

    выложенный алгоритм правильно считает кратчайший путь, но не всегда может его восстановить

    Reply
  13. ildarovich
    Reply
  14. Garykom

    (13) ildarovich, на 30% быстрее получилось потому что соединили алгоритмы Флойда и Дейкстры.

    И еще потому что вершин и ребер мало. Особенно потому что количество ребер практически равно количеству вершин — линии метро однако.

    Попробуйте на 500 вершинах и 5000 ребер? Запрос в этом случае умрет, вариант с соответствием будем в 2-2.5 раза медленнее чем с массивами. Точно будет зависеть от отношения количества вершин к количеству ребер.

    Reply
  15. ildarovich

    (14)

    Запрос в этом случае умрет, вариант с соответствием будем в 2-2.5 раза медленнее чем с массивами

    — А ставку сделать готовы?

    соединили алгоритмы Флойда и Дейкстры

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

    Reply
  16. Garykom

    (15) ildarovich,

    — А ставку сделать готовы?

    Так по опыту собственному и сужу

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

    Какая разница каким образом оптимизировать алгоритм?

    Алгоритм Дейкстры просматривает ребра (получая из них вершины), обычный Флойда все вершины парами (не важно есть ли между ними ребро).

    Модернизированный Флойд:

    «Но существует решение и за sum_{n,;n,;n}O(1) = O(n#k8SjZc9Dxk2), где хранятся значения не для всех вершин, а только значения для предыдущей вершины, так как следующая получается рекурсивно.»

    https://ru.wikipedia.org/wiki/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D­0%A4%D0%BB%D0%BE%D0%B9%D0%B4%D0%B0_%E2%80%94_%D0%A3%D0%BE%D1­%80%D1%88%D0%B5%D0%BB%D0%BB%D0%B0

    И да через битовые маски еще быстрее…

    Reply
  17. ildarovich

    (16)

    — А ставку сделать готовы?

    Я написал это, чтобы побудить вас поспорить на что-нибудь, чтобы мне было интереснее сравнительное тестирование провести. Задача я бы не сказал, чтобы популярная, поэтому пока не решил, стоит ли этим заниматься.

    Статью в Википедии я и раньше видел, этот оборот (про N#k8SjZc9Dxk2) не понял, честно говоря (источник там не указан). В английской версии этой фразы нет. Если вы поняли о чем там речь или знаете где можно про прочитать, подскажите.

    Так что битовые маски не ЕЩЕ быстрее, а просто быстрее. Ну а так как битовых масок в 1С нет, то соответствие а последнем цикле и играет роль маски — позволяет не проверять не существующие дуги.

    Reply
  18. Garykom

    (17) ildarovich, понимаете мне это тестирование совершенно неинтересно.

    Потому что прекрасно понимаю что при разных исходных данных будет разный результат.

    Про N#k8SjZc9Dxk2 это то что вы и сделали по сути.

    Просто за счет «Если Пути[Узел2.Ключ][Узел1.Ключ] <> Неопределено Тогда» исключили из алгоритма обработку несуществующих ребер.

    Если число ребер во много раз больше чем число вершин эта доработка за счет лишнего действия (проверка по условию в выниманием значения из структур и сравнение) даст замедление.

    А битовые маски это то что и сделали (доп условие перед 3-м циклом) только проверка очень быстрая с помощью бинарных операций.

    Бинарные операции в 1С реализуемы через числа и деление нацело с остатком (%).

    http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%A­4%D0%BB%D0%BE%D0%B9%D0%B4%D0%B0

    ЗЫ при операциях с получением данных из памяти, в случае массива нужное значение (адрес ячейки) получается просто умножением, в случае разных коллекций (в т.ч. структура) используются «ключи»

    получение данных «по ключу» медленнее, так как нужно преобразовать значение ключа в адрес ячейки со значением

    но это существенно сказывается только с большими наборами данных

    ЗЗЫ еще тонкость с 1С, когда запускают тестирование из «конфигуратора через отладку», и напрямую запускают «режим предприятия»

    в этом случае отладчик слегка портит результаты

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *