Рекурсия в 1С и управление деревом значений




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

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

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

<?php // Полная загрузка сервисных книжек, создан 2025-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='\

99 Comments

  1. sound

    однозначно чтобы понять рекурсию, надо понять рекурсию

    Reply
  2. Evg-Lylyk

    Может так правильней 😉

    Процедура ИзменитьПометкиПодчиненных(Строка)

    Для Каждого Подчиненный Из Строка.Строки Цикл

    Подчиненный.Пометка = пГлавный.Пометка;

    ИзменитьПометкиПодчиненных(Подчиненный);

    КонецЦикла;

    КонецПроцедуры

    Reply
  3. venger

    (1) Это поможет:-))) http://infostart.ru/projects/4618/

    Но, как доказано где-то когда-то и вроде как математически, любую задачу решаемую рекурсивно, можно решить итеративно и наоборот. Причем в 1С, уж в семерке так точно, рекурсия более затратна, чем цикл:-)

    З.Ы. Ниче против статьи и ее автора не имею, просто ремарка:-)

    Reply
  4. YVolohov

    (2) Вобще то да, пара строчек балласта убрано 🙂

    Reply
  5. Арчибальд

    (3)>Но, как доказано где-то когда-то и вроде как математически, любую задачу решаемую рекурсивно, можно решить итеративно и наоборот.

    Вот и скелет доказательства:

    f(n+1)=g(f(n))

    означает просто

    f(n+1)=g(g(g(……g(1)….)

    что реализуется циклом

    f=g(1);

    for i=1 to n loop

    f=g(f);

    endloop

    И далее индукция по n…

    Reply
  6. Evg-Lylyk

    (3) рекурсия более понятна. На счет затратно сомневаюсь :). Хотелось бы увидеть замену рекурсии для варианта автора.

    Reply
  7. venger

    (6) Замерь две функции из первого поста отсюда: http://infostart.ru/forum/forum9/topic9906/messages/

    И убедишься.

    Reply
  8. Evg-Lylyk

    (7) что на счет примера автора

    Reply
  9. venger

    (8) Что насчет замера? Какие результаты, в семерке, в восьмерке?

    Reply
  10. Evg-Lylyk

    (7) Для приведенного примера ИМХО рекурсия не нужна. Для примера автора пожалуйста

    Reply
  11. Evg-Lylyk

    (9) Так можно увидеть вариант автора без рекурсии

    Reply
  12. venger

    (10) Причем тут одно к другому, давайте разбираться последовательно, Вы выразили сомнения в том, что рекурсия затратнее цикла, Вам предложен чистый эксперимент, без инородных влияющих факторов. Каковы результаты?

    Reply
  13. Evg-Lylyk

    (12) В приведенном примере рекурсия не нужна.

    Вы пишите «Причем в 1С, уж в семерке так точно, рекурсия более затратна, чем цикл:-)» т.е. утверждаете что рекурсия более затратна, хотелось бы видеть для варианта автора

    Reply
  14. YVolohov

    Предлагаю простое сравнение, нужно написать программу которая выводит числа от 1 до 100 с помощью цикла и с помощью рекурсии и сделать замер производительности.

    Reply
  15. Арчибальд

    (6, 10) В чем вопрос-то? Как обойти дерево без рекурсии? Или насколько различаются по затратам рекурсивный обход и нерекурсивный?

    Reply
  16. venger

    (14) Чем плох для сравнения этот пример: http://infostart.ru/forum/forum9/topic9906/messages/

    Reply
  17. venger

    (15) Как обойти дерево без рекурсии, а то человек волнуется и все одно и тоже повторяет, своих ошибок признать не хочет:-)))

    Reply
  18. Dimasik2007

    А кто-нибудь замерял глубину стека в 1С?

    Reply
  19. YVolohov

    Итак, с использованием рекурсии:

    Процедура ВывестиЧисло(пЧисло)

    Если пЧисло > 100 Тогда

    Возврат;

    Иначе

    Сообщить(Строка(пЧисло));

    пЧисло = пЧисло + 1;

    ВывестиЧисло(пЧисло);

    КонецЕсли;

    КонецПроцедуры

    ВывестиЧисло(1);

    0,1154 сек.

    Без использования рекурсии

    Для Счетчик = 1 По 100 Цикл

    Сообщить(Строка(Счетчик));

    КонецЦикла;

    0,1118 сек.

    Почти одинаково, но рекурсия все же работает немного быстреее

    Reply
  20. YVolohov

    (19) рекурсия работает немного медленнее

    Reply
  21. Evg-Lylyk

    (16) тем что там рекурсия не нужна. ИМХО когда рекурсия необходима ее замена не будет эффективней.

    Reply
  22. Evg-Lylyk

    (19) Пример жесть!!! :)))) пожалуйста НА ПРИМЕРЕ АВТОРА ТЕМЫ

    Reply
  23. YVolohov

    (22) сейчас попробую

    Reply
  24. Evg-Lylyk

    (17) «Как обойти дерево без рекурсии, а то человек волнуется и все одно и тоже повторяет, своих ошибок признать не хочет:-)))» в чем моя ошибка поясните пожулайста.

    Reply
  25. Арчибальд

    (17)Заводим два списка ссылок: на текущий уровень и на подчиненный. Первоначально первый список содержит сылку на корень, второй пуст.

    Цикл пока не пуст первый список

    Цикл по элементам первого списка

    Тело: пометить подчиненные и загнать их во второй список

    Конец вложенного цикла

    Второй список сделать первым

    Завести второй пустой список

    Конец внешнего списка.

    Reply
  26. YVolohov

    рекурсия 0,092427

    цикл 0,056335

    Но это ни о чем не говорит, да в цикле быстрее, но зато рекурсия дает дополнительные возможности. Если дерево очень большое, или размер дерева неизвестен кроме рекурсии ничего больше использовать просто нельзя.

    Reply
  27. venger

    (21),(22) Значит первое мы определили, при прочих равных рекурсия затратнее цикла, надеюсь Вы согласитесь:-) Причем родители в справочнике тоже дерево, по сути, чтоб Вы не питали иллюзий:-) Я про пример из форума, куда я давал ссылку, а если Вам лень замерить, то с Вами трудно говорить:-) Это раз:-)

    Второе, для того, чтобы мне поиграться с обходом дерева значений, т.к. оно есть только в восьмерке, мне придется дома найти и поставить восьмерку и посмотреть, какие методы и т.п. есть у этого объекта или что оно там, ибо восьмерку в глаза не видел, так что вам придется подождать пока я найду, поставлю, поиграюсь с восьмеркой. Или восьмерошники выдадут быстрее. Так что подождите. Но по поводу затратности рекурсии по сравнению с циклом при прочих равных мы убедились или еще сомневаемся?:-)))

    Reply
  28. Арчибальд

    (26) См. 25 пост ;))

    Reply
  29. YVolohov

    (27) а можно это выразить в коде

    Reply
  30. venger

    (22) +28, о вот выдали быстрее, чем я дома поставил восьмерку:-))) Пост 25-й Вас удовлетворил?:-))

    Reply
  31. YVolohov

    (30) меня не очень, хотелось бы увидеть это в коде

    Reply
  32. Арчибальд

    (29) Ну, ты сказал:)))

    Описания осьмерочного языка под рукой нет.

    Устроит в семерке обход дерева, где корень и ветви имеют тип СписокЗначений, а листья — другой тип?

    Reply
  33. YVolohov

    (32) устроит, только вот как в семерке можно реализовать дерево, там же такого объекта нет ?

    Reply
  34. YVolohov

    (32) на 1cpp что ли ?

    Reply
  35. Evg-Lylyk

    (26) На чем тестил? А часто ли размер дерева известен? 🙂

    (25) Не мерял, но почти уверен рекурсия будет эффективней

    (28) «Значит первое мы определили, при прочих равных рекурсия затратнее цикла, надеюсь Вы согласитесь:-)» нет там совсем другой случай там рекурсия не нужна НА ПРИМЕРЕ АВТОРА ПОЖАЛУЙСТА

    Для примера автора: рекурсию в 4 строки себе представляют все, а заменить рекурсию сложнее

    Полный обход дерева каталогов как раз подходит для рекурсии

    Reply
  36. Душелов

    «Лучше рекурсия чисто в эстетическом плане и в понимании кода, а фактически хуже (т.к. время на запись активации функции и размещения её на стэк всяка медленнее чем обычный jump). Т.к. для данной задачи производительность не критична, лично я голосую за рекурсию.»

    Взято отсюда: http://forum.codenet.ru/showthread.php?t=31862

    Reply
  37. YVolohov

    (35) тестил на http://www.infostart.ru/projects/4059/

    в текущей версии 2.3 используется рекурсия,

    в более старой версии 2.1 используется цикл,

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

    Reply
  38. Арчибальд

    (33) В семерке есть все:)))

    //Корень — это и есть корень

    ТекСписок=СоздатьОбъект(«СписокЗначений»);

    СледСписок=СоздатьОбъект(«СписокЗначений»);

    ТекСписок.ДобавитьЗначение(Корень);

    Пока ТекСписок.ДлинаСписка()<>0 Цикл

    Для й=1 по ТекСписок.ДлинаСписка() Цикл

    Ссылка=ТекСписок.ПолучитьЗначение(й);

    Если ТипЗначенияСтр(Ссылка)<>»СписокЗначений» Тогда

    //Это лист. Делаем с ним, что хотим, типа стамим пометку

    Иначе //Это ветка. Вносим в список след. уровня

    СледСписок.ДобавитьЗначение(Ссылка);

    КонецЕсли;

    КонецЦикла;

    ТекСписок=СледСписок;

    СледСписок=СоздатьОбъект(«СписокЗначений»);

    КонецЦикла;

    Reply
  39. Душелов

    (0) И конструкцию «Для Каждого … Из …» заменить на «Для … По …».

    Это даст хороший прирост скорости.

    Reply
  40. Арчибальд

    (26) Таки можно?

    Reply
  41. luns

    Все это ерунда.

    Конструкции типа:

    Код
    Процедура ИзменитьПометкиПодчиненных(пГлавный)
    
        Подчиненные1 = пГлавный.Строки;
        Если Подчиненные1.Количество() = 0 Тогда
            Возврат;
        КонецЕсли;
    
        // Первый уровень подчиненных
        Для Каждого Подчиненный1 Из Подчиненные1 Цикл
            Подчиненный1.Пометка = пГлавный.Пометка;
            Подчиненные2 = Подчиненный1.Строки;
            Если Подчиненные2.Количество() = 0 Тогда
                Продолжить;
            КонецЕсли;
    
            // Второй уровень подчиненных
            Для Каждого Подчиненный2 Из Подчиненные2 Цикл
                Подчиненный2.Пометка = пГлавный.Пометка;
                Подчиненные3 = Подчиненный2.Строки;
                Если Подчиненные3.Количество() = 0 Тогда
                    Продолжить;
                КонецЕсли;
    
                // Третий уровень подчиненных
                Для Каждого Подчиненный3 Из Подчиненные3 Цикл
                    Подчиненный3.Пометка = пГлавный.Пометка;
                КонецЦикла;
            КонецЦикла;
        КонецЦикла;
    КонецПроцедуры
    

    Показать полностью

    Непонятны и трудночитаемы. Выйгрыш на дереве в 4-5 вложенности будет минимальным, при вложенности 1000 страшно представить себе этот код.

    Во общем наш выбор: рекурсия!

    Reply
  42. Арчибальд

    (41) См. пост 36. И 39 тоже.

    Кодеры, блин…

    Reply
  43. YVolohov

    (39) надо будет попробовать

    (40) дерево значений созданное из списков значений рулит однозначно ))), семерка действительно может все )))

    Reply
  44. luns

    (42) И что? Можете пример для 30 уровней вложенности привести?

    Выигрыш в долю микросекунды на маленьком дереве меня не интересует, если придется потратить десятки минут на то чтобы разобраться в 100 там, где достаточно 10.

    Reply
  45. luns

    в 100 строчка кода имелось ввиду.

    Reply
  46. Evg-Lylyk

    Давайте исходит из примера автора т.к. для 90% приведенных случаев рекурсия не нужна в том числе и в варианте форума от Душелова. Я чуток знаю низкий уровень (assembler) вызов функции это очень мало операций

    push АдресВозврата;

    push ПеременнаяХ;

    Jmp АдресПроцедуры;

    +

    Внутри процедуры

    Одно вычитание (сдвиг стека)

    и возврат

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

    (37) а что делать если число уровней неизвестно

    Reply
  47. Арчибальд

    (44,45) Я ж и привел в 25 и 38. Количество уровней не лимитировано. А авторский текст, процитированный в (41) — жесть, что я попытался выразить в 42 посте…

    ВоооООтоЧемрееееЕчь…

    Reply
  48. YVolohov

    (44) в приведенном примере всего 4 уровня, причем в http://infostart.ru/projects/4059/ где изначально использовалась процедура из (41) размер дерева и структура фиксированы (корень, коллекции, объекты, таблицы). Если дерево больше, я согласен что надо использовать рекурсию. Да собственно и в этом примере она целесообразнее. Но если дерево небольшое, и размер его фиксирован, то не принципиально что использовать.

    Reply
  49. Evg-Lylyk

    Речь о том что рекурсия эффективней в тех случаях когда она реально необходима, пример полный обход дерева каталогов диска

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

    Reply
  50. Душелов

    Что пишет MSDN:

    Рекурсивные процедуры

    Рекурсивная процедура — это процедура, которая вызывает сама себя. Как правило, это не самый эффективный способ написания кода.

    **** Рассмотрение рекурсивных процедур

    — Ограничивающие условия.

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

    — Использование памяти.

    Приложение имеет ограниченный объем пространства для локальных переменных. Каждый раз, когда процедура вызывает саму себя, она использует больше этого пространства для дополнительных копий ее локальных переменных. Если этот процесс будет продолжаться неопределенно долго, он в конечном счете вызовет ошибку StackOverflowException.

    — Эффективность.

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

    — Взаимная рекурсия.

    Можно наблюдать очень низкую производительность или даже бесконечный цикл, если две процедуры вызывают друг друга. Такой макет представляет те же проблемы, что и одна рекурсивная процедура, но это может быть сложнее обнаружить и отладить.

    — Тестирование.

    Если Вы пишете рекурсивную процедуру, необходимо проверить ее очень внимательно, чтобы убедиться в в том, что она всегда удовлетворяет некоторому граничному условию. Следует также убедиться в том, что в результате слишком большого количества рекурсивных вызовов вы не израсходуете всю доступную память.

    Reply
  51. Арчибальд

    (49) Набор «последовательность+Ветвление+цикл» является ДОСТАТОЧНЫМ для любого алгоритма. Это постулат. Поэтому случаев, когда рекурсия НЕОБХОДИМА просто не существует. Другое дело, что рекурсия зачастую элегантней выглядит…

    Reply
  52. Душелов

    Вопрос:

    — Почему C, Pascal при реализации ЛЮБОЙ рекурсивной процедуры требует памяти, линейно возрастающей с вызовом процедур? Насколько я понимаю, в момент вызова процедуры в стек помещаются параметры процедуры. А если их нет? Адрес возврата?

    Ответ:

    — Да. Так или иначе запоминается адрес возврата.

    Локальные переменные ещё.

    Взято тут: http://www.rsdn.ru/forum/decl/2905355.flat.aspx

    Reply
  53. Душелов

    (52) В ряде случаев рекурсия бывает выгоднее.

    http://www.intuit.ru/department/pl/csharp/10/3.html

    Рассматривается решение задачи «ханойская башня». Ее содержательная постановка такова. В одном из буддийских монастырей монахи уже тысячу лет занимаются перекладыванием колец. Они располагают тремя пирамидами, на которых надеты кольца разных размеров.

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

    Беспокоиться о близком конце света не стоит. Задача эта не под силу и современным компьютерам. Число ходов в ней равно 264, а это, как известно, большое число, и компьютер, работающий в сотню миллионов раз быстрее монахов, не справится с этой задачей в ближайшие тысячелетия.

    ….

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

    ….

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

    Reply
  54. Evg-Lylyk

    Согласен с рекомендациями, но сам знаешь что есть случаи когда рекурсия лучший выбор, пример полный обхода дерева. А все эти факторы влияют по разному в разных случаях гдето например «эффективность использования памяти» сходит на нет.

    Reply
  55. Арчибальд

    (53) 264 = 2**64? ;)))

    Reply
  56. YVolohov

    Итак к чему мы пришли:

    вариант приведенный в (41) основан на циклах, выполняется быстрее рекурсии, но не подходит для слишком больших деревьев или деревьев с неизвестным количеством уровней;

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

    вариант предложенный Арчибальдом, построен на циклах, поэтому не имеет недостатков рекурсии, подходит для деревьев любого размера, но код его менее читабельный чем рекурсия, к тому же требует адаптации к 8-ке.

    Reply
  57. Арчибальд

    (53)Там, главное, не перепутать (чтобы кольца в конце оказались на второй, а не на третьей пирамиде). Поэтому цепочку действий оптимальнее «раскручивать» с конца, т.е. рекурсивно. Без рекурсии мы вынуждены работать с начала…

    Reply
  58. trad

    Тема безрекурсивного вывода дерева ФС не раскрыта.

    ps

    Если что, то под древовидным выводом я понимаю такой вывод в котором видна родственная связь элементов

    Reply
  59. Арчибальд

    (58) Рекурсивного вывода, кстати, тоже…

    Reply
  60. Evg-Lylyk

    (56) Осталось только все выводы учесть в шапке статьи как оказалось все не однозначно :)))

    Кстати про недостаток рекурсии «переполнение стека» это происходит при большом уровне вложенности что крайне редко. ИМХО «замены» потребляют не меньше памяти

    Reply
  61. YVolohov

    (60) Адаптирую вариант Арчибальда под 8-ку (если получиться), сделаю замеры производительности по всех вариантах, вот тогда и допишу концовку статьи со сравнением. А пока еще не все ясно.

    Reply
  62. trad

    (59)

    Перем гТекст;

    Процедура ВывестиПодчиненные(Каталог, Отступ=»»)

    Перем Атрибуты;

    Состояние(Каталог);

    _ФС = СоздатьОбъект(«ФС»);

    ИмяФайла = _ФС.НайтиПервыйФайл(Каталог+»*»);

    Пока ПустаяСтрока(ИмяФайла) = 0 Цикл

    Если Лев(ИмяФайла, 1) <> «.» Тогда

    гТекст.ДобавитьСтроку(Отступ+ИмяФайла);

    _ФС.АтрибутыФайла(Каталог+ИмяФайла,,Атрибуты);

    Если Сред(Атрибуты, 4, 1) = «1» Тогда

    ВывестиПодчиненные(Каталог+ИмяФайла+»», Отступ+» «);

    КонецЕсли;

    КонецЕсли;

    ИмяФайла = _ФС.НайтиСледующийФайл();

    КонецЦикла;

    КонецПроцедуры

    Процедура Сформировать()

    гТекст = СоздатьОбъект(«Текст»);

    ВывестиПодчиненные(«C:»);

    гТекст.Показать(«C:»);

    КонецПроцедуры

    Reply
  63. trad

    (59) твой ход

    Reply
  64. Evg-Lylyk

    На 8-ке:

    Процедура КнопкаВыполнитьНажатие(Кнопка)

    ОбходДереваКаталоговРекурсия(«D:DISTRIB»)

    КонецПроцедуры

    Процедура ОбходДереваКаталоговРекурсия(Каталог)

    НайдФайлы = НайтиФайлы(Каталог,»*.*»);

    Для Каждого Файл Из НайдФайлы Цикл

    Состояние (Файл.ПолноеИмя);

    Если Файл.ЭтоКаталог() Тогда

    ОбходДереваКаталоговРекурсия(Файл.ПолноеИмя);

    КонецЕсли;

    КонецЦикла;

    КонецПроцедуры

    Вместо «Состояние (Файл.ПолноеИмя);» можно вставить любой код

    Reply
  65. Арчибальд

    (63) Да не интересно мне. Ясно же, что решаемо, и достаточно просто.

    Reply
  66. orefkov

    В цикл преобразуется только хвостовая рекурсия.

    Остальные виды рекурсий в общем виде пробразуются в «цикл + стек».

    Рассматривая языки более приближенные к процессору, чем 1С (такие как С / С++), стоит отметить, что организация стека при рекурсивном вызове заключается лишь в изменении стекового регистра. А вот организация объекта-стека при безрекурсивном цикле может быть довольно-таки сложна и включать в себя работу с динамической памятью (выделение/перевыделение), копировании объектов в стек и обратно и тп. Так что имхо на С и С++ для нетривиальных рекурсий довольно неплохие шансы побить их безрекурсивные аналоги.

    Reply
  67. Evg-Lylyk

    (0) комментарии к последнему выводу

    «Перейдем к недостаткам. Использование рекурсии с большой глубиной может привести к переполнению стека и ошибке в работе программы. В случае использования рекурсии быстродействие программы уменьшается сравнительно с использованием циклов. Следовательно, если количество уровней вложенности дерева относительно небольшое, а количество строк большое, то целесообразнее использовать циклы.»

    Неверный вывод. Для большой глубины замены рекурии будут также переполнять память.

    «Следовательно, если количество уровней вложенности дерева относительно небольшое, а количество строк большое, то целесообразнее использовать циклы». Один из недостатков рекурсии доп. вызов функции, но он производится только при шаге в глубь.

    (66) да и зачем реализовывать тоже самое что уже реализовано внутри «call» (вызова процедуры)

    Reply
  68. YVolohov

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

    В общем для деревьев с большим количеством строк и малым количеством уровней оптимальнее использовать циклы. Выигрыш в быстродействии.

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

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

    Для деревьев с малым количеством строк и малым количеством уровней нет разницы что использовать.

    Для деревьев с неизвестным заранее количеством уровней можно только использовать рекурсию.

    Reply
  69. Evg-Lylyk

    (68) можно увидеть примеры на которых вы тестировали т.к. я в них сомневаюсь если это «ВывестиЧисла(пЧисло)» то тогда понятно.

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

    Еще пример для рекурсии расчет выражений например:

    «С=A+B; A=10; B=A+7»

    Для обхода дерева с неограниченным количеством уровней как в твоем примере для 8.х никто не привел пример. Как мне кажется я потрачу на это больше часа и точно не будет быстрее твоего варианта.

    А пример 38 омеет очень много операций он не будет быстрым

    Reply
  70. YVolohov

    В конце статьи есть ссылка на обработку на которой я тестировал. Дерево небольшое, около 1000 строк, 4 уровня. Можешь сам попробовать. Там просто нужно заменить процедуру ИзменитьПометкиПодчиненных() на один а затем на другой вариант и посмотреть что будет быстрее. А насчет вывода чисел — так это только пример, для лучшего понимания, практического значения он не имеет.

    Reply
  71. YVolohov

    (70) около 1000 строк было на той конфигурации, на которой я тестировал, на других может быть и больше (в зависимости от количества метаданных и таблиц)

    Reply
  72. Evg-Lylyk

    (71) Проверил. Для замены на 4 ре цикла рекурсия медленне на 30-50%. Это проблема 1С т.к. в любом «нормальном» языке программирования операция установки пометки будет в десятки раз дольше вызова функции. Можно написать что для известного числа уровней цикл быстрее на Х %. Но нужно не забывать о читаемости т.к. на 30000 строк (я просто запустил 10 раз) на моем компе ушло 1,2 сек на рекурсию и 0.89 на циклы.

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

    Reply
  73. YVolohov

    (72) а есть ли зависимость между количеством уровней дерева и быстодействием, или например между количеством строк и быстродействием ? Тоже вопрос открытый.

    Reply
  74. YVolohov

    (72) пожалуй что наиболее доступное дерево с неизвестным заведомо размером это файловая система

    Reply
  75. venger

    Н-да…. Ну ладно, похоже Вы вдовоем друг друга (YVolohov, Evg-Lylyk) уговорите в верности чего угодно:-))) Ну да ладно…. Если Вам интересно и приятно, то почему бы и нет:-)

    Reply
  76. YVolohov

    (75) Человек старался, тестировал, пришел к определенным выводам, причем достаточно интересным. Почему он не заслуживает плюсика?

    Reply
  77. venger

    (76) Вы в этом плане молодец, я не спорю. Но выводы, т.е. ИМХО, чуть позже и плюс тоже, хорошо? А пока я на море ополоснусться (мне пять минут ходу):-)

    Reply
  78. Evg-Lylyk

    (75) Если что то неверно расскажи. А мне плюсов не жалко у меня их много :)))

    Reply
  79. Арчибальд

    (68)

    > Для деревьев с неизвестным заранее количеством уровней можно только использовать рекурсию.

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

    Reply
  80. Арчибальд

    +79 О как! Оказывается и в самой статье появился вывод о неизбежности рекурсии. Это уже прямая ложь, заслуживающая жирного минуса…

    ТаааАквоооОоот…

    Reply
  81. YVolohov

    (79) (80) Публично покаюсь, если сможешь применить этот код к восьмерочному дереву. Лично у меня не получилось.

    Reply
  82. Арчибальд

    (81)К дереву на Лиспе код тоже не применим. Код опровергает утверждение статьи, цитирую

    » Подведем итоги. В результате этого небольшого исследования мы выяснили, что рекурсию целесообразно использовать для работы с деревьями, имеющими большое количество уровней вложенности или деревьями, количество уровней вложенности которых не определено. В последнем случае применение рекурсии – ЕДИНСТВЕННО возможный механизм.»

    По поводу восьмерки. Вы хотите заявить, что восьмерка не позволяет создать ссылку на элемент (ветку) дерева? Или, что приведенный мной алгоритм неуниверсален (годится не для всяких деревьев)? Или, что дерево в восьмерке не такое как все?

    Reply
  83. YVolohov

    //*******************************************

    Процедура Сформировать()

    Корень = СоздатьОбъект(«СписокЗначений»);

    Ветка1 = СоздатьОбъект(«СписокЗначений»);

    Ветка2 = СоздатьОбъект(«СписокЗначений»);

    Для Счетчик = 1 По 10 Цикл

    Корень.ДобавитьЗначение(1);

    Ветка1.ДобавитьЗначение(11);

    Ветка2.ДобавитьЗначение(12);

    КонецЦикла;

    Корень.УстановитьЗначение(4,Ветка1);

    Корень.УстановитьЗначение(6,Ветка2);

    //Корень — это и есть корень

    ТекСписок=СоздатьОбъект(«СписокЗначений»);

    СледСписок=СоздатьОбъект(«СписокЗначений»);

    ТекСписок.ДобавитьЗначение(Корень);

    Пока ТекСписок.РазмерСписка()<>0 Цикл

    Для й=1 по ТекСписок.РазмерСписка() Цикл

    Ссылка=ТекСписок.ПолучитьЗначение(й);

    Если ТипЗначенияСтр(Ссылка)<>»СписокЗначений» Тогда

    Сообщить(Строка(Ссылка));

    Иначе //Это ветка. Вносим в список след. уровня

    СледСписок.ДобавитьЗначение(Ссылка);

    КонецЕсли;

    КонецЦикла;

    ТекСписок=СледСписок;

    СледСписок=СоздатьОбъект(«СписокЗначений»);

    КонецЦикла;

    КонецПроцедуры

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

    Reply
  84. YVolohov

    Протестируй сам. Да, еще функцию ДлинаСписка я заменил на РазмерСписка, а то выбрасывало ошибку, а больше я ничего не менял.

    Reply
  85. Арчибальд

    (83) Ну, конечно. По Иначе нужно в СледСписок заносить не сам список, а его элементы

    Для йй=1 по Ссылка.РазмерСписка() Цикл

    СледСписок.ДобавитьЗначение(Ссылка.ПолучитьЗначение(йй));

    КонецЦикла;

    Прошу пардону. Но минус не сниму.

    Reply
  86. Арчибальд

    А вот плюсик за тестирование выдам…

    Reply
  87. YVolohov

    (85) Вот сейчас действительно заработало ))) Дерево обходит полностью, правда порядок обхода несколько другой чем у рекурсии или циклов.

    Если корень дерева отметить как 1, подчиненные второго уровня как 11, 12, 13, 14 , третьего уровня 111, 112, 113, 114 и т. д. то цикл или рекурсия обходят так:

    1 — 11 — 111 — 112 -113 -114 -12 — 112 — 113 — 114 а этот алгоритм так —

    1 — 11 — 12 — 13 — 14 — 111 — 112 -113 — 114 — 121 — 122 — 123 и т.д.

    Но это не играет особенной роли, если потомок не должен наследовать каких-либо свойств именно от своего предка. При установке или снятии пометки такого не требуется, поскольку все потомки наследуют свойство (состояние пометки) от общего предка (корня). Так что ты прав — альтернативный рекурсии алгоритм существует.

    Reply
  88. YVolohov

    Сейчас попробую сделать то же для восьмерки.

    Reply
  89. Арчибальд

    (87)Ну да, рекурсия по каждой ветке лезет до листьев, а здесь каждый уровень последовательно обходится. Кстати, номер уровня можно фиксировать и записывать свойство, зависящее и от корневого значения, и от номера уровня.

    При рекурсии труднее иметь номер уровня под рукой :))

    Reply
  90. YVolohov

    Процедура ИзменитьПометкиПодчиненных(пГлавный)

    СтрокиДереваА = Новый Массив;

    СтрокиДереваБ = Новый Массив;

    СтрокиДереваА.Добавить(пГлавный);

    Пока НЕ (СтрокиДереваА.Количество() = 0) Цикл

    Для Каждого СтрокаДерева Из СтрокиДереваА Цикл

    СтрокаДерева.Пометка = пГлавный.Пометка;

    Для Каждого СтрокаДереваПодчиненная из СтрокаДерева.Строки Цикл

    СтрокиДереваБ.Добавить(СтрокаДереваПодчиненная);

    КонецЦикла;

    КонецЦикла;

    СтрокиДереваА = СтрокиДереваБ;

    СтрокиДереваБ = Новый Массив;

    КонецЦикла;

    КонецПроцедуры

    А вот и код на восьмерке, все работает, проверял. Не знаю только как это будет выглядеть с позиций быстродействия, нужно будет протестировать.

    Reply
  91. venger

    Просто пару уточнений по статье:

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

    Будет не бесконечный цикл, а бесконечная рекурсия. При бесконеном цикле (ниже пример) — зависания не будет, просто будет крутиться и все, но это не зависание.

    Пока 1=1 Цикл

    КонецЦикла;

    «Рекурсию следует использовать там, где с помощью циклов решить задачу невозможно или нецелесообразно.»

    Нет таких задач, чтобы невозможно. А вот целесообразно, пока видел только пример про Ханойские башни.

    «Если максимальное количество уровней дерева неизвестно или так велико, что использовать вложенные циклы просто неэффективно, код получиться слишком громоздкий, да еще и повторяющийся?»

    Это вроде как понятно, что не сильно уж и громоздкий, а про эффектиность — рекурсия будет всегда затратней по дереву.

    Ну и все последующие утверждения, про циклы и их невозможность использовать в дереве или их большую затратность по сравнению с рекурсией в зависимости от глубины и ширины дерева тоже не верно.

    А по поводу читабельности и более легкого понимания рекурсии.

    Вы пробовали простому пользователю (желательно не шибко умному и не шибко математичному) объяснить циклы, а потом рекурсию? Как думаете, что он быстрее поймет? Да и со студентами также. Циклы сразу понятны, а рекурсия не сразу всем доступна и понятна становится…. Она тяжелей воспринимается в среднем обущающимися, чем понятие цикла…

    Reply
  92. YVolohov

    Ну насчет бесконечного цикла или рекурсии то это не принципиально, можно сказать «бесконечное повторение одного фрагмента кода», суть от этого не меняется.

    Зависание или не зависание — тоже непринципиально. В конечном итоге во время выполнения такой программы нельзя будет выполнять другие программы (отчеты, обработки) и вообще что либо делать.

    Насчет невозможности решения задачи другим путем — согласен, можно найти и другие варианты. А вот целесообразность уже другое дело, это можно прояснить только тестированием.

    Насчет громоздкости кода — попробуйте себе представить код с девятнадцатью вложенными циклами (для обхода двадцатиуровневого дерева).

    Насчет большей затратности циклов по сравнению с рекурсией — то я утверждал как раз обратное. Циклы быстрее рекурсии. И если в дереве сто тысяч элементов и скажем всего три уровня то нужно использовать циклы. Скорость выполнения увеличиться и код будет не очень большим.

    А вот если в дереве три сотни элементов и двадцать уровней то попробуйте обойти все эти уровни с помощью вложенных циклов. Код будет просто кошмарным а выигрыш в скорости выполнения для дерева с тремястами элементами будет неактуальным, поскольку составит ничтожные доли секунды.

    Насчет понимаемости рекурсии то тут я согласен. Циклы понять легче. А вот читабельность кода — другое дело. Сравните размеры последней и предпоследней процедуры в статье.

    Reply
  93. venger

    (92) > Насчет громоздкости кода — попробуйте себе представить код с девятнадцатью вложенными циклами (для обхода двадцатиуровневого дерева).

    Вы так и не поняли, что 19-ть вложенных циклов делать не нужно;-)

    Остальные Ваши возражения, что принципиально, что нет, по другим уточнениям из поста 91-го тоже вызывают улыбку;-)

    Н-да, похоже действительно спорить с Вами бесполезно;-) Сорри, но со статьей не согласен, пока минус…

    Reply
  94. YVolohov

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

    А улыбка это хорошо, полезно для здоровья. )))

    Reply
  95. Арчибальд

    (94)На мой вкус, Вы еще плюс заработали. За то, что спор не перерос в скандал. Вообще-то это подразумевается, но пока, к сожалению, проблематично.

    А за статью минус оставляю.

    Reply
  96. Evg-Lylyk

    (89) «При рекурсии труднее иметь номер уровня под рукой :))» легко перед вызовом процедуры рекурсии увеличить счетчик и его передать в процедуру… все..

    Reply
  97. Altair777

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

    Reply
  98. anig99

    (101) если мне не изменяет память, то в 1с 8 уровни вложенности рекурсии ограничены…

    (0) Плюс не ставлю, так как банально, но и минус тоже, т.к. подробно и встречал 1с прогеров, которые элементарные вещи в программировании не знают.

    Reply
  99. Ish_2

    (108) Статья хороша тем , что позволила выявить позиции участников последующей дискуссии : кто , как и какие аргументы предъявляет.

    Что же касается предмета статьи , то подача материала мне понравилась.

    Reply

Leave a Comment

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