gifts2017

Долой дубли!

Опубликовал Михаил Семенов (Shaman100M) в раздел Программирование - Практика программирования

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

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

Традиционный способ:
1. Сортируем ТЗ по этим колонкам с данными для шапки (назовем их ключами)
2. Перебираем ТЗ и по мере смены ключей создаем новый/записываем предыдущий документ для заполнения
3. После перебора проверяем и записываем документ.

ТЗ.Сортировать("Ключ_1,Ключ_2,...,Ключ_N");
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл 
  Если ТЗ.НомерСтроки = 1 Тогда
    // БЛОК 1 Создать новый документ
    // БЛОК 2 Заполнить заголовок документа
  ИначеЕсли	(ТЗ.Ключ_1 <> ПредЗначение_Ключ_1) ИЛИ
            (ТЗ.Ключ_2 <> ПредЗначение_Ключ_2) ИЛИ
            ...
            (ТЗ.Ключ_N <> ПредЗначение_Ключ_N) Тогда
    // сменились ключи			
    // БЛОК 3 Заполнить подвал документа
    // БЛОК 4 Записать / вывести документ 

    // БЛОК 1 Создать новый документ !дублируем код!
    // БЛОК 2 Заполнить заголовок документа !дублируем код!
  КонецЕсли;
  // БЛОК 5 Заполнение строки спецификации документа

  ПредЗначение_Ключ_1 = ТЗ.Ключ_1; 
  ПредЗначение_Ключ_2 = ТЗ.Ключ_2;
  ...
  ПредЗначение_Ключ_N = ТЗ.Ключ_N;
КонецЦикла;
// БЛОК 3 Заполнить подвал документа !дублируем код!
// БЛОК 4 Записать / вывести документ !дублируем код!


Тот же алгоритм применяется при группировке и выводе данных из ТЗ в печатную форму.

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

Первое, и самое простое, что можно сделать - добавить и перестроить условия в цикле ( комментарии (6) Tarasenkov и (10) Orefkov ) :

ПредЗначение_Ключ_1	= ПолучитьПустоеЗначение();
ПредЗначение_Ключ_2	= ПолучитьПустоеЗначение();
...
ПредЗначение_Ключ_N	= ПолучитьПустоеЗначение();

ТЗ.Сортировать("Ключ_1,Ключ_2,...,Ключ_N");
ТЗ.НоваяСтрока();
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл 
  Если (ТЗ.Ключ_1 <> ПредЗначение_Ключ_1) ИЛИ
       (ТЗ.Ключ_2 <> ПредЗначение_Ключ_2) ИЛИ
       ...
       (ТЗ.Ключ_N <> ПредЗначение_Ключ_N) ИЛИ (ТЗ.НомерСтроки = 1) Тогда
    Если ТЗ.НомерСтроки > 1 Тогда
      // БЛОК 3 Заполнить подвал документа
      // БЛОК 4 Записать / вывести документ
    КонецЕсли;
    Если ТЗ.НомерСтроки = ТЗ.КоличествоСтрок() Тогда
      Прервать;
    КонецЕсли;
    // БЛОК 1 Создать новый документ
    // БЛОК 2 Заполнить заголовок документа
  КонецЕсли;
  // БЛОК 5 Заполнение строки спецификации документа

  ПредЗначение_Ключ_1 = ТЗ.Ключ_1; 
  ПредЗначение_Ключ_2 = ТЗ.Ключ_2;
  ...
  ПредЗначение_Ключ_N = ТЗ.Ключ_N;
КонецЦикла; 
ТЗ.УдалитьСтроку(ТЗ.КоличествоСтрок());


Вариант poppy (комментарий (23)) c избавлением от нагромождения условий и самым простым кодом: достигается вынесением в функцию чтения и сравнения ключей двух соседних записей:

                                        
// ПроверитьСтрокуТаблицы(<ТЗ>,<СписокКлючей>)
// 
// функция проверяет достижение текущей строки ТЗ ее последней записи, 
// или несовпадение значений по списку колонок в текущей и следующей записи, возвращает:
// 1 в случае выполнения условий, 0 - иначе.
// Параметры:
// <ТЗ>             - таблица значений
// <СписокКлючей>   - список значений - идентификаторов сравниваемых колонок

