gifts2017

Алгоритм разбиения строки (предложения,текста) на несколько строк, с заданными длинами результирующих строк

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

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

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

Текст самой процедуры:

//Функция разбивает строку на подстроки, по заданным размерам строк
//Параметры:СтрокаРазбиения - исходная строка любого размера
//МассивРазмераСтрок - Массив с данными о размерах строк
//Возврат - обработанная строка
Функция РазбитьТекстПоМассивуРазмераСтрок(СтрокаРазбиения, МассивРазмераСтрок)
	
	МассивВозврата = Новый Массив;
	
	МассивРазделителей = новый Массив;
	МассивРазделителей.Добавить(" ");
	МассивРазделителей.Добавить(",");
	МассивРазделителей.Добавить(";");

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

				СтрокаВозврата = СтрокаВозврата+Слово;
				МассивВозврата.Добавить(Лев(СтрокаВозврата,МассивРазмераСтрок[ИндексМассиваРазмераСтрок]));
				СтрокаВозврата = "";
				Слово ="";
				Прервать;
				
			Иначе//Если не вмещаемся
				
				Если СтрДлина(Слово) > МассивРазмераСтрок[ИндексМассиваРазмераСтрок] И СтрокаВозврата="" Тогда //Если слово больше чем доступная длинна строки
					
					Если МассивВозврата.Количество() = МассивРазмераСтрок.Количество() Тогда
						Прервать;
					КонецЕсли;

					МассивВозврата.Добавить(Лев(Слово,МассивРазмераСтрок[ИндексМассиваРазмераСтрок]));
					

					Слово = Сред(Слово,МассивРазмераСтрок[ИндексМассиваРазмераСтрок]+1);  
					Слово = Слово + Символ;
					ИндексМассиваРазмераСтрок = ИндексМассиваРазмераСтрок+ 1;

							
				Иначе
					Если МассивВозврата.Количество() = МассивРазмераСтрок.Количество() Тогда
						Прервать;
					КонецЕсли;
					
					МассивВозврата.Добавить(СокрЛП(СтрокаВозврата));
					СтрокаВозврата = "";
					ИндексМассиваРазмераСтрок = ИндексМассиваРазмераСтрок+ 1;
					Сч = Сч - 1;
					Продолжить;
					
				КонецЕсли;
			КонецЕсли;
			
		КонецЕсли;
		
	КонецЦикла;
	
	Если ЗначениеЗаполнено(СтрокаВозврата+Слово) И МассивВозврата.Количество() < МассивРазмераСтрок.Количество() Тогда
	
		МассивВозврата.Добавить(Лев(СтрокаВозврата+Слово,МассивРазмераСтрок[ИндексМассиваРазмераСтрок]));
		
	КонецЕсли;
	
	Возврат(МассивВозврата);
	
КонецФункции

Скачать файлы

Наименование Файл Версия Размер
ОтладкаРазбиенияСтроки.epf
.epf 9,96Kb
17.03.16
0
.epf 9,96Kb Скачать

См. также

Подписаться Добавить вознаграждение
Комментарии
1. Гость 17.03.16 16:45
2. Александр Полетаев (Alias) 17.03.16 17:36
Ну как сказать... оно конечно работает, несмотря на некоторые огрехи. Например, если разделитель попадёт как раз в конец блока, то строка будет на один символ длиннее разрешённой (см. пример ниже). Ну и наверняка будут неточности в сложных случаях -- типа длинной строки из пробелов или сплошных разделителей и т.д. Считаем, что сложные случаи мы не рассматриваем.

А вот если оценить сложность(размер кода) алгоритма и его оптимальность, то тут ещё есть куда расти.

Например, самая трудозатратная операция (МассивРазделителей.Найти(Символ) = Неопределено) выполняется столько раз, сколько букв в исходной строке. В сумме это составляет около 30% всего времени выполнения алгоритма! Это очень много, зачем? В моём примере реализации той же задачи (опять же, см. ниже) этот оператор вызывается на порядок меньше раз (32 против 486). Из-за этого общее время выполнения снижено с 0.003427 до 0.000324 (опять же, меньше на порядок).

По количеству кода мой вариант набросанный на коленке уместился в 27 строк, у автора -- 43. Зачем столько писать для реализации такой простой задачи?

Мой вариант (простите, не знаю как раскрасить):

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

Огрехи авторского алгоритма, и результат моего:
70[;Возник вопрос написания такого алгоритма переноса строки. Задача] (длина 66)
60[была следующая - необходимо разбивать строку текста под] (длина 55)
50[заданные графы в печатной форме и размещать эту;] (длина 51) разрешено всего 50
40[строку там. В качестве признаков] (длина 32)
50[допустимости переноса были выбраны символы пробела,] (длина 51) разрешено всего 50
60[запятой, точки с запятой (при необходимости - можно] (длина 51)
70[модифицировать саму функцию переноса). Прилагается обработка для теста] (длина 70)
60[самого алгоритма, в которой необходимо внести исходную] (длина 54)
50[строку и задать размеры строк разбиения.] (длина 40)
------------------
70[;Возник вопрос написания такого алгоритма переноса строки. Задача] (длина 66)
60[была следующая - необходимо разбивать строку текста под] (длина 55)
50[заданные графы в печатной форме и размещать эту] (длина 50)
40[строку там. В качестве признаков] (длина 32)
50[допустимости переноса были выбраны символы пробела] (длина 50)
60[запятой, точки с запятой (при необходимости - можно] (длина 51)
70[модифицировать саму функцию переноса). Прилагается обработка для теста] (длина 70)
60[самого алгоритма, в которой необходимо внести исходную] (длина 54)
50[строку и задать размеры строк разбиения.] (длина 40)
3. Sergey Andreev (starik-2005) 19.03.16 13:06
Как-то слишком много текста в функции. Сдается мне, что можно все в 10 строк уложить...
4. Алексей Ситников (SiAl) 14.04.16 11:43
Молодец. Но я бы лично использовал регулярные выражения, в WIndows, например, - RegExp. Правда решение не универсально, но объем кода меньше, и работать будет шустрее