Как стать автором
Обновить

Как найти возможные суммы из N элементов в массиве

Время на прочтение8 мин
Количество просмотров5.9K

Доброго всем дня, меня вдохновила статья "Всё, что вы хотели знать о динамическом программировании, но боялись спросить", за что Вам огромное спасибо.

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

Где я использую этот модуль? Мне была поставлена задача распределения разносортного товара на паллеты, "1" это метр кубический - объем помещающийся на паллет. У меня была мысль раскидать по объему товар 0,25 / 0,5 / 0,75 и логически складывать, 0,5 +0,5 / 0,75 + 0,25, но это породило огромное количество когда , чем дальше, тем сложнее это было.

Для моей задачи выходом стала "Ленивая динамика:" из вышеуказанной статьи.

В этом примере из статьи идет последовательное сложение строк и перебор в цикле с какого элемента начинать сложение - перебор вариантов сложения.

Минус этого метода только в последовательности.
Минус этого метода только в последовательности.

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

Это рабочий код из моей программы, отлаженный.

Таблица000//(Таблица Значений с товаром), 
Эдиал = 1; //=1 метру кубическому	
КоличествоСтрокТаб = Таблица000.Количество();//количество строк	

ОтклонениеВПлюс =Число("0,1");//до 1,1
ОтклонениеВМинус = Число("0,1");//до 0,9
ОптимальнаяРазница = Эдиал;//задаем максимальную разницу
	ОптимальнаяСумма = 0;
//задаем 2 табличные части или можно использовать массивы, по вкусу
	ОптимальнаяТаблицаСтрок = Новый ТаблицаЗначений;     
    ОптимальнаяТаблицаСтрок.Колонки.Добавить("НомерСтрокиП");
	ТаблицаСтрок = Новый ТаблицаЗначений;     
    ТаблицаСтрок.Колонки.Добавить("НомерСтрокиП");

/////////////////////////////////////////////////////////////////////////////////	
//Цикл от первого элемента к последнему
Для СтартСчетчик = 0 По КоличествоСтрокТаб-1 Цикл
  Счетчик = СтартСчетчик;//стартовое смещение
	ТаблицаСтрок.Очистить();
	ТекущаяСумма = 0;	
  Для ТекущийСчетчик = 0 По КоличествоСтрокТаб-1 Цикл //переменная нигде
    //не используется, это клон первого цикла , контроль количества повторений,
    //а первый цикл только задает последовательный обход с кого начинать
	
    Если Счетчик = КоличествоСтрокТаб Тогда  //если уходим за таблицу то в начало
Счетчик = 0;	
КонецЕсли;
    НоваяСтрока = ТаблицаСтрок.Добавить();//постепенно наполняем таблицу
НоваяСтрока.НомерСтрокиП = Счетчик;
ТекущаяСумма = ТекущаяСумма + Таблица000[Счетчик].ОбъемТары;
    //объем Количество "/" Количество помещающее на паллет

    ВнутреннийСчетчик = Счетчик+1;//временная переменная

    Если ВнутреннийСчетчик = КоличествоСтрокТаб Тогда 
      //если уходим за таблицу то в начало
ВнутреннийСчетчик = 0;	
КонецЕсли;

    Пока НЕ ВнутреннийСчетчик = СтартСчетчик Цикл 
      // с каждым углублением количество циклов сокращается
	
Если ТекущаяСумма > Эдиал + ОтклонениеВПлюс Тогда
        //если перебор
Прервать;
КонецЕсли; 

ВариантСуммы = ТекущаяСумма + Таблица000[ВнутреннийСчетчик].ОбъемТары;
Если  ВариантСуммы >= Эдиал - ОтклонениеВМинус И ВариантСуммы <= Эдиал + ОтклонениеВПлюс Тогда
        //если попадает сумма в наш период вычисляем разницу
РазницаСумм = ВариантСуммы - Эдиал;
        Если РазницаСумм < 0 тогда //убираем минусы
РазницаСумм = РазницаСумм * -1;
КонецЕсли;

Если РазницаСумм < ОптимальнаяРазница Тогда
ОптимальнаяРазница = РазницаСумм;
ОптимальнаяСумма = ВариантСуммы;
Для Каждого Стр Из ТаблицаСтрок Цикл
            //записываем цепочку индексов строк
