В предыдущей статье "Минимализмы" в итоге было приведено двадцать одно решение. Просматривать статью и дрбавлять новые решения стало неудобно. Поэтому следующая серия решений оформлена в виде отдельной публикации. Для удобства более новые решения теперь помещаются в начало текста.
38. Удаление повторяющихся символов в начале или конце текста
http://www.forum.mista.ru/topic.php?id=765792
Функции СокрЛ, СокрП, СокрЛП удаляют любое количество пробелов в начале или конце строки. Этим можно воспользоваться, чтобы удалить другие начальные или конечные повторяющиеся символы, заменив их на пробел, а затем выполнив обратную замену. Например, чтобы удалить любое количество точек в конце строки, можно воспользоваться выражением:
СтрЗаменить(СокрП(СтрЗаменить(ИсходнаяСтрока, ".", " ")), " ", ".")
Правда, если исходная строка изначально содержит пробелы, то результат будет неправильным. В этом случае предварительно требуется заменить пробелы каким-либо редким сочетанием символов, а после преобразования выполнить обратную замену.
СтрЗаменить(СтрЗаменить(СокрП(СтрЗаменить(СтрЗаменить(ИсходнаяСтрока, " ", Символы.НПП), ".", " ")), " ", "."), Символы.НПП, " ")
37. Генератор вариантов формул
http://forum.infostart.ru/forum86/topic146521/
В данной задаче требуется сформировать список строк, каждая из которых будет являться вариантом формулы, содержащей аргументы X1, X2, ..., Xmax, знаки арифметических операций и скобки. Этот список затем можно использовать для решений задач типа "расставьте знаки арифметических операций и скобки между заданными числами, чтобы результат вычислений давал заданное число". Функция, решающая данную задачу, может иметь следующий вид:
Функция СписокФормул(От, До)
Перем Результат, Сч, Оп; // для безопасности
Результат = Новый СписокЗначений;
Если От >= До Тогда
Результат.Добавить("X" + От);
Результат.Добавить("( - X" + От + ")")
Иначе
Для Сч = От По До - 1 Цикл
ПравыеЧасти = СписокФормул(Сч + 1, До); // для скорости
Для каждого Слева Из СписокФормул(От, Сч) Цикл
Для каждого Справа Из ПравыеЧасти Цикл
Для Оп = 1 По 3 Цикл
Результат.Добавить("(" + Слева + " " + Сред("+*/", Оп, 1) + " " + Справа + ")")
КонецЦикла
КонецЦикла
КонецЦикла
КонецЦикла
КонецЕсли;
Возврат Результат
КонецФункции // СписокФормул()
Логика тут такая: Цепочка аргументов разными способами разбивается на две части: операцией между первым и вторым, вторым и третьим, третьим и четвертым аргументами и так далее. Варианты формул для левой и правой части цепочки вычисляются рекурсивно. Затем варианты комбинируются, конкатенируясь с тремя (кроме минуса) разными вариантами соединяющих их операций. Скобки обрамляют результат. Когда диапазон номеров аргументов сужается до одного, возвращается два варианта: Xj и ( - Xj). Знак минус обрабатывается особым образом, поскольку минус может быть унарной операцией.
Например, для четырех аргументов функция выдает 2 160 различных формулы, последняя из которых имеет вид:
(((( - X1) / ( - X2)) / ( - X3)) / ( - X4))
36. Алгоритм перебора всех возможных сочетаний элементов
http://forum.infostart.ru/forum86/topic146528/
На входе массив из элементов: {X1, X2, ..., Xm}. Требуется составить все возможные сочетания элементов без учета размещения: {X1}, {X2}, ..., {Xm}, {X1, X2}, ..., {X1, Xm},...,{X2, Xm},...,{X1, X2, ..., Xm}. Другими словами, требуется получить все возможные подмножества множества элементов, заданного в исходном массиве.
Вот функция, решающая данную задачу:
Функция ВсеПодмножества(Множество)
Ответ = Новый Массив;
Ответ.Добавить(Новый СписокЗначений);
Для ИндексЭлемента = 0 По Множество.Количество() - 1 Цикл
Для ИндексВарианта = 0 По Ответ.Количество() - 1 Цикл
Ответ.Добавить(Ответ[ИндексВарианта].Скопировать());
Ответ[ИндексВарианта].Добавить(Множество[ИндексЭлемента])
КонецЦикла
КонецЦикла;
Возврат Ответ
КонецФункции
Результат функции - массив списков значений, содержащих разные подмножества элементов исходного массива. Массив включает и пустое подмножество (на последнем месте).
35. Выбрать в запросе одну запись из нескольких
http://www.forum.mista.ru/topic.php?id=764178
Имеется таблица, содержащая, например, колонки Ф, К1, К2, К3, К4. Для каждого значения Ф в тоблице может быть несколько записей.
Ф | К1 | К2 | К3 | К4 |
Петя | 1 | 1 | 1 | 1 |
Петя | 2 | 0 | 0 | 0 |
Петя | 3 | 0 | 0 | 0 |
Вася | 5 | 5 | 5 | 5 |
Женя | 4 | 4 | 4 | 4 |
Требуется получить таблицу, включающую по одной (любой) записи для каждого значения Ф.
Ф | К1 | К2 | К3 | К4 |
Петя | 1 | 1 | 1 | 1 |
Вася | 5 | 5 | 5 | 5 |
Женя | 4 | 4 | 4 | 4 |
Несмотря на простоту формлировки, у задачи нет простого решения, если только не использовать коррелированный запрос. С использованием коррелированного запроса решение получается очень простым:
ВЫБРАТЬ РАЗЛИЧНЫЕ Ф, К1, К2, К3, К4
ИЗ Дано
ГДЕ (Ф, К1, К2, К3, К4)
В (ВЫБРАТЬ ПЕРВЫЕ 1 * ИЗ Дано КАК ВСЁ ГДЕ ВСЁ.Ф = Дано.Ф)
При использовании коррелированных запросов нельзя забывать о подводных камнях этого механизма, в частности о том, что он может служить причиной падения производительности запроса. Кроме того, в некоторых версиях файлового варианта условие ГДЕ из-за ошибки платформы 8.2 не срабатывает.
34. Как свернуть таблицу значений в коде, но получить не сумму, а максимум
http://www.forum.mista.ru/topic.php?id=716861
http://www.forum.mista.ru/topic.php?id=760950
С помощью небольшой предварительной обработки таблицы значений можно добиться реализации агрегатных функций минимум, максимум, первые, последние при выполнении свертки. Для этого достаточно обнулить все другие значения в группировках, кроме нужных. Это можно сделать по разному. Например, так
Дано.Сортировать("Поле1, Поле2");
Для ё = 1 По Дано.Количество() - 1 Цикл
Если Дано[ё].Поле1 = Дано[ё - 1].Поле1 И Дано[ё].Поле2 = Дано[ё - 1].Поле2 Тогда
Дано[ё].Поле3 = МИН(Дано[ё].Поле3, Дано[ё - 1].Поле3); // = МАКС(Дано[ё].Поле3, Дано[ё - 1].Поле3);
Дано[ё - 1].Поле3 = 0
КонецЕсли
КонецЦикла;
Дано.Свернуть("Поле1, Поле2", "Поле3");
Здесь Поле1, Поле2 - поля группировки, а Поле3 - поле, в котором ищется минимум в группировках.
Заменив функцию МИН на функцию МАКС, получим агрегатную функцию максимум, если убрать первый оператор в цикле, то будут выбираться все последние записи в группировках, а если в цикле оставить только оператор
Дано[ё].Поле3 = 0
то будут выбираться первые значения в группировках.
В обсуждении http://www.forum.mista.ru/topic.php?id=766103 родился еще более короткий вариант:
Дано.Сортировать("Поле1, Поле2, Поле3 Убыв"); //для МАКС, Дано.Сортировать("Поле1, Поле2, Поле3"); //для МИН
Для ё = 1 По Дано.Количество() - 1 Цикл
Если Дано[ё].Поле1 = Дано[ё - 1].Поле1 И Дано[ё].Поле2 = Дано[ё - 1].Поле2 Тогда
Дано[ё].Поле3 = 0
КонецЕсли
КонецЦикла;
Дано.Свернуть("Поле1, Поле2", "Поле3");
Чтобы не записывать каждый раз конкретные названия полей группировки, можно обобщить приведенный выше фрагмент, задавая строку имен полей группировок:
Дано.Сортировать(ИменаПолейГруппировки);
Для ё = 1 По Дано.Количество() - 1 Цикл
Повтор = Истина;
Для каждого Элемент Из Новый Структура(ИменаПолейГруппировки) Цикл
Повтор = Повтор И (Дано[ё][Элемент.Ключ] = Дано[ё - 1][Элемент.Ключ])
КонецЦикла;
Если Повтор Тогда
Дано[ё][Ресурс] = МИН(Дано[ё][Ресурс], Дано[ё - 1][Ресурс]);
Дано[ё - 1][Ресурс] = 0
КонецЕсли
КонецЦикла;
Дано.Свернуть(ИменаПолейГруппировки, Ресурс);
В принципе, можно обойтись и без сортировки, если использовать соответствие. Для случая группировки по одному полю, нужный фрагмент кода будет иметь вид:
Сток = Новый Соответствие;
Для каждого Строка Из Дано Цикл
Ключ = Строка[ПолеГруппировки];
Если Сток[Ключ] = Неопределено Тогда
Сток[Ключ] = Строка
Иначе
Сток[Ключ][Ресурс] = МИН(Сток[Ключ][Ресурс], Строка[Ресурс]);
Строка[Ресурс] = 0
КонецЕсли
КонецЦикла;
Дано.Свернуть(ПолеГруппировки, Ресурс);
33. Поиск кратчайших путей по алгоритму Флойда-Уоршолла
http://forum.infostart.ru/forum24/topic144086/
Функция, решающая данную задачу:
Функция КратчайшийПутьФлойд(Откуда, Куда, Дуги) Экспорт
// загрузка графа в "соответствие соответствий" так, что Пути[Откуда][Куда] = Длина
Пути = Новый Соответствие;
Для каждого Дуга Из Дуги Цикл
Пути[Дуга.Откуда] = ?(Пути[Дуга.Откуда] = Неопределено, Новый Соответствие, Пути[Дуга.Откуда]);
Пути[Дуга.Откуда][Дуга.Куда] = Дуга.Длина
КонецЦикла;
// три вложенных цикла по узлам графа
Для каждого Узел1 Из Пути Цикл // тогда Узел.Ключ - это одна вершина из полного множества вершин
Для каждого Узел2 Из Пути Цикл
Если Пути[Узел2.Ключ][Узел1.Ключ] <> Неопределено Тогда
Для каждого Узел3 Из Узел1.Значение Цикл // здесь только вершины, связанные с Узел1.Ключ
Длина = Пути[Узел2.Ключ][Узел3.Ключ];
Обход = Пути[Узел2.Ключ][Узел1.Ключ] + Узел3.Значение; // Узел3.Значение == Пути[Узел1.Ключ][Узел3.Ключ]
Пути[Узел2.Ключ][Узел3.Ключ] = ?(Длина = Неопределено, Обход, Мин(Длина, Обход))
КонецЦикла
КонецЕсли
КонецЦикла
КонецЦикла;
// восстановление пути
Путь = Дуги.СкопироватьКолонки("Куда, Длина");
Путь.Добавить().Куда = Откуда;
Путь.Добавить().Куда = Куда;
Путь[1].Длина = Пути[Откуда][Куда];
Для каждого Узел Из Пути Цикл
Если Пути[Откуда][Узел.Ключ] <> Неопределено И Пути[Узел.Ключ][Куда] <> Неопределено
И Пути[Откуда][Узел.Ключ] + Пути[Узел.Ключ][Куда] = Пути[Откуда][Куда] Тогда
ЗаполнитьЗначенияСвойств(Путь.Добавить(), Новый Структура("Куда, Длина", Узел.Ключ, Пути[Откуда][Узел.Ключ]))
КонецЕсли
КонецЦикла;
Путь.Сортировать("Длина");
Возврат Путь
КонецФункции // КратчайшийПуть()
Данное решение иллюстрирует использование соответствия при решении задач на графах. Благодаря этому достигается существенный выигрыш по быстродействию. Например, приведенная функция примерно на 30% опрережает по быстродействию решение задачи поиска пути в московском метро запросом, приведенное в статье "Определение кратчайших путей, критических путей одним запросом".
С другой стороны, и алгоритм Флойда и алгоритм матричного умножения относятся к классу задач "All Pairs Shortest Paths (APSP) problem", то есть для определения расстояния между парой вершин являются избыточными. В данной задаче (для заданной пары вершин) лучше использовать алгоритм Дейкстры.
32. Определение длины строки в запросе методом половинного деления
В исходной статье был продемонстрирован оригинальный алгоритм определения длины строки в запросе. Однако при высокой скорости вычислений выражение для получения длины строки получается чрезвычайно громозким. На каждую возможную длину строки требуется записать вариант "ТОГДА". В приведенном ниже решении этот недостаток устранен:
ВЫБРАТЬ
Стр, Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 0, 512) = "" ТОГДА Х - 1 ИНАЧЕ Х КОНЕЦ КАК Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 1, 512) = "" ТОГДА Х - 2 ИНАЧЕ Х КОНЕЦ КАК Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 3, 512) = "" ТОГДА Х - 4 ИНАЧЕ Х КОНЕЦ КАК Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 7, 512) = "" ТОГДА Х - 8 ИНАЧЕ Х КОНЕЦ КАК Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 15, 512) = "" ТОГДА Х - 16 ИНАЧЕ Х КОНЕЦ КАК Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 31, 512) = "" ТОГДА Х - 32 ИНАЧЕ Х КОНЕЦ КАК Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 63, 512) = "" ТОГДА Х - 64 ИНАЧЕ Х КОНЕЦ КАК Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 127, 512) = "" ТОГДА Х - 128 ИНАЧЕ Х КОНЕЦ КАК Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 255, 512) = "" ТОГДА Х - 256 ИНАЧЕ Х КОНЕЦ КАК Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 511, 512) = "" ТОГДА Х - 512 ИНАЧЕ Х КОНЕЦ КАК Х
ИЗ Дано КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ
Для строки длиной до 1024 символов используется система из 10-ти вложенных запросов. Исходная таблица, кроме самой строки, должна содержать начальное значение поиска Х, равное 1024.
Данный вариант не учитывает в длине строки концевые пробелы. Если это действительно требуется, то придется использовать вот такой вариант:
ВЫБРАТЬ
Стр, Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 0, 1) + "!" = "!" ТОГДА Х - 1 ИНАЧЕ Х КОНЕЦ Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 1, 1) + "!" = "!" ТОГДА Х - 2 ИНАЧЕ Х КОНЕЦ Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 3, 1) + "!" = "!" ТОГДА Х - 4 ИНАЧЕ Х КОНЕЦ Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 7, 1) + "!" = "!" ТОГДА Х - 8 ИНАЧЕ Х КОНЕЦ Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 15, 1) + "!" = "!" ТОГДА Х - 16 ИНАЧЕ Х КОНЕЦ Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 31, 1) + "!" = "!" ТОГДА Х - 32 ИНАЧЕ Х КОНЕЦ Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 63, 1) + "!" = "!" ТОГДА Х - 64 ИНАЧЕ Х КОНЕЦ Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 127, 1) + "!" = "!" ТОГДА Х - 128 ИНАЧЕ Х КОНЕЦ Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 255, 1) + "!" = "!" ТОГДА Х - 256 ИНАЧЕ Х КОНЕЦ Х
ИЗ (ВЫБРАТЬ Стр, ВЫБОР КОГДА ПОДСТРОКА(Стр, Х - 511, 1) + "!" = "!" ТОГДА Х - 512 ИНАЧЕ Х КОНЕЦ Х
ИЗ Дано КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ) КАК ВЗ
31. Подсчет записей, содержащих одинаковые неупорядоченные пары значений
http://www.forum.mista.ru/topic.php?id=760508
Если в некоторой таблице есть два поля, заполняемые в произвольном порядке, то как посчитать число "одинаковых" записей?
К примеру, для таблицы "два любимых блюда"
Блюдо1 | Блюдо2 |
Овсянка | Омлет |
Плов | Борщ |
Омлет | Овсянка |
Пельмени | Шашлык |
Борщ | Плов |
так можно посчитать число различных комбинаций блюд:
Блюдо1 | Блюдо2 | Количество |
Овсянка | Омлет | 2 |
Борщ | Плов | 2 |
Пельмени | Шашлык | 1 |
Собственно, запрос, решающий данную задачу:
ВЫБРАТЬ А, Б, КОЛИЧЕСТВО(*)
ИЗ (ВЫБРАТЬ А, Б ИЗ Дано ОБЪЕДИНИТЬ ВЫБРАТЬ Б, А ИЗ Дано ГДЕ А <> Б) КАК ВЗ
ГДЕ А <= Б
СГРУППИРОВАТЬ ПО А, Б
30. Первый пропущенный артикул
http://www.forum.mista.ru/topic.php?id=759443
Чтобы по максимуму использовать номерную емкость, иногда требуется назначать новому элементу незанятую комбинацию цифр, а для этого определять первый пропущенный артикул.
Идея решения здесь в том, чтобы преобразовать артикулы в числа методом из статьи "Выразить строку как число и строку как дату в запросе", а затем найти первое следующее число, не вошедшее в имеющееся множество.
Для максимизации скорости запрос преобразования в число записан иначе, чем в исходной статье. Кроме того, для "надежности" вместо коррелированного запроса используется группировка, хотя запись при этом также получается более длинной:
ВЫБРАТЬ
ВЫБОР ПОДСТРОКА(Артикул, 6, 1)КОГДА"1"ТОГДА 1 КОГДА"2"ТОГДА 2 КОГДА"3"ТОГДА 3 КОГДА"4"ТОГДА 4 КОГДА"5"ТОГДА 5 КОГДА"6"ТОГДА 6 КОГДА"7"ТОГДА 7 КОГДА"8"ТОГДА 8 КОГДА"9"ТОГДА 9 ИНАЧЕ 0 КОНЕЦ
+ ВЫБОР ПОДСТРОКА(Артикул, 5, 1)КОГДА"1"ТОГДА 1 КОГДА"2"ТОГДА 2 КОГДА"3"ТОГДА 3 КОГДА"4"ТОГДА 4 КОГДА"5"ТОГДА 5 КОГДА"6"ТОГДА 6 КОГДА"7"ТОГДА 7 КОГДА"8"ТОГДА 8 КОГДА"9"ТОГДА 9 ИНАЧЕ 0 КОНЕЦ * 10
+ ВЫБОР ПОДСТРОКА(Артикул, 4, 1)КОГДА"1"ТОГДА 1 КОГДА"2"ТОГДА 2 КОГДА"3"ТОГДА 3 КОГДА"4"ТОГДА 4 КОГДА"5"ТОГДА 5 КОГДА"6"ТОГДА 6 КОГДА"7"ТОГДА 7 КОГДА"8"ТОГДА 8 КОГДА"9"ТОГДА 9 ИНАЧЕ 0 КОНЕЦ * 100
+ ВЫБОР ПОДСТРОКА(Артикул, 3, 1)КОГДА"1"ТОГДА 1 КОГДА"2"ТОГДА 2 КОГДА"3"ТОГДА 3 КОГДА"4"ТОГДА 4 КОГДА"5"ТОГДА 5 КОГДА"6"ТОГДА 6 КОГДА"7"ТОГДА 7 КОГДА"8"ТОГДА 8 КОГДА"9"ТОГДА 9 ИНАЧЕ 0 КОНЕЦ * 1000
+ ВЫБОР ПОДСТРОКА(Артикул, 2, 1)КОГДА"1"ТОГДА 1 КОГДА"2"ТОГДА 2 КОГДА"3"ТОГДА 3 КОГДА"4"ТОГДА 4 КОГДА"5"ТОГДА 5 КОГДА"6"ТОГДА 6 КОГДА"7"ТОГДА 7 КОГДА"8"ТОГДА 8 КОГДА"9"ТОГДА 9 ИНАЧЕ 0 КОНЕЦ * 10000
+ ВЫБОР ПОДСТРОКА(Артикул, 1, 1)КОГДА"1"ТОГДА 1 КОГДА"2"ТОГДА 2 КОГДА"3"ТОГДА 3 КОГДА"4"ТОГДА 4 КОГДА"5"ТОГДА 5 КОГДА"6"ТОГДА 6 КОГДА"7"ТОГДА 7 КОГДА"8"ТОГДА 8 КОГДА"9"ТОГДА 9 ИНАЧЕ 0 КОНЕЦ * 100000 КАК Х
ПОМЕСТИТЬ Числа
ИЗ Дано
ДЛЯ ИЗМЕНЕНИЯ
;
ВЫБРАТЬ
Х,
ЛОЖЬ КАК Свободен
ПОМЕСТИТЬ ЧислаПлюс
ИЗ Числа
ОБЪЕДИНИТЬ
ВЫБРАТЬ
Х + 1,
ИСТИНА
ИЗ Числа
;
ВЫБРАТЬ ПЕРВЫЕ 1
Х КАК ПервыйСвободныйАртикул
ИЗ ЧислаПлюс
СГРУППИРОВАТЬ ПО Х
ИМЕЮЩИЕ КОЛИЧЕСТВО(*) = 1 И МАКСИМУМ(Свободен) = ИСТИНА
Пример приведен для случая шестизначных артикулов.
Обратное преобразование числового артикула в строковый здесь не делается. Это возможно, но большого смысла в этом нет. Преобразование числового артикула в строковый легче сделать вне запроса перед присвоением нового артикула.
29. Количество дней, когда товар был на складе
http://forum.infostart.ru/forum14/topic141252/
Обычный подход к решению этой задачи - рассчитать "остатки на каждый день" и подсчитать дни наличия товара. Предлагается другой подход: найти все интервалы отсутствия товара и вычесть их общую длину из длины периода. Получается следующий запрос:
ВЫБРАТЬ
Движения.Склад,
Движения.Номенклатура,
Движения.Период,
Движения.КоличествоНачальныйОстаток,
Движения.КоличествоКонечныйОстаток
ПОМЕСТИТЬ Движения
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, Секунда, , ) КАК Движения
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Движения.Склад,
Движения.Номенклатура,
Движения.Период,
Движения.КоличествоНачальныйОстаток,
Движения.КоличествоКонечныйОстаток
ПОМЕСТИТЬ ДополненныеДвижения
ИЗ
Движения КАК Движения
ОБЪЕДИНИТЬ
ВЫБРАТЬ РАЗЛИЧНЫЕ
НачальныеНули.Склад,
НачальныеНули.Номенклатура,
&НачалоПериода,
0,
0
ИЗ
Движения КАК НачальныеНули
ОБЪЕДИНИТЬ
ВЫБРАТЬ РАЗЛИЧНЫЕ
КонечныеНули.Склад,
КонечныеНули.Номенклатура,
&КонецПериода,
0,
0
ИЗ
Движения КАК КонечныеНули
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Точки.Склад,
Точки.Номенклатура,
Точки.Период,
МАКСИМУМ(Точки.КоличествоНачальныйОстаток) КАК КоличествоНачальныйОстаток,
МАКСИМУМ(Точки.КоличествоКонечныйОстаток) КАК КоличествоКонечныйОстаток
ПОМЕСТИТЬ ТочкиВремени
ИЗ
ДополненныеДвижения КАК Точки
СГРУППИРОВАТЬ ПО
Точки.Склад,
Точки.Номенклатура,
Точки.Период
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Точки.Склад,
Точки.Номенклатура,
МАКСИМУМ(Слева.Период) КАК НачалоОтсутствия,
Точки.Период
ПОМЕСТИТЬ Интервалы
ИЗ
ТочкиВремени КАК Точки
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТочкиВремени КАК Слева
ПО Точки.Склад = Слева.Склад
И Точки.Номенклатура = Слева.Номенклатура
И (Точки.КоличествоНачальныйОстаток = 0)
И (Слева.КоличествоКонечныйОстаток = 0)
И Точки.Период > Слева.Период
СГРУППИРОВАТЬ ПО
Точки.Склад,
Точки.Номенклатура,
Точки.Период
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ РАЗЛИЧНЫЕ
Точки.Склад,
Точки.Номенклатура,
&КонецПериода,
&НачалоПериода
ИЗ
Движения КАК Точки
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Интервалы.Склад,
Интервалы.Номенклатура,
СУММА(РАЗНОСТЬДАТ(Интервалы.Период, Интервалы.НачалоОтсутствия, СЕКУНДА)) / 60 / 60 / 24 КАК ДнейНаличия
ИЗ
Интервалы КАК Интервалы
СГРУППИРОВАТЬ ПО
Интервалы.Склад,
Интервалы.Номенклатура
Чтобы обработать "особые случаи" (когда в начале или в конце интервала остаток будет нулевым), потребовалось добавить записи - "заглушки" по краям интервала. Чтобы учесть возможность, когда периодов отсутствия не было, потребовалось добавить записи полного интервала, из которых при группировке вычитаются интервалы отсутствия.
Из-за этих особых случаев запрос получился длинным, но по идее он простой. Большим плюсом этого подхода является то, что он замечает движения в течении дня. Если же ориентироваться только на остатки в начале/конце дня, то на складе типа "гардероб" (завезли и тут же продали) товар будет всегда отсутствовать.
Возможно, при небольшом объеме данных необходимости повышать быстродействие, выделяя более редкие интервалы отсутствия товаров, и не возникнет. Тогда можно просуммировать собственно интервалы наличия, что выливается в гораздо более короткий запрос (сразу для дней, но можно переделать и для секунд):
ВЫБРАТЬ
Движения.Период,
Движения.Склад,
Движения.Номенклатура,
Движения.КоличествоКонечныйОстаток
ПОМЕСТИТЬ Движения
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, ДЕНЬ, ДвиженияИГраницыПериода, Склад = &Склад) КАК Движения
;
ВЫБРАТЬ
Интервалы.Склад,
Интервалы.Номенклатура,
СУММА(Интервалы.Интервал) КАК ДнейНаличия
ИЗ
(ВЫБРАТЬ
Было.Склад КАК Склад,
Было.Номенклатура КАК Номенклатура,
РАЗНОСТЬДАТ(Было.Период, МИНИМУМ(Стало.Период), ДЕНЬ) КАК Интервал
ИЗ
Движения КАК Было
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Движения КАК Стало
ПО Было.Склад = Стало.Склад
И Было.Номенклатура = Стало.Номенклатура
И (Было.КоличествоКонечныйОстаток > 0)
И Было.Период < Стало.Период
СГРУППИРОВАТЬ ПО
Было.Период,
Было.Склад,
Было.Номенклатура) КАК Интервалы
СГРУППИРОВАТЬ ПО
Интервалы.Склад,
Интервалы.Номенклатура
28. Движения периодического регистра сведений без повторов
http://forum.infostart.ru/forum26/topic140198/
Часто бывает так, что одно и то же значение устанавливается в периодическом регистре сведений несколько раз подряд. На примере таблицы дат и цен следующий запрос позволяет избавиться от ненужных повторов:
ВЫБРАТЬ
Дано.Дата КАК Дата,
Дано.Цена
ИЗ
Дано КАК Дано
ЛЕВОЕ СОЕДИНЕНИЕ Дано КАК Слева
ПО (Слева.Дата < Дано.Дата)
СГРУППИРОВАТЬ ПО
Дано.Дата,
Дано.Цена
ИМЕЮЩИЕ
ЕСТЬNULL(МАКСИМУМ(Слева.Дата), 0) <> ЕСТЬNULL(МАКСИМУМ(ВЫБОР Слева.Цена КОГДА Дано.Цена ТОГДА Слева.Дата КОНЕЦ), 1)
Или вот еще решение через коррелированный запрос (всегда требуется тестировать производительность таких вариантов!)
ВЫБРАТЬ
Дано.Дата КАК Дата,
Дано.Цена
ИЗ
Дано КАК Дано
ГДЕ
НЕ Дано.Цена В
(ВЫБРАТЬ ПЕРВЫЕ 1
Слева.Цена
ИЗ
Дано КАК Слева
ГДЕ
Слева.Дата < Дано.Дата
УПОРЯДОЧИТЬ ПО
Слева.Дата УБЫВ)
27. Округление в большую сторону
http://www.forum.mista.ru/topic.php?id=752392
Этот прием ранее уже применялся в минимализмах, как часть другого решения, но его можно показать и отдельно, поскольку такие задачи встречаются.
Для округления числа Ч в бОльшую сторону можно применять выражение:
М - Цел(М - Ч)
где М = 999999999 или больше, при необходимости.
В качестве дополнения красивый прием, взятый из чужого кода. Абсолютное значение числа Ч:
Макс(Ч, -Ч)
26. Сравнение двух массивов
http://www.forum.mista.ru/topic.php?id=750240
Задача заключается в том, чтобы сравнить два массива и вывести те их элементы, которые не встречаются в обоих массивах, то есть выполнить операцию исключающего ИЛИ для массивов. С использованием соответствия функция сравнения получается достаточно простой:
Функция ИсключающееИЛИ(Массив1, Массив2)
Результат = Новый Массив;
Повтор = Новый Соответствие;
Для каждого Элемент Из Массив1 Цикл
Повтор[Элемент] = ?(Повтор[Элемент] = Неопределено, Ложь, Истина)
КонецЦикла;
Для каждого Элемент Из Массив2 Цикл
Повтор[Элемент] = ?(Повтор[Элемент] = Неопределено, Ложь, Истина)
КонецЦикла;
Для каждого Элемент Из Повтор Цикл
Если НЕ Элемент.Значение Тогда
Результат.Добавить(Элемент.Ключ)
КонецЕсли
КонецЦикла;
Возврат Результат
КонецФункции
Если в массиве могут быть только даты (в исходной задаче было так), то можно использовать другой принцип, сократив один цикл:
Функция ИсключающееИЛИ1(Массив1, Массив2)
Результат = Новый Массив;
Строка1 = ЗначениеВСтрокуВнутр(Массив1);
Строка2 = ЗначениеВСтрокуВнутр(Массив2);
Для каждого Элемент Из Массив1 Цикл
Если НЕ Найти(Строка2, Формат(Элемент,"ДФ=ггггММддЧЧммсс")) Тогда
Результат.Добавить(Элемент)
КонецЕсли
КонецЦикла;
Для каждого Элемент Из Массив2 Цикл
Если НЕ Найти(Строка1, Формат(Элемент,"ДФ=ггггММддЧЧммсс")) Тогда
Результат.Добавить(Элемент)
КонецЕсли
КонецЦикла;
Возврат Результат
КонецФункции
25. Объединение непересекающихся интервалов в запросе
http://www.forum.mista.ru/topic.php?id=748551
Задача заключается в том, чтобы собрать в один непрерывный интервал все время проживание гостя в гостинице, если между интервалами не было перерывов. То есть для таблицы
НачалоПериода | КонецПериода | Гость |
1.01.2001 | 2.01.2001 | Росс Геллер |
3.01.2001 | 4.01.2001 | Росс Геллер |
7.01.2001 | 8.01.2001 | Росс Геллер |
должно получиться
НачалоПериода | КонецПериода | Гость |
1.01.2001 | 4.01.2001 | Росс Геллер |
7.01.2001 | 8.01.2001 | Росс Геллер |
Это делает следующий запрос, являющийся развитием идей решения задачи 14 из предыдущей статьи на случай интервалов:
ВЫБРАТЬ
Дано.НачалоПериода,
Дано.КонецПериода,
Дано.Гостиница,
Дано.ФизическоеЛицо,
СУММА(РАЗНОСТЬДАТ(Слева.НачалоПериода, Слева.КонецПериода, ДЕНЬ) + 1) КАК Интеграл
ПОМЕСТИТЬ ДаноПлюс
ИЗ
Дано КАК Дано
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Дано КАК Слева
ПО (Слева.НачалоПериода <= Дано.НачалоПериода)
И Дано.Гостиница = ДаноСлева.Гостиница
И Дано.ФизическоеЛицо = ДаноСлева.ФизическоеЛицо
СГРУППИРОВАТЬ ПО
Дано.НачалоПериода,
Дано.КонецПериода,
Дано.Гостиница,
Дано.ФизическоеЛицо
;
ВЫБРАТЬ
МИНИМУМ(Дано.НачалоПериода) КАК НачалоПериода,
МАКСИМУМ(Дано.КонецПериода) КАК КонецПериода,
Дано.Гостиница,
Дано.ФизическоеЛицо
ИЗ
ДаноПлюс КАК Дано
СГРУППИРОВАТЬ ПО
ДОБАВИТЬКДАТЕ(Дано.КонецПериода, ДЕНЬ, -Дано.Интеграл),
Дано.Гостиница,
Дано.ФизическоеЛицо
24. Генерация случайных чисел с логарифмическим распределением
http://www.forum.mista.ru/topic.php?id=747493
Имеющийся в платформе генератор случайных чисел дает возможность смоделировать равномерное распределение случайной величины. Если требуется иной закон распределения, то можно воспользоваться принципом, примененным в нижеследующем решении, где требовалось реализовать довольно экзотическое "логарифмическое" распределение.
Принцип заключается в том, чтобы записать рядом с каждым кандидатом на выбор его вероятность, затем посчитать нарастающий итог вероятностей, сгенерировать обычное равномерно распределенное случайное число и выбрать тот элемент, на отрезок которого в нарастающем итоге будет приходиться равномерно-распределенное случайное число.
Вот необходимые для решения этой задачи функции:
Перем ГСЧ;
Перем МассивФункцииРаспределения Экспорт;
Функция Вероятность(К, Тэта)
Возврат -1 / log(1 - Тэта) * Pow(Тэта, К) / К
КонецФункции
// все вероятности нарастающим итогом, послелнее значение == 1, это будет функция распределения
Функция Распределение(Тэта)
Ответ = ТаблицаВыбора.ВыгрузитьКолонку(0);
// табулируем вероятности
Для ё = 1 По Ответ.Количество() Цикл
Ответ[ё - 1] = Вероятность(ё, Тэта)
КонецЦикла;
ВГраница = Ответ.ВГраница();
// наращиваем итог
Для ё = 1 По ВГраница Цикл
Ответ[ё] = Ответ[ё] + Ответ[ё - 1]
КонецЦикла;
// нужно нормировать, так как число строк в ТЗ ограничено в отличии от случая "классического" логарифмического распределения
Для ё = 0 По ВГраница Цикл
Ответ[ё] = Ответ[ё] / Ответ[ВГраница]
КонецЦикла;
Возврат Ответ
КонецФункции
Функция ЛогарифмическоеСлучайноеЧисло() Экспорт
Икс = ГСЧ.СлучайноеЧисло(0, 4294967295) / 4294967295;
Для ё = 0 По МассивФункцииРаспределения.ВГраница() Цикл
Если Икс <= МассивФункцииРаспределения[ё] Тогда
Возврат ё + 1
КонецЕсли
КонецЦикла
КонецФункции
Процедура Подготовка() Экспорт
МассивФункцииРаспределения = Распределение(Тэта);
КонецПроцедуры
ГСЧ = Новый ГенераторСлучайныхЧисел();
ТаблицаВыбора здесь - это таблица значений, из которой производится выбор элементов.
23. Оптимизация подбора кусков заданного суммарного метража методом ветвей и границ
http://www.forum.mista.ru/topic.php?id=745022&page=1
http://forum.infostart.ru/forum26/topic138663/
Имеется таблица "Метраж", содержащая информацию о количестве кусков и их метраже: индекс куска, количество кусков, размер куска, общий метраж индекса. Требуется при вводе заданного метража подобрать куски, оптимально составляющие заданный метраж. Желательно использовать минимум кусков вообще и минимум разных кусков.
Задача решается методом ветвей и границ. Этот метод заключается в сокращенном переборе вариантов. Сокращение достигается за счет предварительной оценки развиваемого варианта. Если оценка хуже уже полученного рекорда, развитие прекращается. В первую очередь развиваются самые перспективные варианты.
Решение представляет собой две функции. В первой производится подготовка, а во второй рекурсивной функции - собственно решение:
Процедура КнопкаСформироватьНажатие(Кнопка)
РабочаяТаблица = Метраж.Выгрузить();
РабочаяТаблица.ЗаполнитьЗначения(Метраж.Итог("Имеется"), "НужноВзять");
Метраж.ЗагрузитьКолонку(РабочаяТаблица.ВыгрузитьКолонку("НужноВзять"), "НужноВзять");
РабочаяТаблица.ЗаполнитьЗначения(0, "НужноВзять");
РабочаяТаблица.Сортировать("Коэффициент Убыв");
ПодборКусков(РабочаяТаблица, РабочаяТаблица.Количество() - 1, Требуется)
КонецПроцедуры
Процедура ПодборКусков(РабочаяТаблица, ВГраница, ЗаданнаяСумма, УжеВзяли = 0) Экспорт
Если ВГраница < 0 ИЛИ ЗаданнаяСумма < 0 ИЛИ УжеВзяли >= Метраж.Итог("НужноВзять") Тогда //промах
Возврат
ИначеЕсли ЗаданнаяСумма = 0 Тогда //фиксируем решение
Ответ = РабочаяТаблица.Скопировать( , "НомерСтроки, НужноВзять");
Ответ.Сортировать("НомерСтроки");
Метраж.ЗагрузитьКолонку(Ответ.ВыгрузитьКолонку("НужноВзять"), "НужноВзять")
КонецЕсли;
Курсор = РабочаяТаблица[ВГраница];
Для НужноВзять = 0 По Курсор.Имеется Цикл
Курсор.НужноВзять = НужноВзять;
ПодборКусков(РабочаяТаблица, ВГраница - 1, ЗаданнаяСумма - НужноВзять * Курсор.Коэффициент, УжеВзяли + НужноВзять);
Курсор.НужноВзять = 0
КонецЦикла
КонецПроцедуры // ПодборКусков()
22. Объединение данных двух периодических регистров
http://forum.infostart.ru/forum26/topic111009/
Есть два периодических регистра. Первый содержит количества на дату (РегистрКоличество), а второй - состояния на дату (РегистрСостояние). Даты двух регистров независимы друг от друга. Требуется запросом построить третий "регистр", являющийся объединением первых двух, чтобы показывались соответствующие друг другу даты, количества и состояния.
Вот короткое решение с использованием коррелированных запросов (ахтунг!):
ВЫБРАТЬ
ВЫБОР
КОГДА ТаблицаКоличества.Дата > ТаблицаСостояний.Дата
ТОГДА ТаблицаКоличества.Дата
ИНАЧЕ ТаблицаСостояний.Дата
КОНЕЦ КАК Дата,
ТаблицаКоличества.Количество КАК Количество,
ТаблицаСостояний.Состояние КАК Состояние
ИЗ
ТаблицаКоличества КАК ТаблицаКоличества
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТаблицаСостояний КАК ТаблицаСостояний
ПО (ТаблицаСостояний.Дата В
(ВЫБРАТЬ
МАКСИМУМ(ТаблицаСостояний.Дата)
ИЗ
ТаблицаСостояний КАК ТаблицаСостояний
ГДЕ
ТаблицаСостояний.Дата <= ТаблицаКоличества.Дата)
ИЛИ ТаблицаКоличества.Дата В
(ВЫБРАТЬ
МАКСИМУМ(ТаблицаКоличества.Дата)
ИЗ
ТаблицаКоличества КАК ТаблицаКоличества
ГДЕ
ТаблицаКоличества.Дата <= ТаблицаСостояний.Дата))