[Закрыть]
 
popoff.donetsk.ua
Ты сам и есть источник всего, что было, есть и будет.
Начало | Новости | Статьи | Форум | Опросы | Карта сайта | Обо мне
popoff.donetsk.ua - Статьи - Программирование - Технологии программирования - Флаги и оператор GOTO - Найти позицию элемента в массиве
Я это делаю
Персональное меню
Голосование
Деньги, либо любимое занятие? Постоянный адрес этого вопроса
Ваш возраст (не обязательно):

Введите целое число от 3 до 99.
Почему? (не обязательно):
Другие вопросы
Поиск по сайту
Реклама
Обмен электронных валют
money.dn.ua
Статистика

Найти позицию элемента в массиве

Постоянный адрес статьи

Пусть дан массив из n целочисленных элементов a[0] ... a[n-1]. Описание на Обероне/Компонентном Паскале:

VAR a: ARRAY n OF INTEGER;

Пусть про этот массив известно, что один из элементов равен x. Требуется найти этот элемент и записать его позицию в целую переменную i.

Типичный начинающий программист (и многие «профессионалы») сразу напишет FOR c вложенной проверкой и начнет искать способ выйти из цикла:

FOR i := 0 TO n-1 DO
  IF a[i] = x THEN ... END
END

Но в Обероне/Компонентном Паскале из цикла FOR выйти просто так нельзя. Если не отказываться от цикла FOR (см. ниже решение этой задачи с циклом WHILE), то единственный способ это сделать — заключить цикл в процедуру и использовать оператор RETURN, позволяющий выйти из процедуры в произвольной точке:

PROCEDURE Найти (VAR pos: INTEGER);
  VAR i: INTEGER;
BEGIN
  FOR i := 0 TO n-1 DO
    IF a[i] = x THEN pos := i; RETURN END
  END
END Найти

Тогда в основной программе вместо цикла нужно вызвать эту процедуру:

Найти( i )

Еще можно заменить FOR на оператор LOOP, в теле которого допускается выход на конец цикла (оператор EXIT).

Но ни в том, ни в другом случае нельзя «выпрыгнуть» вообще в произвольную точку вне цикла: RETURN «прыгает» на конец процедуры, а EXIT — на конец цикла. Таким образом программа остается достаточно структурированной.

Кстати, вот классическое «правильное» решение в стиле Дейкстры, выраженное на Обероне/Компонентном Паскале:

i := 0;
WHILE (i < n) & (a[i] # x) DO
  i := i + 1
END

Напомним, что вычисление логических выражений в Обероне/Компонентном Паскале производится по правилу «короткого замыкания»: если результат логической операции можно определить по первому операнду, то второй не вычисляется.

В данном случае по достижении i = n первый операнд (i < n) даст FALSE, так что результатом операции & (логическое И) тоже будет FALSE, и выход из цикла произойдет сразу, без вычисления второго операнда. Заметим, что вычисление второго операнда привело бы к ошибке и аварийной остановке программы из-за выхода за верхнюю границу массива.

Правило весьма удобно при систематической разработке алгоритмов и приводит к лаконичным программам; одно это правило исключает множество ситуаций, где наивный инстинкт требует использовать GOTO. Много примеров на этот счет — в том числе гораздо менее тривиальных, чем приведенный — дано в книгах Гриса и Дейкстры в нашем списке.

Заметим еще, что по приведенной стандартной схеме решается большое число задач, сводящихся к задаче поиска:

<инициализация цикла>
WHILE <условие ограничения поиска> & ~(<условие окончания поиска>) DO
  i := i + 1
END

Логическое значение, выражающее успех поиска, равно значению выражения <условие ограничения поиска> после выхода из цикла.

В некоторых языках, например В Си, можно выйти и из цикла for:

pos=-1;
for(i=0;i<n && pos<0;i++)
  if(a[i]==x) pos=i;

Смотрите также

Оригинальная идея этого примера:
http://www.inr.ac.ru/~info21/blackbox/disciplina/goto.htm

Последняя модификация: 13.11.05 18:28

Обсуждение статьи в форуме

Не проходите мимо! Оставьте Ваш комментарий в форуме! >>>

22.11.05 11:43 Screamer

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

22.11.05 13:12 popoff

В примере поиска элемента массива на самом деле избавления от флага нет.

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

Это как комментарии в программах. О них принято считать, что комментарии - это всегда хорошо. Но на самом деле комментарии используют, когда код плохой. Если код написан хорошо, то для того, чтобы в нем разобраться, не нужны комментарии.

22.11.05 16:11 Screamer

Да, но нужно понимать, особенно начинающим программистам, используется ли флаг в процедуре (но при этом совмещен с другими данными) или не используется вообще. Иначе, напрмер, можно, забыв обработать флаг «элемент массива не найден», получить ошибку «попытка доступа к несуществующему элементу массива». Или не учесть, что значение флага на самом деле допустимо и как валидный результат - к примеру, если в массиве допускаются и отрицательные индексы.

28.11.05 21:27 popoff

Я, кажется, понял

1. Плохой является перегрузка значений переменных, когда в одной переменной хранится несколько разных по смыслу данных. Как в нашем случае, в одной переменной хранится одновременно и индекс в массиве и флаг, который говорит о том, найден ли результат. Что хуже, перегрузка значений или введение флагов, пока что сложно сказать.

2. В нашем случае флаг «найден ли элемент в массиве» является результатом работы функции поиска и как бы является не совсем тем флагом, о которых я поднял вопрос в данном разделе статей. Я поднял вопрос о тех флагах, которые нужны для того, и только для того, чтобы управлять ходом работы программы, и сами по себе не являются некоторым осмысленным результатом или исходным данным. Если вопрос о том, «найден ли элемент в массиве» приписывать к рассматриваемым мной флагам, то я должен был бы говорить не о флагах, а о булевских данных вообще.

Вероятно, стоило бы ввести раздел с определением терминов.

3. В формулировке задачи написана совершенно конкретная фраза:

Пусть про этот массив известно, что один из элементов равен x.



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

4. В случае с процедурой, если элемент не найден, то переменная pos просто не будет изменена.

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

5. В исходном варианте при использовании цикла while в случае, если элемент не найден, в цикл записывается значение, выходящее за пределы индексов в массиве, равное n. Это можно рассматривать как флаг «элемент не найден». Если сравнивать два способа перегрузки значений переменных, то вероятно, предложенная мной в варианте на Си перегрузка значений лучше, чем предложенная в примере на Обероне, потому что -1 явно прописана в программе, а n определяется из логики работы; значение -1 является понятным для всех массивов, о которых известно, что индекс не может быть отрицательным и для того, чтобы понять, найден ли элемент, не нужен сам массив; если используется n, то для того, чтобы понять, найден ли элемент, нужно знать, в дополнение к результату поиска, как минимум длину массива или даже весь массив, если, как в PHP, в качестве индексов допускается что-угодно (в том числе и отрицательные числа). Но если допустить в качестве индексов что-угодно, то и алгоритм поиска будет совсем другим.

6. Три приведенных варианта решения задачи поиска элемента в массиве не являются эквивалентными. В первом варианте значение переменной не изменяется, во втором - возвращается значение n, а в третьем - значение -1.

7. Вариант с циклом, в котором перебираются все значения - это плохой код, потому что он не оптимален - перебираются все значения, когда можно перебирать меньше. Вариант с процедурой - это лучше, и, возможно, даже, намного лучше, потому что начинающие часто забывают выносить повторяющийся и/или обособленный (решающий отдельную, со своим ограниченным набором входных данных и результатов, независимую от остальных задачу) код в процедуры. В варианте с процедурой я плохим назвал бы использование процедуры вместо функции (я не знаю Оберона и не знаю, почему в оригинале использовалась процедура - возможно, там нет функций, хотя это весьма сомнительно; возможно также, такой вариант был приведен умышленно, чтобы показать описываемые мной в текущем пункте недостатки). Я назвал бы плохим подход с использованием изменяемых аргументов - от них хорошо бы избавляться везде, где от них можно избавиться. И я назвал бы плохим использование внешних переменных (в процедуре внешними являются массив, в которым производится поиск и само значение, которое ищется). Это становится понятным, если спросить у любого, кто не знает, как устроена процедура «Найти», что означает такой код:

Найти(i);

Лично я подумал бы, что нужно найти значение, хранящееся в переменной i. Но при этом я задался бы вопросом, «а где искать?» и «а почему игнорируется результат поиска?». Этот код не говорит сам за себя и требует обязательного комментария. Код, который требует комментария, является плохим кодом.

8. Исходя из задачи, поднятой в рассматриваемом примере («поиск элемента в массиве»), недостатки кода, рассмотренные в п.7, не имеют к задаче никакого отношения. Однако могут быть использованы при комментировании варианта решения с процедурой как дополнительный показатель того, что человек, который решил задачу таким способом, на самом деле решил задачу плохо.

9. Наша цель - показать, какой код является хорошим, а какой - плохим. При этом «хорошим» является код, работу которого можно понять без дополнительных комментариев, о котором нельзя сказать, что в нем есть какие-то «хитросплетения логики». Сравнивая два варианта, об одном можно сказать, что он лучше, чем другой, если работу первого можно понять быстрее, чем работу другого, если внести изменения в первый можно быстрее, чем во второй и если при внесении изменений в первый вариант вероятность возникновения ошибки меньше, чем при внесении изменений во второй вариант.

10. Для достижения нашей цели не требуется приводить хороший вариант решения. Достаточно привести плохой вариант и показать, чем он плох. Хороший вариант - это вариант, который не обладает рассмотренными недостатками.

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

11. Исходя из нашей цели (п.9), не имеет значения, можно ли написать вариант решения с процедурой, который не обладает недостатками, приведенными в п.7. Мы привели плохой вариант и показали, чем он плох. Мы добились нашей цели.

12. Исходя из нашей цели (п.9), нам становится все равно, каким именно образом обрабатывается ситуация «элемент не найден». Мы привели вариант плохого решения и обратили внимание на то, чем этот вариант является плохим. Мы добились нашей цели.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Screamer:
Спасибо Вам за Ваш комментарий. Благодаря Вашему комментарию я смог сформулировать новые, не сформулированные ранее в этом разделе признаки плохого кода (п.1 и п.7). О признаке, сформулированном в п.1 я раньше не читал и не слышал, чтобы о нем прямо не говорили, что «это - плохо». От всех знающих был бы рад получить ссылки, если этот признак где-нибудь уже рассматривался. Признаки, сформулированные в п.7 рассматриваются довольно подробно в курсе «Функциональное и логическое программирование».

30.01.10 10:37 VNik62a

Чрезвычайно рад встрече на сайте после некоторого перерыва. Нахожу его чрезвычайно для себя полезным и желаю автору развития и процветания
PS давно не заходил более подробные отзывы после изучения материала

05.04.10 23:28 Hale_32bit

Спасибо автору. Хорошие темы здесь поднимаются.

Просмотреть все комментарии в режиме форума. Всего комментариев: 6
Не проходите мимо! Оставьте Ваш комментарий в форуме! >>>