Рекурсия с недостачей, или Краткая теория графов

Публикация № 1869464 26.05.23

Разработка - Математика и алгоритмы

Как приспособить решение, предложенное в статье «Транзитивное замыкание запросом» (https://infostart.ru/1c/articles/158512/), к неудобной задаче.

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

Есть справочник Укладки. В нем есть табличная часть, состоящая из строк:

ВложеннаяУкладка, Количество

ВложеннаяУкладка – элемент того же справочника Укладки. Во вложенной укладке, в свою очередь, тоже могут быть вложенные укладки, и так далее. Зацикливание вложенности не допускается. С этим у нас строго. Но об этом потом.

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

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

Например, собираемся мы в поход туда, где нет розеток. Совсем нет. Никаких. А потому запасаемся контейнерами с батарейками. Распихиваем по всем карманам всех рюкзаков. А также в аптечки и кошельки. И даже в маникюрный набор. В итоге очень развесистое дерево вложенности получается. Но при этом все контейнеры – это один и тот же элемент справочника Укладки.

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

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

И проблема здесь в том, чтобы правильно посчитать количество. То есть решения, которые просто отмечают факт вложенности, не подходят. Например, хорошо известный прием со связью наборов данных в СКД не годится именно по этой причине.

Но тут нашлась отличная статья «Транзитивное замыкание  запросом». Кратко перескажу суть.

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

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

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

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

Великолепно. Но не годится. То есть прямо так не годится. Надо слегка доработать.

Прежде всего, что там с количеством? Как будто бы все просто. Соединяя кусочки, надо умножать количества в них и записывать результат в объединенный кусок. У переходов в ноль шагов количество равно единице. Кстати, они сами играют роль единицы в арифметике переходов. А заодно и нуля. Такая вот арифметика.

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


 

Ну ладно, воспользуемся полем Количество, раз уж оно и так есть, и объединим  укладки, непосредственно вложенные в одну и ту же укладку:

 

 

Но сюрприз ожидает на этапе соединения кусков. Как только фрагменты AC и CB соединятся в AB, кривой путь от A до B моментально будет перепутан с прямым. То есть нет, здесь еще не будет. Просто разные значения поля Количество не позволят перепутать маршруты.

А вот когда соберется путь через F и G, тут и количество совпадет. В итоге из нашего отчета пропадут два контейнера с батарейками. Ну, или что там у вас.

Таким образом, возникла задача как-то идентифицировать каждый маршрут. Но тут внимательный читатель спросит, а в чем, собственно, проблема? Видно же, что укладки B висят каждая на своей ветке. Раздаем номерки – и все дела. Получаем иерархию уникальных элементов и делаем, как в статье написано. А в конце номерки отнимем, и укладки снова станут одинаковыми. Тогда и посчитаем общее количество.

Вот тут-то нас и поджидает настоящий сюрприз. Неправильное на картинке дерево.

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

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


 

В общем, некому там раздавать номерки. Все пути от A до B не только начинаются в одном месте, но и заканчиваются в одной-единственной точке. Надо как-то накапливать идентификаторы промежуточных укладок и постараться не растерять их по дороге.

Однако ж, идею с раздачей номерков тоже не стоит с ходу отбрасывать. После объединения укладок, вложенных в одну и ту же укладку, действительно получается, что разные экземпляры висят на разных ветках. И у них уже есть уникальные идентификаторы – ссылки на те самые укладки, в которые они вложены.

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

А что насчет поля Код? Если не разрешать пользователям редактировать коды элементов справочника, то и оно сгодится. А как же, уникальная строка из 9 цифр. Или сколько захотите. Очень удобно. В общем, номерки нашлись.

Если не удалось отгородить поле Код от пользователей, можно прямо в запросе предварительно раздать элементам справочника номера с помощью функции АВТОНОМЕРЗАПИСИ(), а затем преобразовать их в строки. В версии платформы 8.3.20 такая возможность была наконец-то реализована. Только надо не забыть добавить к этим строкам разделители, чтобы 1 и 2 не совпали с 12.

В конце концов, можно добавить в элемент справочника реквизит, в который заранее класть УникальныйИдентификатор, но только в виде строки. Например, вот так:

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	
	ЭтоНовыйОбъект = Не ЗначениеЗаполнено(Объект.Ссылка);
	
	Если ЭтоНовыйОбъект Тогда
		
		Объект.УИдСтрока = Строка(Новый УникальныйИдентификатор);
		
	Иначе
		
		Если Не ЗначениеЗаполнено(Объект.УИдСтрока) Тогда
			
			Объект.УИдСтрока = Строка(Новый УникальныйИдентификатор);
		
		КонецЕсли;
	
	КонецЕсли;
	
КонецПроцедуры

Ладно, какие-то строковые идентификаторы нашлись. Осталось придумать, как собирать их по пути. А дело в том, что на этапе соединения кусков каждый маршрут собирается всеми возможными способами. Просто лишние дубли каждый раз выкидываются.

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

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

Однако мы все знаем, что в запросах ограничена длина строк, над которыми выполняются операции сравнения. Делим 1024 на 9 – получилось 113. Очень трудно представить себе реальную укладку с сотней уровней вложенности. Можно считать, что этого вполне хватит, учитывая назначение нашей базы.

Другой вариант – вычислить хэш поля Код каждого элемента справочника, а потом складывать хэши при сборке кусков. Есть опасение, что сумма где-нибудь да совпадет, но нам достаточно различать только те пути, у которых совпадают начала и концы. То есть количество значений резко ограничено, а диапазон превосходит все разумные потребности. Зато никаких проблем с длиной строки, даже гипотетических.

Как в запросе вычислять хэш кода элемента справочника, показано в другой статье того же автора – «Расчет хэш-функции в запросе». Смотрите в ней Пример 1.

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


 

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

Получается, что в этом дереве в параллельных ветках показаны одна и та же укладка C и одна и та же укладка D. И то, что C в одной ветке содержит D, а в другой сама в ней содержится, означает только одно – зацикливание вложенности:


 

Как так получилось? Попробуем реализовать этот пример в базе. Допустим, сначала есть только левая ветка. Пока все хорошо.

Начинаем строить правую – добавляем D в корень дерева. Никаких проблем. Надо только внимательно смотреть, куда направляются стрелочки.

А теперь попытаемся вложить C в D. Возникает зацикливание вложенности, чего конфигурация нам сделать не позволит:


 

После этого останется провести стрелочку от C к B, но только на бумаге. Результат показан выше. А в физическом мире попытка реализовать такую вложенность породит кошмар завхоза, уходящий в бесконечность:

 

 

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

Вернемся к основному примеру. Если на дереве с тремя укладками B открыть ту, что поближе, структура вложенности будет выглядеть, к примеру, так:

 

 

На самом деле это означает, что в физическом мире должно полностью копироваться все содержимое укладки B:

 

 

Как уже не раз было сказано, причина в том, что в базе данных существует только один элемент справочника, описывающий структуру укладки B:

 

 

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

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

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

Специальные предложения

Комментарии
В избранное Подписаться на ответы Сортировка: Древо развёрнутое
Свернуть все
1. sandr13 15 28.05.23 23:40 Сейчас в теме
Хорошая статья, но хотелось бы побольше наглядных схем, а не только текстовых описаний проблемы.
Оставьте свое сообщение

См. также

Все консоли запросов для 1С

Запросы Бесплатно (free)

Список всех популярных обработок.

17.03.2023    10987    kuzyara    66    

109

Обработка результатов запроса произвольными вычисляемыми полями. Обзор некоторых новых функций СКД

Запросы СКД Платформа 1С v8.3 Запросы Система компоновки данных Конфигурации 1cv8 Бесплатно (free)

В данной статье вспомним, как обрабатывать результаты запроса в вычисляемых полях СКД, а также сделаем небольшой обзор на новые функции СКД платформы 8.3.20.

07.02.2023    3228    quazare    7    

35

Идентификатор объекта в запросе. Вы этого хотели?

Запросы Механизмы платформы 1С Платформа 1С v8.3 Запросы Бесплатно (free)

В платформе 8.3.22 появилась возможность получать идентификатор в запросе. Лично я ждал этого давно, но по итогу ждал большего. Что не так?

12.01.2023    12754    dsdred    15    

68

[После] Новогодние задачи 2023

Математика и алгоритмы О жизни Бесплатно (free)

Не желаете ли очередную порцию интересных задач?

03.01.2023    1338    Alxby    18    

7

Практическая шпаргалка по новым возможностям языка запросов 1С

Механизмы платформы 1С Запросы Платформа 1С v8.3 Запросы Конфигурации 1cv8 Бесплатно (free)

В предлагаемой статье решил привести примеры применения новых возможностей языка запросов 1С, начиная с версии платформы 8.3.20.

21.11.2022    16274    quazare    34    

113

Новые возможности языка запросов в платформе 8.3.20

Запросы Платформа 1С v8.3 Запросы Бесплатно (free)

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

27.09.2022    10557    zeltyr    17    

75

Ускорим проведение в 1С:Управление холдингом

HighLoad оптимизация Запросы Платформа 1С v8.3 1С:Управление холдингом Бесплатно (free)

В 1С:Управление холдингом есть "нехороший" запрос, который съедает значительную часть времени проведения документов. Если его подправить, то проведение заметно ускорится.

10.08.2022    5309    sapervodichka    64    

74

Экспертный кейс. История расследования одного небыстрого закрытия месяца в 1C:ERP. Пример неочевидных путей расследования в виде детективной истории

HighLoad оптимизация Механизмы платформы 1С Запросы Платформа 1С v8.3 1С:ERP Управление предприятием 2 Бесплатно (free)

В данной статье хотим рассказать об одном нашем непростом расследовании, в котором удалось собрать сразу несколько проблем на разных уровнях инфраструктуры заказчика и изначальной методологии ведения учета. Само расследование в какой-то момент стало напоминать детективную историю, с роялями в кустах, ошибками платформы, странным поведением пользователей и магическим поведением хорошо знакомых механизмов. Но мы реалисты, поэтому все проблемы были выявлены и устранены ;)