Функция ПроверитьСтрокуТаблицы(ТЗ, СписокКлючей)
  // (отредактировано автором)
  Перем НомСтр;
  Перем М1;
  Перем Колонка;
  НомСтр = ТЗ.НомерСтроки;
  Если НомСтр >= ТЗ.КоличествоСтрок() Тогда
    Возврат 1;
  КонецЕсли;
  Для М1 = -СписокКлючей.РазмерСписка() По -1 Цикл
    Колонка = СписокКлючей.ПолучитьЗначение(-М1);
    Если  ТЗ.ПолучитьЗначение(НомСтр      , Колонка) <>
             ТЗ.ПолучитьЗначение(НомСтр + 1, Колонка) Тогда
      Возврат 1;
    КонецЕсли;
  КонецЦикла;
  Возврат 0;
КонецФункции

Применение функции:
СтрокаКолонок = "Ключ_1,Ключ_2,...,Ключ_N";
СписокКлючей = СоздатьОбъект("СписокЗначений");
СписокКлючей.ИзСтрокиСРазделителями("""" + СтрЗаменить(СтрокаКолонок, ",", """,""") + """");
   	
Флаг = 1;
ТЗ.Сортировать(СтрокаКолонок);
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл 
  Если Флаг = 1 Тогда
    // БЛОК 1 Создать новый документ
    // БЛОК 2 Заполнить заголовок документа
  КонецЕсли;
  // БЛОК 5 Заполнение строки спецификации документа
  Флаг = ПроверитьСтрокуТаблицы(ТЗ, СписокКлючей);
  Если Флаг = 1 Тогда
    // БЛОК 3 Заполнить подвал документа
    // БЛОК 4 Записать / вывести документ
  КонецЕсли;
КонецЦикла;

Просто и понятно.

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

// ИзмененияКлючейТЗ(<ТЗ>,<СтрокаКолонок>,<ИмяКолонкиНач>,<ИмяКонКолонкиКон>)
//
// Процедура выполняет сравнение значений в указанных колонках ТЗ в соседних записях и 
// записывает результат сравнения  в две добавляемые колонки 
//
// ТЗ             - таблица значений
// СтрокаКолонок  - Строка колонок (ключей) через запятую, по которым сравниваются значения между соседними записями
// ИмяКолонкиНач  - Имя добавляемой в ТЗ первой колонки, необязательное 
//   (если не задано, обращение к ней возможно по номеру "1") . 
//   В данную колонку заносится число, результат сравнения текущей и предыдущей записи, 
//   а именно, ближайший номер колонки (по порядку из <СтрокаКолонок>), по которой 
//   значения в указанных записях не совпадают; 0 если несовпадений нет. 
// ИмяКолонкиКон	- Имя добавляемой в ТЗ второй колонки, необязательное 
//   (если не задано, обращение к ней возможно по номеру "1")
//   В данную колонку заносится число, результат сравнения текущей и следующей записи, 
//   а именно, ближайший номер колонки (по порядку из <СтрокаКолонок>), по которой 
//   значения в указанных записях не совпадают; 0 если несовпадений нет. 

Процедура ИзмененияКлючейТЗ(ТЗ,Знач СтрокаКолонок,Знач ИмяКолонкиНач = "",Знач ИмяКолонкиКон = "")
  Перем ИДКолонок[10]; // идентификаторы колонок-ключей 
  Перем Ключи[10];     // значения ключей
  Перем КолКлючей;     // количество ключей          
  Перем М1,М2;
  Перем КолСтрокТЗ;  
  Перем ПромЗнач;
   
  КолСтрокТЗ = ТЗ.КоличествоСтрок();

  Если КолСтрокТЗ > 0 Тогда
    СтрокаКолонок = СтрЗаменить(СтрокаКолонок,",",РазделительСтрок);
    КолКлючей = СтрКоличествоСтрок(СтрокаКолонок);

    // получить идентификаторы колонок
    Для М1 = 1 По КолКлючей Цикл
      ИДКолонок[М1] = СтрПолучитьСтроку(СтрокаКолонок,М1);
    КонецЦикла;

    ТЗ.ВставитьКолонку(ИмяКолонкиНач,1);
    ТЗ.ВставитьКолонку(ИмяКолонкиКон,2);

    ТЗ.Заполнить(0,,,"1,2");

    ТЗ.УстановитьЗначение(1,1,1); 	// начало в 1-й записи
    ТЗ.УстановитьЗначение(КолСтрокТЗ,2,1);	// конец в последней

    // заполнить ключи по первой записи
    Для М1 = 1 По КолКлючей Цикл
      Ключи[М1] = ТЗ.ПолучитьЗначение(1,ИДКолонок[М1]);
    КонецЦикла;

    // начинаем сравнение со второй по последнюю
    Для М2 = 2 По КолСтрокТЗ Цикл

      М1 = 1;
      Пока М1 <=КолКлючей Цикл
        ПромЗнач = ТЗ.ПолучитьЗначение(М2,ИДКолонок[М1]);
        Если ПромЗнач   <> Ключи[М1] Тогда
          ТЗ.УстановитьЗначение(М2 - 1,2,М1); // конец старой группировки в пред записи
          ТЗ.УстановитьЗначение(М2    ,1,М1); // начало новой группировки в текущей записи 
          // обновим оставшиеся ключи 
          Ключи[М1]   = ПромЗнач;
          М1 = М1 + 1;
          Пока М1 <= КолКлючей Цикл
            Ключи[М1] = ТЗ.ПолучитьЗначение(М2,ИДКолонок[М1]);
            М1 = М1 + 1;
          КонецЦикла;
          Прервать;
        КонецЕсли;     
        М1 = М1 + 1;
      КонецЦикла;               
    КонецЦикла;
  КонецЕсли;
КонецПроцедуры                        

Процедура отрабатывает сравнение ключей полностью, с указанием номера несовпадающей колонки-ключа.
Получилось не совсем кратко, вследствие оптимизации, зато использование очень компактное:
СтрокаКолонок = "Ключ_1,Ключ_2,...,Ключ_N";	
ТЗ.Сортировать(СтрокаКолонок);
ИзмененияКлючейТЗ(ТЗ,СтрокаКолонок,"Нач","Кон");
	
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл 
  Если ТЗ.Нач > 0 Тогда
    // БЛОК 1 Создать новый документ 
    // БЛОК 2 Заполнить заголовок документа 
  КонецЕсли;
  // БЛОК 5 Заполнение строки спецификации документа
  Если ТЗ.Кон > 0 Тогда
    // БЛОК 3 Заполнить подвал документа
    // БЛОК 4 Записать документ 
  КонецЕсли;
КонецЦикла;


Оригинальный способ указан int3 (комментарий (3)). Он заключается в создании вспомогательной таблицы индексов, которая представляет из себя исходную ТЗ, свернутую по колонкам-ключам с суммированием количества записей. (изменено оформление).
// СоздатьИндексТЗ(<ТЗ>,<СтрокаКолонок>,<ИмяИндексКолонки>)
// функция возвращает свернутую таблицу значений по указанным колонкам 
// с суммированием количества записей в добавляемой колонке
// параметры: 
// ТЗ               - таблица значений для свертки
// СтрокаКолонок    - строка колонок, разделенных запятыми, для свертки.
// ИмяИндексКолонки - добавляемая колонка №1, в которой суммируется количество записей.

Функция СоздатьИндексТЗ(ТЗ,СтрокаКолонок,Знач ИмяИндексКолонки	= "")
  Перем ТЗИндекс;
  ТЗИндекс  = СоздатьОбъект("ТаблицаЗначений");
  Если ТЗ.КоличествоСтрок()  > 0 Тогда
    ТЗ.Выгрузить(ТЗиндекс,,,СтрокаКолонок);      

    ТЗИндекс.ВставитьКолонку(ИмяИндексКолонки,1);
    ТЗИндекс.Заполнить(1,,,"1");
    ТЗИндекс.Свернуть(СтрокаКолонок,"1");
    ТЗИндекс.Сортировать(СтрокаКолонок,1);
  КонецЕсли;
  Возврат ТЗИндекс;
КонецФункции


Применение таблицы значений - "индекса":
СтрокаКолонок = "Ключ_1,Ключ_2,...,Ключ_N";	
ТЗ.Сортировать(СтрокаКолонок);

ТЗИндекс = СоздатьИндексТЗ(ТЗ,СтрокаКолонок,"Кол");
ТЗИндекс.ВыбратьСтроки();

ТЗ.ВыбратьСтроки();
Пока ТЗИндекс.ПолучитьСтроку()  = 1 Цикл
  // БЛОК 1 Создать новый документ 
  // БЛОК 2 Заполнить заголовок документа 
  Для М1 = 1 По ТЗИндекс.Кол Цикл
    ТЗ.ПолучитьСтроку();
    // БЛОК 5 Заполнение строки спецификации документа
  КонецЦикла;
  // БЛОК 3 Заполнить подвал документа
  // БЛОК 4 Записать документ 
КонецЦикла;


Автор выражает благодарность Poppy, Int3, Tarasenkov, Orefkov за активное участие в статье.

P.S. О быстродействии.
Самый быстрый, - это метод Int3. Он выигрывает у традиционного при средней длине "спецификации" от 50-ти строк ("2% шапок"), или по 1 строке в 98% случаях.
Метод автора дает к традиционному от +50 до +130%.
Но даже сотня процентов дает очень незначительный прирост по времени, - "голые" циклы и условия (сравнивалось на них) на 100т записях прогоняются за секунды, так что красота, удобочитаемость и универсальность кода того ст'оит
 
Изменения от 16.03.2009
 
Еще один метод, с хранением всех новых документов в списке значений и общей записью документов вне цикла

                                        
// ОбщийКлюч(<ТЗ>,<СписокКлючей>)
// 
// функция формирует и возвращает общий строковый ключ по значениям из текущей строки ТЗ, 
// по переданному списку колонок
// Параметры:
// <ТЗ>             - таблица значений
// <СписокКлючей>   - список значений - идентификаторов колонок

Функция ОбщийКлюч(ТЗ, СписокКлючей)
  Перем НомСтр;
  Перем М1;
  Перем ТекКлюч;
  НомСтр = ТЗ.НомерСтроки;
  ТекКлюч = ЗначениеВСтрокуВнутр(  ТЗ.ПолучитьЗначение(НомСтр,
                                                      СписокКлючей.ПолучитьЗначение(1)));
  Для М1 = 2 По СписокКлючей.РазмерСписка()  Цикл
     ТекКлюч = ТекКлюч +  
                 ЗначениеВСтрокуВнутр(   ТЗ.ПолучитьЗначение(НомСтр ,
                                                      СписокКлючей.ПолучитьЗначение(М1)));
  КонецЦикла;
  Возврат 0;
КонецФункции

Применение функции:
СтрокаКолонок = "Ключ_1,Ключ_2,...,Ключ_N";
СписокКлючей = СоздатьОбъект("СписокЗначений");
СписокНовДок = СоздатьОбъект("СписокЗначений");
СписокКлючей.ИзСтрокиСРазделителями("""" + СтрЗаменить(СтрокаКолонок, ",", """,""") + """");
   	
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл 
  ТекКлюч = ОбщийКлюч(ТЗ, СписокКлючей);
  НовДок = СписокНовДок.Получить(ТекКлюч);
  Если ТипЗначенияСтр(НовДок) = "Документ" Тогда
    // БЛОК 1 Создать новый документ в НовДок с использованием СоздатьОбъект()
    // БЛОК 2 Заполнить заголовок документа НовДок
    СписокНовДок.Установить(ТекКлюч, НовДок);
  КонецЕсли;
  // БЛОК 5 Заполнение строки спецификации документа НовДок
КонецЦикла;
Для М1 = 1 По СписокНовДок.РазмерСписка() Цикл
    НовДок = СписокНовДок.ПолучитьЗначение(М1);
      // БЛОК 3 Заполнить подвал документа НовДок
      // БЛОК 4 Записать / вывести документ НовДок
КонецЦикла;


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

См. также

Подписаться Добавить вознаграждение

Комментарии

1. Сhe Burashka (CheBurator) 12.01.08 20:31
неэстетично...
имхается проще и красивше рекурсивную процедуру заюзать...
2. Poppy (poppy) 12.01.08 21:18
Как-то уж очень мудрено...

Самый простой способ избавиться от дублей - использовать процедуры. Ведь они именно для этого придуманы?
3. int3 (int3) 13.01.08 23:22
Если есть желание сэкономить число сравнений, почему бы не построить индекс? Например так:
Код
СтрокаКолонок = "Ключ_1,Ключ_2,...,Ключ_N";   
ТЗ.Сортировать(СтрокаКолонок);
ТЗиндекс = СоздатьОбъект("ТаблицаЗначений");
ТЗ.Выгрузить(ТЗиндекс,,,СтрокаКолонок);
ИндексКолонка = ТЗиндекс.КоличествоКолонок()+1;
ТЗиндекс.КоличествоКолонок(ИндексКолонка);
ТЗиндекс.Заполнить(1,,,ИндексКолонка);
ТЗиндекс.Свернуть(СтрокаКолонок,ИндексКолонка);
ТЗ.ВыбратьСтроки();
РазмерБлока = 0;
НомерБлока = 0;
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
    Если РазмерБлока=0 Тогда
      НомерБлока = НомерБлока + 1;
      РазмерБлока = ТЗиндекс.ПолучитьЗначение(НомерБлока,ИндексКолонка);
        // БЛОК 1 Создать новый документ 
        // БЛОК 2 Заполнить заголовок документа 
    КонецЕсли;
    // БЛОК 5 Заполнение строки спецификации документа
   РазмерБлока = РазмерБлока - 1;
    Если РазмерБлока=0 Тогда
        // БЛОК 3 Заполнить подвал документа
        // БЛОК 4 Записать документ 
    КонецЕсли;
КонецЦикла;
Показать полностью

всяко пошустрее будет
Shaman100M; +1 Ответить 3
4. Tarasenkov (tarasenkov) 14.01.08 09:06
А не проще написать так:
Код
Если (ТЗ.НомерСтроки    = 1) ИЛИ
(ТЗ.Ключ_1<>ПредЗначение_Ключ_1) ИЛИ
(ТЗ.Ключ_2<>ПредЗначение_Ключ_2) ИЛИ
...
(ТЗ.Ключ_N<>ПредЗначение_Ключ_N) Тогда

Если (ТЗ.НомерСтроки    = 1) Тогда
        // БЛОК 1 Создать новый документ !дублируем код!
        // БЛОК 2 Заполнить заголовок документа !дублируем код!
Иначе
        // сменились ключи         
        // БЛОК 3 Заполнить подвал документа
        // БЛОК 4 Записать / вывести документ 
КонецЕсли;

КонецЕсли;
Показать полностью

При этом никакого дублирования кода нет,
и удобочитаемость остается.
5. Tarasenkov (tarasenkov) 14.01.08 09:09
И почему нельзя редактировать комментарии?
Я имел ввиду такой код:

Если (ТЗ.НомерСтроки = 1) ИЛИ
(ТЗ.Ключ_1<>ПредЗначение_Ключ_1) ИЛИ
(ТЗ.Ключ_2<>ПредЗначение_Ключ_2) ИЛИ
...
(ТЗ.Ключ_N<>ПредЗначение_Ключ_N) Тогда
// БЛОК 1 Создать новый документ
// БЛОК 2 Заполнить заголовок документа
Если (ТЗ.НомерСтроки > 1) Тогда
// сменились ключи
// БЛОК 3 Заполнить подвал документа
// БЛОК 4 Записать / вывести документ
КонецЕсли;
КонецЕсли;
6. Tarasenkov (tarasenkov) 14.01.08 09:15
* и предварительного просмотра нет - тоже плохо :(
Итак.
Код
Если (ТЗ.НомерСтроки = 1) ИЛИ
(ТЗ.Ключ_1<>ПредЗначение_Ключ_1) ИЛИ
(ТЗ.Ключ_2<>ПредЗначение_Ключ_2) ИЛИ
...
(ТЗ.Ключ_N<>ПредЗначение_Ключ_N) Тогда
   Если (ТЗ.НомерСтроки > 1) Тогда
      // сменились ключи 
      // БЛОК 3 Заполнить подвал документа
      // БЛОК 4 Записать / вывести документ 
   КонецЕсли;
   // БЛОК 1 Создать новый документ
   // БЛОК 2 Заполнить заголовок документа
КонецЕсли;
Показать полностью


Дублирование записи документа/вывода печатной формы после цикла не так страшно.
Хотя тоже можно избежать добавив в таблицу фиктивную последнюю строку.
Shaman100M; +1 Ответить 5
7. Алексей (begemot) 14.01.08 09:17
Как оригинальный вариант решения имеет право на существование...
Shaman100M; +1 Ответить
8. Михаил Семенов (Shaman100M) 14.01.08 09:57
(1) (2) Рекурсивная, скорее всего, на сравнение и заполнение строк ТЧ. Иногда может получиться громоздко, т.к. нужно хранить / передавать в процедуру(ы) ключи и все, что там необходимо будет сделать. Я попытался реализовать одной вспомогательной универсальной процедурой с четко определенными параметрами.
(3) +1 хороший индекс! Единственное, добавил бы после свертки ТЗИндекс
Код
 ТЗИндекс.Сортировать(СтрокаКолонок); // Сортировка после Свернуть() может сбиться. 
Показать полностью
9. Михаил Семенов (Shaman100M) 14.01.08 10:10
(4) (5) (6) от объединения условий отказался, - можно запутаться в условиях. Блоки 3 и 4 всегда должны идти после блока 5 (для текущего документа), их дублирования условиями не избежать. Если добавлять фиктивную строку, - необходимо проверить/задать уникальные ключи для нее.
10. Александр Орефков (orefkov) 14.01.08 10:56
Я бы это сделал так:
Код
ПредЗначение_Ключ_1   = ПолучитьПустоеЗначение(99);
ПредЗначение_Ключ_2   = ПолучитьПустоеЗначение(99);
...
ПредЗначение_Ключ_N   = ПолучитьПустоеЗначение(99);

ТЗ.Сортировать("Ключ_1,Ключ_2,...,Ключ_N");
ТЗ.НоваяСтрока();
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл 
    Если (ТЗ.Ключ_1<>ПредЗначение_Ключ_1) ИЛИ
      (ТЗ.Ключ_2<>ПредЗначение_Ключ_2) ИЛИ
      ...
      (ТЗ.Ключ_N<>ПредЗначение_Ключ_N) Тогда
      Если ТЗ.НомерСтроки > 1 Тогда
           // БЛОК 3 Заполнить подвал документа
           // БЛОК 4 Записать / вывести документ
      КонецЕсли;
      Если ТЗ.НомерСтроки = ТЗ.КоличествоСтрок() Тогда
         Прервать;
      КонецЕсли;
        // БЛОК 1 Создать новый документ
        // БЛОК 2 Заполнить заголовок документа
    КонецЕсли;
    // БЛОК 5 Заполнение строки спецификации документа
      
    ПредЗначение_Ключ_1   = ТЗ.Ключ_1; 
    ПредЗначение_Ключ_2   = ТЗ.Ключ_2;
    ...
    ПредЗначение_Ключ_N   = ТЗ.Ключ_N;
КонецЦикла;

Показать полностью
Shaman100M; +1 Ответить 6
11. Михаил Семенов (Shaman100M) 14.01.08 11:41
(10) а если ТЗ.ПолучитьЗначение(1,"Ключ_1") = ПолучитьПустоеЗначение() ?
12. Михаил Семенов (Shaman100M) 14.01.08 12:21
Замерил время выполнения, 10т записей
традиционный, (6) (10) 100%

3 ключа, значения от 1 до 100:
(3) от +40% до +50%
мой: от +250 до +300%

3 ключа, значения от 1 до 10:
(3) +400%
мой: +180%

1 ключ, значения от 1 до 10, от 1 до 100
(3) от + 20% до +40%
мой: +130%

Ст'оит ли красота времени?
13. Михаил Семенов (Shaman100M) 14.01.08 12:43
Хотя мож и стоит. Сравнивается с голым циклом с парой-тройкой условий и присваиваний.
14. int3 (int3) 14.01.08 13:57
(8) Согласен, никто не гарантирует порядка после свертки, правда ни разу не сталкивался с такой ситуацией :-/ (Хотя в реальных алгоритмах дополнительную сортировку включаю в код - надежность дороже ;-) )
(12) такой индекс даст прирост производительности на больших объемах данных, т.е. когда сложность условия в цикле превысит накладные расходы на индексирование, т.е. к примеру при десятке ключей на сотне тысяч записей
ну или иначе - при варировании ключевых условий, тут уже выйдет на первое место гибкость :-/
15. int3 (int3) 14.01.08 14:26
> 3 ключа, значения от 1 до 10:
> (3) +400%
> мой: +180%

Кстати, очень интересный результат. На что ушло время?
16. Михаил Семенов (Shaman100M) 14.01.08 15:41
(15) Замерял не в отладчике, а с пом. _GetPerformanceCounter() . Отладчик показывает +100% Максимум времени ушло на Свернуть()
17. Михаил Семенов (Shaman100M) 14.01.08 16:30
(14) при десятке ключей на сотне т: минус 40%
18. int3 (int3) 15.01.08 08:30
(16) хм... Такая картина - несовпадение результатов разных методов замера наверное объясняется "особенностями" работы платформы, в частности уборщика мусора.
(17) т.е. сфера применения у него найдется, и не только в плане "красивости" кода? буду рад, если пригодится :)
когда-то на проклабе обнаружил конкурс на самый быстрый метод удаления строк из ТЗ (правда он к тому времени уже закончился), заинтересовался и написал свой вариант с использованием такого индексирования - занятный вариант получился, но не без "особенностей" :)
19. Александр Орефков (orefkov) 15.01.08 09:57
(11)
Ну, сделай
ключ1 = "bcad7cf3-2fd3-4286-a654-139b37840a79"
20. Михаил Семенов (Shaman100M) 15.01.08 12:49
(18) да, полезный конкурс, столько особенностей при оптимизации обнаружилось, вплоть до наличия запятой в НоваяКолонка() :)
(19) )))) Ну тогда какое-нить другое красивое слово.
21. Михаил Семенов (Shaman100M) 15.01.08 12:53
(18) если не против, добавлю в статью с ссылкой.
23. Poppy (poppy) 16.01.08 11:37
Внесу свои пять копеек в решение задачи "Простой метод исключения дублирования кода в одном из часто используемых алгоритмов."