НоваяСтрока = ОптимальнаяТаблицаСтрок.Добавить();
НоваяСтрока.НомерСтрокиП = Стр.НомерСтрокиП;
КонецЦикла;	
          //дополняем из временной переменной
НоваяСтрока = ОптимальнаяТаблицаСтрок.Добавить();
НоваяСтрока.НомерСтрокиП = ВнутреннийСчетчик;

	

Сообщить(Строка(ОптимальнаяСумма));
КонецЕсли;

КонецЕсли;
ВнутреннийСчетчик = ВнутреннийСчетчик +1;
      //если уходим за таблицу то в начало
Если ВнутреннийСчетчик = КоличествоСтрокТаб Тогда
ВнутреннийСчетчик = 0;	
КонецЕсли;
КонецЦикла;//Пока Цикл
Если ТекущаяСумма > Эдиал + ОтклонениеВПлюс Тогда
Прервать;
КонецЕсли;
Счетчик = Счетчик+1;
КонецЦикла;
/////////////////////////////////////////////////////////////////////////////////////
КонецЦикла;
//добавление колонок и очистка данных 
ОднаТаблица = Новый ТаблицаЗначений;
ОднаТаблица = Таблица000.Скопировать();
ОднаТаблица.Очистить();
Для Каждого Стро Из ОптимальнаяТаблицаСтрок Цикл
НоваяСтрока = ОднаТаблица.Добавить();
  //заполнение нужных строк
ЗаполнитьЗначенияСвойств(НоваяСтрока, Таблица000[Стро.НомерСтрокиП]);	
КонецЦикла;

Расскажу о своей задаче в общем:

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

Товары поделены на однотипности, параметры "Фасовка" и "ВидФасовки" за их определение отвечают. В "приходе" не заполняется номер паллета, а вычисляется вышеуказанным методом.

//ОбработкаПроведения(Отказ, Режим)
//Создаем документ с табличной частью, и булево переменной "Приход"
//таким образом мы понимаем что приход, а что расход
Если Приход = Истина Тогда
	// регистр РегистрРаспределениеНаПаллеты Приход
	Движения.РегистрРаспределениеНаПаллеты.Записывать = Истина;
	Для Каждого ТекСтрокаПродукция Из Продукция Цикл
		Движение = Движения.РегистрРаспределениеНаПаллеты.Добавить();
		Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
		Движение.Период = Дата;
		Движение.Приход = Приход;
		Движение.Вариант = Вариант;
		Движение.Номенклатура = ТекСтрокаПродукция.Номенклатура;
		Движение.Характеристика = ТекСтрокаПродукция.Характеристика;
		Движение.НомерПалета = ТекСтрокаПродукция.НомерПалета;
		Движение.Однотипность = ТекСтрокаПродукция.Однотипность;
		Движение.Фасовка = ТекСтрокаПродукция.Фасовка;
		Движение.ВидФасовки = ТекСтрокаПродукция.ВидФасовки;
		Движение.ОбъемТары = ТекСтрокаПродукция.ОбъемТары;
		Движение.КоличествоНаОдинПаллет = ТекСтрокаПродукция.КоличествоНаОдинПаллет;
		Движение.Количество = ТекСтрокаПродукция.Количество;
		Движение.Партия = ТекСтрокаПродукция.Партия;
		Движение.ЕдиницаИзмерения = ТекСтрокаПродукция.ЕдиницаИзмерения;
	КонецЦикла;
Иначе
	// регистр РегистрРаспределениеНаПаллеты Расход
	Движения.РегистрРаспределениеНаПаллеты.Записывать = Истина;
	Для Каждого ТекСтрокаПродукция Из Продукция Цикл
		Движение = Движения.РегистрРаспределениеНаПаллеты.Добавить();
		Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
		Движение.Период = Дата;
		Движение.Приход = Приход;
		Движение.Вариант = Вариант;
		Движение.Номенклатура = ТекСтрокаПродукция.Номенклатура;
		Движение.Характеристика = ТекСтрокаПродукция.Характеристика;
		Движение.НомерПалета = ТекСтрокаПродукция.НомерПалета;
		Движение.Однотипность = ТекСтрокаПродукция.Однотипность;
		Движение.Фасовка = ТекСтрокаПродукция.Фасовка;
		Движение.ВидФасовки = ТекСтрокаПродукция.ВидФасовки;
		Движение.ОбъемТары = ТекСтрокаПродукция.ОбъемТары;
		Движение.КоличествоНаОдинПаллет = ТекСтрокаПродукция.КоличествоНаОдинПаллет;
		Движение.Количество = ТекСтрокаПродукция.Количество;
		Движение.Партия = ТекСтрокаПродукция.Партия;
		Движение.ЕдиницаИзмерения = ТекСтрокаПродукция.ЕдиницаИзмерения;
	КонецЦикла;
КонецЕсли;

Стартовый обход документа "Приход".

&НаСервере
Функция ФункцияОбходаПрихода() Экспорт
Запрос = Новый Запрос;
	Запрос.Текст =
	
	"ВЫБРАТЬ
	|	РегистрРаспределениеНаПаллеты.Период,
	|	РегистрРаспределениеНаПаллеты.Регистратор,
	|	РегистрРаспределениеНаПаллеты.НомерСтроки,
	|	РегистрРаспределениеНаПаллеты.Активность,
	|	РегистрРаспределениеНаПаллеты.ВидДвижения,
	|	РегистрРаспределениеНаПаллеты.Приход,
	|	РегистрРаспределениеНаПаллеты.Вариант,
	|	РегистрРаспределениеНаПаллеты.Номенклатура,
	|	РегистрРаспределениеНаПаллеты.Характеристика,
	|	РегистрРаспределениеНаПаллеты.НомерПалета,
	|	РегистрРаспределениеНаПаллеты.Однотипность,
	|	РегистрРаспределениеНаПаллеты.Фасовка,
	|	РегистрРаспределениеНаПаллеты.ВидФасовки,
	|	РегистрРаспределениеНаПаллеты.ОбъемТары,
	|	РегистрРаспределениеНаПаллеты.КоличествоНаОдинПаллет,
	|	РегистрРаспределениеНаПаллеты.Партия,
	|	РегистрРаспределениеНаПаллеты.МоментВремени,
	|	РегистрРаспределениеНаПаллеты.ЕдиницаИзмерения,
	|	РегистрРаспределениеНаПаллеты.Количество
	|ИЗ
	|	РегистрНакопления.РегистрРаспределениеНаПаллеты КАК РегистрРаспределениеНаПаллеты
	|ГДЕ
	|	РегистрРаспределениеНаПаллеты.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)";
	Выборка = Запрос.Выполнить().Выбрать();
	//Выборка.Следующий();
	Возврат Выборка;
КонецФункции

Функция вычисления следующего паллета для распределения.

&НаСервере
Функция ФункцияМаксимальныйНомерПаллетаПлюсОдин() Экспорт//Следующий
Запрос = Новый Запрос;
	Запрос.Текст =	
	"ВЫБРАТЬ
	|	МАКСИМУМ(РегистрРаспределениеНаПаллеты.НомерПалета) КАК НомерПалета
	|ИЗ
	|	РегистрНакопления.РегистрРаспределениеНаПаллеты КАК РегистрРаспределениеНаПаллеты";
	Выборка = Запрос.Выполнить().Выбрать();
	Выборка.Следующий();
	Если ЗначениеЗаполнено(Выборка.НомерПалета) Тогда
		Возврат Выборка.НомерПалета+1;
	Иначе
		Возврат 1;
	КонецЕсли;
КонецФункции

Далее два запроса на поиск остатков свободного товара, готового к распределению. Параметры "Начало" и "Конец" отвечают за выбираемый объем если требуется. Однотипность - число от 1 и выше.