11.07.2022    5734    it-expertise    27    

57

Кратность в Юанях (CNY) 10 и 1

Запросы Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Обратите внимание на обмены данными с Юанями. Кратность там меняется между 10 и 1, в зависимости от значения курса > 10 или < 10. Т.е. НЕ ВСЕГДА равна 1. А многие разработчики (в том числе и я) грешат, ставя Кратность = 1 по умолчанию в обменах и выгрузках. P.S. Идём на Восток, становимся хитрее.

10.06.2022    9450    sapervodichka    13    

37

Экспертный кейс. Расследование фатального замедления времени расчета себестоимости в 1С:ERP 2

HighLoad оптимизация Механизмы типовых конфигураций Запросы Платформа 1С v8.3 1С:ERP Управление предприятием 2 Бесплатно (free)

При выполнении нагрузочного тестирования информационной системы на базе 1С:ERP для одного из клиентов с целью оценки возможности миграции системы на PostgreSQL и Astra Linux мы столкнулись с неприемлемым увеличением времени выполнения расчета себестоимости. Строго говоря, сценарий тестирования закрытия месяца не был выполнен вообще – он не укладывался в таймаут выполнения теста, 24 часа. По прошествии 18 часов всё ещё шло выполнение операции «Распределение затрат и расчет себестоимости». Более 16 часов выполнялся подэтап “Расчет партий и себестоимости. Этап. Расчет себестоимости: РассчитатьСтоимость”. Всё это время выполнялся запрос, который в текущей инфраструктуре клиента (СУБД MS SQL Server) выполняется чуть более 3 минут на аналогичных данных.