Код
Функция ПроверитьСтрокуТаблицы(ТЗ, НомерСтроки, СписокКлючей)
   
   Если НомСтр < 1 Тогда
       Возврат 1;
   КонецЕсли;
   
   Если НомСтр >= ТЗ.КоличествоСтрок() Тогда
       Возврат 1;
   КонецЕсли;
   
   Для ии = 1 По СписокКлючей.РазмерСписка() Цикл
      Колонка = СписокКлючей.ПолучитьЗначение(СписокКлючей.РазмерСписка() - ии + 1);
      Если не (ТЗ.ПолучитьЗначение(НомерСтроки, Колонка) = ТЗ.ПолучитьЗначение(НомерСтроки + 1, Колонка)) Тогда
          Возврат 1;
      КонецЕсли;
   КонецЦикла;
   
   Возврат 0;
   
КонецФункции


   СтрокаКолонок = "Ключ_1,Ключ_2,...,Ключ_N";
   
   СписокКлючей = СоздатьОбъект("СписокЗначений");
   СписокКлючей.ИзСтрокиСРазделителями("""" + СтрЗаменить(СтрокаКолонок, ",", """,""") + """");

      ТЗ.Сортировать(СтрокаКолонок);
      
   Флаг = 1;

   ТЗ.ВыбратьСтроки();
   Пока ТЗ.ПолучитьСтроку() = 1 Цикл 
      
      Если Флаг = 1 Тогда
         // БЛОК 1 Создать новый документ
         // БЛОК 2 Заполнить заголовок документа
      КонецЕсли;
      
      // БЛОК 5 Заполнение строки спецификации документа
      
      Флаг = ПроверитьСтрокуТаблицы(ТЗ, ТЗ.НомерСтроки, СписокКлючей);
   
      Если Флаг = 1 Тогда
         // БЛОК 3 Заполнить подвал документа
         // БЛОК 4 Записать / вывести документ
      КонецЕсли;
      
   КонецЦикла;
Показать полностью


Очевидно, что представленный код работает медленнее, чем при индексировании. Но ведь речь идет о простоте и понятности алгоритма и кода? Не так ли?
magics; Shaman100M; +2 Ответить 3
24. Михаил Семенов (Shaman100M) 17.01.08 08:54
25. Poppy (poppy) 17.01.08 11:33
(24)
Я не против.

Можно еще описать решение (6). Считаю, что оно вполне справляется с поставленной в статье задаче.

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

З.Ы. Попробуй длинный комментарий к процедуре ИзмененияКлючейТЗ() сделать в несколько строк, а то он у меня на экран не помещается - неудобно читать статью. :-(
26. Михаил Семенов (Shaman100M) 17.01.08 14:49
(25) насчет (6) ( и (10) ) да, подсознательно фильтровал универсальные, но для полной картины включу. Можно, в принципе и его сделать универсальным, вынеся операции сравнения и присваивания в функции.
По комментарию - разве он у тебя не переносится на след. строки? Хотя, при копировании в конфигуратор лучше, чтобы строки были короткие.
27. Poppy (poppy) 18.01.08 02:45
(26)
> По комментарию - разве он у тебя не переносится на след. строки?

Есть ощущение, что строки внутри конструкции ( code ) ( /code) не переносятся... :( В IE7

Но это претензия скорее к саппорту, чем к тебе. Сделай опиcание ИмяКолонкиНач и ИмяКолонкиКон в несколько строк, возможно, будет лучше...

З.Ы.
Решение описанное в (10) не стоит внимания, потому-что неправильное.
28. Михаил Семенов (Shaman100M) 18.01.08 10:42
(27) Чем оно неправильное? Решения (6) и (10) - одно и тоже, просто (10) дописано до конца, а в (6) код + идея.
29. Poppy (poppy) 18.01.08 16:52
(28)
Да, действительно, решение (10) правильное. Я невнимательно его прочтала. Приношу извинения перед автором за напрасный поклеп. ;)
30. Alexandr (maloi_a) 24.01.08 09:49
(23)
Внесу свою копейку.
В Функции ПроверитьСтрокуТаблицы(ТЗ, СписокКлючей)
надо добавить объявление локальных переменных НомСтр и М1.
Перем НомСтр и М1;
Иначе может поменять глобальные переменные с такими именами.
31. Poppy (poppy) 24.01.08 09:59
(30) Правильное замечание.

В (23) вкралась ошибка. В условиях вместо НомСтр должно быть написано НомерСтроки.

Получается, что локальными переменными нужно объявлять: Перем ии, Колонка.
32. Михаил Семенов (Shaman100M) 24.01.08 10:07
(30) нашел, таки, но про колонку забыл. ;)
Правило про переменные, - это классика. Стараюсь его выполнять. Ну, вряд ли кто-то додумается сделать глобальные переменные с такими короткими и простыми именами, но вероятность есть. Обычно добавляется префикс "гл".
(31) (23) в статье скорректировано, ошибка с НомСтр изначально была устранена. Кстати, зачем передавался НомерСтроки ?
33. Poppy (poppy) 25.01.08 00:19
(32)
> Кстати, зачем передавался НомерСтроки ?

Наверно, для бОльшей универсальности. Ведь можно написать и так:

Код
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл 
   Если ПроверитьСтрокуТаблицы(ТЗ, ТЗ.НомерСтроки - 1, СписокКлючей) = 1 Тогда
      // БЛОК 1 Создать новый документ
      // БЛОК 2 Заполнить заголовок документа
   КонецЕсли;
   
   // БЛОК 5 Заполнение строки спецификации документа
   
   Если ПроверитьСтрокуТаблицы(ТЗ, ТЗ.НомерСтроки, СписокКлючей) = 1 Тогда
      // БЛОК 3 Заполнить подвал документа
      // БЛОК 4 Записать / вывести документ
   КонецЕсли;
КонецЦикла;
Показать полностью
34. Михаил Семенов (Shaman100M) 26.01.08 18:09
35. Михаил Семенов (Shaman100M) 16.03.09 19:41
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа