АВС-анализ и табличное программирование
Представлен простейший алгоритм решения задачи АВС-анализа. На данном примере продемонстрирован метод табличного программирования, описанный Стивом Макконнеллом в книге «Совершенный код. Мастер-класс».
- Описание
- Подробнее
Описание
Мне бы только
мой крошечный вклад внести
За короткую жизнь сплести
Хотя бы ниточку шёлка.
песня "Шелкопряд", автор Flёur
Когда знаешь — все просто
Николай Павлов, портал "Планета Эксель"
Всем привет!
Книга Стива Макконнелла «Совершенный код. Мастер-класс» является одной из настольных книг разработчика. До сих пор никто не адаптировал алгоритмы и приемы из книги под 1С-программирование. Про рекурсию и циклы все понятно. Про применение табличного программирования, описанного в главе 18 "Табличные методы", уже поймет не каждый. Я привожу примеры табличного программирования для всеобщего понимания такого приема разработки. С помощью этого способа можно элегантно писать алгоритмы: они становятся на порядок меньше, код становится легче масштабировать и адаптировать.
Для примера возьму простейший алгоритм расчета категорий АВС по номенклатуре. В типовых конфигурациях представлен АВС-анализ в виде отчетов. Я демонстрирую другой универсальный вариант решения задачи: при котором пользователю удобно изменять кол-во категорий (от двух до четырех и более), менять диапазоны категорий, а для разработчика описана схема доработки инструмента – легко добавить другие показатели для анализа.
Алгоритм возьму отсюда ABC-анализ: характеристика, особенности и применение. Автор Никита Шуравин:
1. Выгружаем данные для анализа — показатели сумму продаж и прибыль за период. Тут же суммируем общую сумму продаж и общую прибыль.
2. Рассчитываем столбец «Процент, %» — доля в общей выручке и отдельно доля в общей прибыли.
3. Делаем сортировку по долям или по величине показателя — от большего к меньшему. Сначала обрабатываем показатель "Сумма продаж", затем отдельно обрабатываем показатель "Прибыль".
4. Группируем строки с помощью нарастающего итога по столбцу "Процент, %", пока общая сумма по столбцу не приблизится к соответствующему заданному диапазону — сначала для категории А, затем для категории В и т.д.
В классической формулировке задаются три категории А, В и С. Если вы ограничитесь двумя категориями, то получите правило Парето. В общем случае, бывают ситуации, когда разбить на три категории множество объектов не получается — тогда можно выделить 4 категории для анализа. К слову сказать, клиентов я анализировал по 4 категориям в статье Про деньги фрилансера.

5. Сохраняем рассчитанные сведения в регистр сведений и выводим их по необходимости в АРМ или документах.

Пример разработан на платформе 1С:Предприятие 8.3 (8.3.20.2184), на демо-конфигурации "Управление торговлей", редакция 10.3 (10.3.73.1).
Теперь опишу метод табличного программирования, использованный в данном примере — во внешней обработке.
Пример 1. Сравните вывод категорий в список товаров — как это было бы запрограммировано изначально:
Если Не ПустаяСтрока(Категория) Тогда
Если Категория = "A" Тогда
Стр.Ячейки[Кат].ЦветФона = Новый Цвет(142,235,142); //зеленоватый
ИначеЕсли Категория = "B" Тогда
Стр.Ячейки[Кат].ЦветФона = Новый Цвет(255,255,92); //желтоватый
Иначе
Стр.Ячейки[Кат].ЦветФона = Новый Цвет(255,204,153); //красноватый
КонецЕсли;
Иначе
//белый цвет по умолчанию
КонецЕсли;Согласно табличному методу этот код выглядит вот так:
//заранее в модуле формы задаем таблицу соответствий: категория - цвет поля
СоответствиеЦветовКатегорий = Новый Соответствие;
СоответствиеЦветовКатегорий.Вставить("", Новый Цвет(255,255,255));
СоответствиеЦветовКатегорий.Вставить("A", Новый Цвет(142,235,142));
СоответствиеЦветовКатегорий.Вставить("B", Новый Цвет(255,255,92));
СоответствиеЦветовКатегорий.Вставить("Другие", Новый Цвет(255,204,153));
...
//Далее в алгоритме прорисовки пишем такой код
Категория = Выборка[Кат]; //определяем категорию
Стр.Ячейки[Кат].УстановитьТекст(Категория); //выводим в ячейку категорию
ЦветПоля = СоответствиеЦветовКатегорий.Получить(Категория); //определяем цвет поля
//задаем цвет поля
Стр.Ячейки[Кат].ЦветФона = ?(ЦветПоля<>Неопределено, ЦветПоля, СоответствиеЦветовКатегорий.Получить("Другие"));
Пример 2. Использование разрядности для отображения на форме значений процентов. Как это можно запрограммировать обычным способом:
ЭлементыФормы.ТекРезультат.Колонки.ПроцентСуммыПродаж.Формат = Формат("ЧЦ=19; ЧДЦ=" + РазрядностьПроцентов);
ЭлементыФормы.ТекРезультат.Колонки.ПроцентПрибыли.Формат = Формат("ЧЦ=19; ЧДЦ=" + РазрядностьПроцентов);
Как это выглядит с использованием табличного метода:
//Заранее в модуле формы задаем коллекцию колонок Процентов
МассивПроцентов = Новый Массив;
МассивПроцентов.Добавить("ПроцентСуммыПродаж");
МассивПроцентов.Добавить("ПроцентПрибыли");
...
//Далее в необходимом месте прописываем перебор колонок
Для Каждого Процент Из МассивПроцентов Цикл
ЭлементыФормы.ТекРезультат.Колонки[Процент].Формат = Формат("ЧЦ=19; ЧДЦ=" + РазрядностьПроцентов);
КонецЦикла;Пример 3. Как описать проверку на NULL или 0 итоговой суммы показателя? Далее показано как было и как стало:
Если Выборка.Следующий() Тогда
ОбщаяСуммаПродаж = Выборка.СуммаПродаж;
ОбщаяПрибыль = Выборка.Прибыль;
//было
Если ОбщаяСуммаПродаж = NULL ИЛИ ОбщаяСуммаПродаж = 0 Тогда
...
КонецЕсли;
Если ОбщаяПрибыль = NULL ИЛИ ОбщаяПрибыль = 0 Тогда
...
КонецЕсли;
//стало
Если МассивНекорректныхЗначений.Найти(ОбщаяСуммаПродаж)<>Неопределено Тогда
...
КонецЕсли;
Если МассивНекорректныхЗначений.Найти(ОбщаяПрибыль)<>Неопределено Тогда
...
КонецЕсли;
КонецЕсли;
//При этом заранее задаем в модуле формы коллекцию некорректных итоговых значений
МассивНекорректныхЗначений = Новый Массив;
МассивНекорректныхЗначений.Добавить(NULL);
МассивНекорректныхЗначений.Добавить(0);
Для 3-его примера было для двух показателей две проверки — всего четыре. Стало в итоге две проверки.
При этом мы видим повторение проверок. Поэтому мы заводим очередную коллекцию МассивПоказателей и переписываем код следующим образом:
Выборка = Результат.Выбрать();
Если Выборка.Следующий() Тогда
...
//проведем проверку
Для Каждого ИмяПоказателя Из МассивПоказателей Цикл
Если МассивНекорректныхЗначений.Найти(Выборка[ИмяПоказателя])<>Неопределено Тогда
Сообщить("Итоги " + СоответствиеИменПоказателей.Получить(ИмяПоказателя) + " не рассчитались - обратитесь к разработчику.", СтатусСообщения.Информация);
ТекРезультат.Очистить();
Возврат;
Иначе
Сообщить("Итоги " + СоответствиеИменПоказателей.Получить(ИмяПоказателя) + " = " + Выборка[ИмяПоказателя]);
КонецЕсли;
КонецЦикла;
КонецЕсли;
При этом заранее задаем коллекцию:
МассивПоказателей = Новый Массив;
МассивПоказателей.Добавить("СуммаПродаж");
МассивПоказателей.Добавить("Прибыль");
СоответствиеИменПоказателей = Новый Соответствие;
СоответствиеИменПоказателей.Вставить("СуммаПродаж", "по сумме продаж");
СоответствиеИменПоказателей.Вставить("Прибыль", "по прибыли");Пример 4. Сам алгоритм из п.4 выше полностью построен на использовании табличного метода — когда мы заранее задаем коллекции, таблицы соответствия и далее их используем при обходе:
Процедура РассчитатьКатегории()
Для Каждого ИмяДиапазона Из МассивТаблицДиапазонов Цикл
ИмяКолонкиПоказатель = СоответствиеКолонокПоказатель.Получить(ИмяДиапазона);
ИмяКолонкиПроцент = СоответствиеКолонокПроцент.Получить(ИмяДиапазона);
ИмяКолонкиКатегория = СоответствиеКолонокКатегория.Получить(ИмяДиапазона);
СоответствиеГраниц = Новый Соответствие;
МассивГраниц = Новый Массив;
Для Каждого Стр Из ЭтотОбъект[ИмяДиапазона] Цикл
Граница = Стр.До;
МассивГраниц.Добавить(Граница);
СоответствиеГраниц.Вставить(Граница, Стр.Категория);
КонецЦикла;
//теперь все показатели упорядочиваем
ТекРезультат.Сортировать(ИмяКолонкиПоказатель + " Убыв");
...
К = 0;
Сумма = 0;
Для Каждого Граница Из МассивГраниц Цикл
...
Для Инд = К По ТекРезультат.Количество()-1 Цикл
Стр = ТекРезультат[Инд];
Процент = Стр[ИмяКолонкиПроцент];
...
Сумма = Сумма + Процент;
Если Сумма <= Граница Тогда
Стр[ИмяКолонкиКатегория] = СоответствиеГраниц.Получить(Граница);
Иначе
...
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
...
//Заранее в модуле формы задаем коллекции и таблицы соответствия
МассивТаблицДиапазонов = Новый Массив;
МассивТаблицДиапазонов.Добавить("ДиапазоныСуммыПродаж");
МассивТаблицДиапазонов.Добавить("ДиапазоныПрибыли");
СоответствиеИменДиапазонов = Новый Соответствие;
СоответствиеИменДиапазонов.Вставить("ДиапазоныСуммыПродаж", "по сумме продаж");
СоответствиеИменДиапазонов.Вставить("ДиапазоныПрибыли", "по прибыли");
СоответствиеКолонокПроцент = Новый Соответствие;
СоответствиеКолонокПроцент.Вставить("ДиапазоныСуммыПродаж", "ПроцентСуммыПродаж");
СоответствиеКолонокПроцент.Вставить("ДиапазоныПрибыли", "ПроцентПрибыли");
СоответствиеКолонокКатегория = Новый Соответствие;
СоответствиеКолонокКатегория.Вставить("ДиапазоныСуммыПродаж", "КатегорияПоСуммеПродаж");
СоответствиеКолонокКатегория.Вставить("ДиапазоныПрибыли", "КатегорияПоПрибыли");
СоответствиеКолонокПоказатель = Новый Соответствие;
СоответствиеКолонокПоказатель.Вставить("ДиапазоныСуммыПродаж", "ПоказательСуммаПродаж");
СоответствиеКолонокПоказатель.Вставить("ДиапазоныПрибыли", "ПоказательПрибыль");
В публикации представлена внешняя обработка, при этом для хранения категорий в регистре сведений вам потребуется создать регистр сведений (см. рис. 3 в ленте изображений).

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