25.03.2022    5878    it-expertise    92    

68

Изменения формата файлов конфигурации (CF) в 8.3.16

Математика и алгоритмы Платформа 1С v8.3 Бесплатно (free)

Дополнение по формату файлов конфигурации (*.cf) в версии 8.3.16.

16.12.2021    3159    fishca    13    

35

Готовые механизмы 1С: ЗУП, представления

Механизмы типовых конфигураций Запросы Платформа 1С v8.3 Сложные периодические расчеты 1С:Зарплата и Управление Персоналом 3.x Бухгалтерский учет Бесплатно (free)

Здесь будет храниться архив запросов, которые могут помочь разработчику правильно строить отчеты и получать данные в 1С: ЗУП. Статью буду периодически дополнять.

03.11.2021    7706    Margo462    19    

91

Работа с SQL. Шаблон общего модуля

Запросы Платформа 1С v8.3 Бесплатно (free)

Шаблон общего модуля для работы с MS/PG SQL из 1С.

21.10.2021    4467    mrChOP93    3    

67

Интересная задача на Yandex cup 2021

Математика и алгоритмы Бесплатно (free)

Мое решение задачи на Yandex cup 2021 (frontend). Лабиринт. JavaScript.

12.10.2021    6825    John_d    73    

46

Как читать чужой код? Часть 3. Разбор и доработка запросов

Запросы Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Во всех вакансиях есть требование - умение читать чужой код. Но ни на одних курсах специально этому не учат. Чтобы устранить это противоречие, пишу данную статью. Рассмотрю случаи, в которых нам необходимо разбирать чужой код, поймём, чей код мы пытаемся разобрать, зачем и, главное, как. В статье описан личный опыт длиною в 18 лет начиная с версии платформы 7.7. Статья будет большой, набираемся терпения). Статья содержит в себе описание сценариев разбора кода, т.е. набор шагов. В статье не получится показать это на практике. Для этого планирую сделать онлайн или оффлайн курс, где на примерах будет показан разбор незнакомого кода. Статья разбита на 4 публикации для удобства изучения.

20.09.2021    5143    biimmap    33    

38

Механизм анализа данных. Кластеризация.

Математика и алгоритмы Анализ учета Платформа 1С v8.3 Анализ и прогнозирование Бесплатно (free)

Подробный разбор, с примером использования, встроенного механизма кластеризации 1С.

31.08.2021    5277    dusha0020    8    

62

Алгоритмы распределения сумм (наивная методика, Алгоритм Кэхэна)

Математика и алгоритмы Россия Бесплатно (free)

Многим встречалась задача распределения суммы и вытекающая из нее проблема округления, каждый решал ее по-своему, все ли способы вам известны?

08.07.2021    5372    con-men    31    

22

Снежинка для запроса

Запросы Россия Бесплатно (free)

В запросах использование * позволяет выбрать все поля. В пакете запросов этот символ мог бы быть очень востребован при обращении к полям таблиц промежуточных результатов, да и при полных выборках тоже. Однако конструктор его не поддерживает, а в тексте его использование может приводить к появлению дублей полей. Реализовать собственный вариант работы с символом позволяет "Схема запроса".

21.06.2021    2490    kalyaka    5    

15

Эффективные приемы разработки

Математика и алгоритмы Рефакторинг и качество кода СКД Платформа 1С v8.3 Бесплатно (free)

На Infostart Meetup Ekaterinburg.Online выступил Сергей Наумов – руководитель центра аналитики и консалтинга WiseAdvice. Сергей поделился с коллегами приемами разработки, которые помогут избежать потенциальных проблем при реализации сложных проектов.

07.04.2021    5302    SergeyN    13    

39

Последний раз про срез последних (на каждую дату в запросе)

Запросы Платформа 1С v8.3 Запросы Бесплатно (free)

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

15.02.2021    36832    randomus    47    

155

Аналог PIVOT в запросе 1С (как выполнить транспонирование таблицы в запросе 1С)

Запросы Платформа 1С v8.3 Бесплатно (free)

В статье показывается простой метод реализации аналога оператора PIVOT в запросе 1С без использования соединений.

12.12.2020    8970    Eugen-S    25    

71

Итоги по объединенной совокупности группировок в запросе

Запросы Платформа 1С v8.3 Бесплатно (free)

Способ формирования итогов в запросе по совокупности группировок, объединенных в единый набор, при помощи функции АВТОНОМЕРЗАПИСИ.

18.11.2020    12471    antonivan    21    

100

Улучшенный конструктор запроса тонкого клиента (Infostart Toolkit)

Запросы Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

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

07.09.2020    8993    Evg-Lylyk    23    

57

«Варп-двигатель» для «среза последних»

Запросы Бесплатно (free)

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

10.08.2020    4687    hobi    52    

26

1С: Документооборот, Data Science и Python

Документооборот и делопроизводство (СЭД) Математика и алгоритмы 1С:Документооборот Бесплатно (free)

В статье рассказывается о создании и обучении модели Data Science на языке Python и интеграции с системой 1С: Документооборот

04.08.2020    5374    Vaganov_Alexey    13    

55

Применение математических достижений в решении сложных задач бизнеса

Математика и алгоритмы Бесплатно (free)

Как правило, самые сложные задачи решаются с точки зрения математики очень легко. Но чтобы найти правильное решение, важно понять бизнес-цель, которую достигает эта задача. О практическом применении математических достижений для эффективного решения сложных задач бизнеса на конференции Infostart Event 2019 Inception рассказал Дмитрий Мишнов.

25.05.2020    6704    Mishnov    17    

22

Проводим по БУ "на лету"

Инструментарий разработчика Запросы Платформа 1С v8.3 1С:ERP Управление предприятием 2 1С:Комплексная автоматизация 2.х Бухгалтерский учет Налоговый учет Бесплатно (free)

В базе ERP и КА есть форма тестировщика, которая автоматически получает из конфигурации полные тексты запросов формирования бухгалтерских проводок выбранного документа, даёт возможность модифицировать запрос и сразу проверить результат.

01.05.2020    9584    sapervodichka    1    

94

JSON в запросах DaJet QL

Запросы Бесплатно (free)

Практические примеры работы с JSON непосредственно в языке запросов. Перенос курсов валют между УТ и БП. Требуется SQL Server 2016 и выше.

24.04.2020    5177    zhichkin    6    

18

Улучшение пооперационного планирования в 1С:ERP 2.4 внешними средствами

Математика и алгоритмы HighLoad оптимизация Бесплатно (free)

Задача построения оптимального производственного расписания требует сравнения тысяч и десятков тысяч вариантов. Выполнять такие вычисления средствами платформы 1С Предприятие нецелесообразно. Как реализовать пооперационное планирование с использованием генетических алгоритмов и параллельных вычислений в докладе на конференции Infostart Event 2019 Inception рассказал генеральный директор компании «ИНТЕХ» Сергей Сафаров.

02.03.2020    8989    ildarovich    8    

53

Нечёткий поиск "ПОДОБНО". Нюансы

Запросы Платформа 1С v8.3 Бесплатно (free)

Заметки о "ПОДОБНО" в языке запросов

23.02.2020    63414    Yashazz    34    

121

Treemapping — способ визуализации данных древовидной структуры. Карта-схема дерева

Математика и алгоритмы Работа с интерфейсом Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

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

18.02.2020    8541    randomus    20    

75

Нумерация строк в запросе методами платформы

Запросы Запросы Конфигурации 1cv8 Казахстан Бесплатно (free)

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

09.01.2020    49318    user602678_maxipunchik    47    

52

Оптимизатор запроса. Часть первая

Запросы Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Работа оптимизатора запроса является ключевой для обработки данных. Знание того, как оптимизатор выстраивает свою стратегию, отлично помогает при построении запросов.

23.12.2019    14115    darkdan77    21    

91