&НаСервере
Функция ФункцияОстатковСвободныхПоОднотипности(Однотипность, Начало, Конец) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
	"ВЫБРАТЬ
	|	РегистрРаспределениеНаПаллетыОстатки.Номенклатура,
	|	РегистрРаспределениеНаПаллетыОстатки.Характеристика,
	|	РегистрРаспределениеНаПаллетыОстатки.Однотипность КАК Однотипность,
	|	РегистрРаспределениеНаПаллетыОстатки.Фасовка,
	|	РегистрРаспределениеНаПаллетыОстатки.ВидФасовки,
	|	РегистрРаспределениеНаПаллетыОстатки.КоличествоНаОдинПаллет,
	|	РегистрРаспределениеНаПаллетыОстатки.КоличествоОстаток КАК Количество,
	|	РегистрРаспределениеНаПаллетыОстатки.КоличествоОстаток / РегистрРаспределениеНаПаллетыОстатки.КоличествоНаОдинПаллет КАК ОбъемТары,
	|	РегистрРаспределениеНаПаллетыОстатки.ЕдиницаИзмерения,
	|	РегистрРаспределениеНаПаллетыОстатки.Партия
	|ИЗ
	|	РегистрНакопления.РегистрРаспределениеНаПаллеты.Остатки КАК РегистрРаспределениеНаПаллетыОстатки
	|ГДЕ
	|	РегистрРаспределениеНаПаллетыОстатки.Однотипность = &Однотипность
	|	И РегистрРаспределениеНаПаллетыОстатки.КоличествоОстаток / РегистрРаспределениеНаПаллетыОстатки.КоличествоНаОдинПаллет <= &Конец
	|	И РегистрРаспределениеНаПаллетыОстатки.КоличествоОстаток / РегистрРаспределениеНаПаллетыОстатки.КоличествоНаОдинПаллет > &Начало
	|
	|УПОРЯДОЧИТЬ ПО
	|	ОбъемТары УБЫВ";
	Запрос.УстановитьПараметр("Однотипность", Однотипность);
	Запрос.УстановитьПараметр("Начало", Начало);
	Запрос.УстановитьПараметр("Конец", Конец);
Результат = Запрос.Выполнить();
Возврат Результат;
КонецФункции

// по всем остаткам
&НаСервере 
Функция ФункцияОстатковСвободных(Начало, Конец) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	РегистрРаспределениеНаПаллетыОстатки.Номенклатура,
|	РегистрРаспределениеНаПаллетыОстатки.Характеристика,
|	РегистрРаспределениеНаПаллетыОстатки.Однотипность КАК Однотипность,
|	РегистрРаспределениеНаПаллетыОстатки.Фасовка,
|	РегистрРаспределениеНаПаллетыОстатки.ВидФасовки,
|	РегистрРаспределениеНаПаллетыОстатки.КоличествоНаОдинПаллет,
|	РегистрРаспределениеНаПаллетыОстатки.КоличествоОстаток КАК Количество,
|	РегистрРаспределениеНаПаллетыОстатки.КоличествоОстаток / РегистрРаспределениеНаПаллетыОстатки.КоличествоНаОдинПаллет КАК ОбъемТары,
|	РегистрРаспределениеНаПаллетыОстатки.ЕдиницаИзмерения,
|	РегистрРаспределениеНаПаллетыОстатки.Партия
|ИЗ
|	РегистрНакопления.РегистрРаспределениеНаПаллеты.Остатки КАК РегистрРаспределениеНаПаллетыОстатки
|ГДЕ
|	РегистрРаспределениеНаПаллетыОстатки.КоличествоОстаток / РегистрРаспределениеНаПаллетыОстатки.КоличествоНаОдинПаллет <= &Конец
|	И РегистрРаспределениеНаПаллетыОстатки.КоличествоОстаток / РегистрРаспределениеНаПаллетыОстатки.КоличествоНаОдинПаллет > &Начало
|
|УПОРЯДОЧИТЬ ПО
|	ОбъемТары УБЫВ";
Запрос.УстановитьПараметр("Начало", Начало);
Запрос.УстановитьПараметр("Конец", Конец);
Результат = Запрос.Выполнить();
Возврат Результат;
КонецФункции

Функция записи документа.

НомерПалета = ФункцияМаксимальныйНомерПаллетаПлюсОдин();//вышеуказанная
ПеременнаяОбъема = 0;
//цикл по полученной таблице
Для каждого СтрокаТЗ Из ОднаТаблица Цикл
	ПеременнаяОбъема = ПеременнаяОбъема + СтрокаТЗ.ОбъемТары;
	Если ПеременнаяОбъема <= Число("1,05") Тогда 
    //////////////////////////// пока не перебор, записывать			 
НоваяСтрока = НовыйДокумент.Продукция.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТЗ);
НоваяСтрока.НомерПалета = НомерПалета;
////////////////////////////
	Иначе
		Прервать;
	КонецЕсли;
КонецЦикла;
НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
Теги:
Хабы:
Всего голосов 5: ↑3 и ↓2+3
Комментарии20

Публикации

Истории

Работа

Консультант 1С
96 вакансий
Аналитик 1С
8 вакансий
Программист 1С
68 вакансий

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань