Главная страница
Навигация по странице:

  • Продублировать информацию, чтобы использовать ключ напрямую

  • Преобразовать ключ, чтобы использовать его напрямую

  • Изолируйте преобразование ключа в собственном методе

  • 18.3. Таблицы с индексированным доступом

  • Рис. 18-4.

  • 18.4. Таблицы со ступенчатым доступом

  • Пример ступенчатого поиска в таблице (Visual Basic)

  • Вероятность Сумма страхового иска

  • Следите за граничными точками

  • Рассмотрите вопрос использования бинарного поиска вместо последова

  • ЧАСТЬ IV

  • Поместите ступенчатый поиск в таблице в отдельный метод

  • 18.5. Другие примеры табличного поиска

  • Перекрестная ссылка

  • 19.1. Логические выражения

  • ГЛАВА 19

  • Примеры использования неоднозначных флажков для логических значений (Visual Basic)

  • Хорошие, но не превосходные примеры использования True и False вместо числовых значений для проверок условий (Visual Basic)

  • Используйте неявное сравнение логических величин с true или false

  • Улучшенные примеры неявных проверок True или False (Visual Basic)

  • Руководство по стилю программирования и конструированию по


    Скачать 7.6 Mb.
    НазваниеРуководство по стилю программирования и конструированию по
    Дата18.05.2023
    Размер7.6 Mb.
    Формат файлаpdf
    Имя файлаCode_Complete.pdf
    ТипРуководство
    #1139697
    страница52 из 104
    1   ...   48   49   50   51   52   53   54   55   ...   104
    ГЛАВА 18 Табличные методы
    417
    Подгонка значений ключа
    Во всех трех предыдущих примерах вы могли использовать данные в качестве ключа для прямого обращения к таблице. То есть можно было указать перемен- ную
    messageID как ключ без всяких изменений, переменную month в примере количества дней в месяцах, а также
    gender, maritalStatus и smokingStatus в приме- ре ставок страхования.
    Было бы хорошо всегда обращаться к таблице напрямую, потому что это просто и быстро. Однако не всегда данные для этого годятся. В примере со ставками стра- хования переменная
    age не очень удобна в качестве ключа. Первоначальная ло- гика определяла одну ставку для лиц моложе 18 лет, индивидуальные ставки для возрастов от 18 до 65 и одну ставку для людей старше 65. Это означает, что для возрастов от 0 до 17 и от 66 и выше нельзя использовать возраст как ключ напря- мую, если таблица хранит только один набор ставок для нескольких лет.
    Это приводит к обсуждению вопроса подгонки значений ключа в таблице поис- ка. Подогнать ключ можно несколькими способами.
    Продублировать информацию, чтобы использовать ключ напрямую
    Один прямолинейный способ заставить
    age работать ключом в таблице ставок —
    продублировать все ставки для лиц, моложе 18, для каждого возраста от 0 до 17, а затем использовать возраст для прямого обращения к таблице. То же самое мож- но сделать и для возрастов от 66 лет и старше. Преимущество этого подхода в том,
    что структура таблицы остается простой и доступ к данным так же прост. Если нужно добавить специальное значение ставки для некоторого возраста, меньше- го 17, вы можете просто изменить табличное значение. Недостаток этого метода в том, что дублирование приведет к напрасным затратам на хранение избыточ- ной информации, а также увеличит вероятность появления ошибок в таблице хотя бы потому, что таблица будет содержать избыточные данные.
    Преобразовать ключ, чтобы использовать его напрямую Второй способ задействовать
    Age в качестве прямого ключа — применить к переменной Age неко- торую функцию, которая позволит это делать. В данном случае такая функция дол- жна преобразовывать все возрасты от 0 до 17 к какому-то одному значению, ска- жем, 17, а возрасты старше 66 — к другому, например, 66. В данном случае такое преобразование легко выполнить с помощью функций
    min() и max(). Так, для со- здания табличного ключа в диапазоне от 17 до 66 можно использовать выражение:
    max( min( 66, Age ), 17 )
    Реализация функции трансформации требует хорошего понимания структуры данных, которые вы хотите применить как ключ, и это не всегда так просто, как использование функций
    min() и max(). Допустим, в этом примере ставки меня- ются через интервалы не в 5 лет, а в 1 год. Если только вы не хотите дублировать все данные по пять раз, вам придется написать функцию, которая делит
    Age на 5
    и использует методы
    min() и max().
    Изолируйте преобразование ключа в собственном методе Если вам нуж- но подгонять данные для использования в качестве табличного ключа, помести- те операции, трансформирующие данные в ключ, в отдельный метод. Его исполь- зование исключает возможность применения разных преобразований в разных

    418
    ЧАСТЬ IV Операторы местах. Это упростит модификацию при изменении функции преобразования.
    Хорошее имя процедуры, такое как
    KeyFromAge(), также прояснит и задокументи- рует назначение математических махинаций.
    Если ваша среда предоставляет готовые варианты преобразования ключа, исполь- зуйте их. Например, класс
    HashMap в языке Java позволяет создавать пары «ключ- значение».
    18.3. Таблицы с индексированным доступом
    Иногда простого математического преобразования недостаточно для перехода от таких данных, как
    Age к значению ключа. Некоторые из таких случаев подходят для схем с индексным доступом.
    Применяя индексы, вы используете исходные данные для поиска ключа в индексной таблице, а затем значение из этой таблицы служит для поиска интересующих вас данных.
    Допустим, вы заведуете складом, и у вас около 100 наименований товара. Далее предположим, что каждый товар имеет четырехзначный номер в диапазоне от 0000
    до 9999. В этом случае, если вы захотите задействовать номер товара в качестве ключа для прямого доступа к таблице, описывающей какой-то признак каждого товара, вам придется создать индексный массив с 10 000 записей (от 0 до 9999).
    Этот массив в основном будет пустым за исключением 100 элементов, соответ- ствующих номерам товаров на вашем складе. Как показано на рис. 18-4, эти эле- менты указывают на таблицу с описанием товаров, содержащую гораздо менее
    10 000 записей.
    Рис. 18-4. В отличие от таблиц с прямым доступом для обращения к таблице
    с индексным доступом используется промежуточный индекс

    ГЛАВА 18 Табличные методы
    419
    У схем с индексным доступом два главных преимущества. Во-первых, если каж- дый элемент главной таблицы поиска имеет большой размер, создание индекс- ного массива с большим количеством пустых ячеек потребует гораздо меньше места, чем создание самой таблицы поиска с большим количеством пустых яче- ек. Пусть, например, элемент основной таблицы занимает 100 байт, а элемент индексной таблицы — 2 байта. Далее предположим, что главная таблица содер- жит 100 записей, а данные, необходимые для обращения к ней, могут принимать
    10 000 возможных значений. В этом случае выбор осуществляется между исполь- зованием индексной таблицы с 10 000 записей или только одной главной табли- цы с 10 000 записей. Если вы используете индекс, общий объем требуемой памя- ти равен 30 000 байт. Если вы откажетесь от индекса и будете попусту тратить память в основной таблице, то общий объем используемой памяти составит
    1 000 000 байт.
    Вторым преимуществом индексного доступа (даже если вам не удастся сэкономить память с его помощью) является то, что иногда дешевле манипулировать элемен- тами индекса, чем элементами основной таблицы. Так, если у вас есть таблица с именами работников, датами их приема на работу и окладами, вы можете создать один индекс для доступа к таблице по имени работника, второй — для доступа на основе даты приема, и третий — для доступа на основе размера зарплаты.
    И последнее преимущество индексной схемы доступа — общее для всех таблиц поиска удобство сопровождения. Данные, закодированные в таблицах, легче со- провождать, чем внедренные в код. Для увеличения гибкости поместите код ин- дексного доступа в отдельный метод и вызывайте его, когда вам нужно получить значение ключа на основе номера товара. Когда понадобится изменить таблицу,
    вы можете решить изменить схему индексного доступа или даже перейти к дру- гому способу доступа к таблице поиска. Схему доступа будет легче поменять, если код доступа по индексу не разбросан по всей программе.
    18.4. Таблицы со ступенчатым доступом
    Еще один способ табличного доступа — ступенчатый метод. Этот метод не такой прямой, как индексная структура, но позволяет не терять такого большого объе- ма памяти.
    Основная идея ступенчатой структуры в том, что записи в таблице соответству- ют некоторому диапазону данных, а не отдельным элементам (рис. 18-5).
    Рис. 18-5. Ступенчатый подход классифицирует каждый элемент, определяя
    уровень, на котором он наталкивается на «лестницу». «Ступенька», в которую
    этот элемент упирается, определяет его категорию

    420
    ЧАСТЬ IV Операторы
    Например, если вы пишете аттестационную программу, диапазон оценки «B» мо- жет быть в пределах 75–90%. Вот список оценок, которые вам однажды, возмож- но, придется запрограммировать:
    ≥ 90.0%
    A
    < 90.0%
    B
    < 75.0%
    C
    < 65.0%
    D
    < 50.0%
    F
    Это ужасный диапазон для табличного поиска, потому что вы не можете напи- сать простую функцию преобразования данных для соответствия буквам от
    A до
    F. Индексная схема неудобна, так как используются числа с плавающей запятой.
    Вы можете предложить конвертировать числа с плавающей запятой в целые, что для данного случая вполне допустимо, однако в целях иллюстрации этот пример будет придерживаться чисел с плавающей запятой.
    Применяя ступенчатый метод, вы помещаете верхнюю границу каждого диапазо- на в таблицу, а затем пишете цикл для сравнения количества баллов с этой верх- ней границей. Обнаружив точку, в которой сумма баллов в первый раз превысит заданный предел, вы узнаете оценку. Применяя ступенчатую методику, надо сле- дить за правильной обработкой граничных точек диапазона. Вот пример кода Visual
    Basic, присваивающей оценки группе студентов, основываясь на данных этого примера:
    Пример ступенчатого поиска в таблице (Visual Basic)
    ‘ Подготавливаем данные для таблицы оценок.
    Dim rangeLimit() As Double = { 50.0, 65.0, 75.0, 90.0, 100.0 }
    Dim grade() As String =
    { ”F”, “D”, “C”, “B”, “A” }
    maxGradeLevel = grade.Length – 1
    ’ Присваиваем оценки, основываясь на количестве баллов, набранных студентом.
    gradeLevel = 0
    studentGrade = “A”
    While ( ( studentGrade = “A” ) and ( gradeLevel < maxGradeLevel ) )
    If ( studentScore < rangeLimit( gradeLevel ) ) Then studentGrade = grade( gradeLevel )
    End If gradeLevel = gradeLevel + 1
    Wend
    Хотя это и простой пример, вы легко можете его обобщить для работы с несколь- кими студентами или несколькими системами оценок (например, введя разные оценки для различных уровней сложности выполняемых заданий), а также для изменения системы оценок.
    Преимущество этого подхода перед другими табличными методами в том, что он хорошо работает с нестандартными данными. Пример с оценками прост с той точки зрения, что, хотя оценки и присваиваются через неодинаковые промежут-

    ГЛАВА 18 Табличные методы
    421
    ки, сами рассматриваемые числа — «круглые», оканчивающиеся на 5 или 0. Сту- пенчатый способ столь же хорошо подходит и для данных, не заканчивающихся на 5 или 0. Вы можете применять эту методику и в статистических задачах для работы с такими примерами вероятностных распределений, как этот:
    Вероятность
    Сумма страхового иска
    0,458747
    $0,00 0,547651
    $254,32 0,627764
    $514,77 0,776883
    $747,82 0,893211
    $1 042,65 0,957665
    $5 887,55 0,976544
    $12 836,98 0,987889
    $27 234,12
    Такие ужасные числа сводят на нет все попытки создать функцию для их точного преобразования в табличные ключи. В этом случае следует использовать ступен- чатый метод.
    Этот подход также позволяет оценить главные преимущества табличных методов:
    гибкость и модифицируемость. Если диапазоны баллов в примере с оценками надо изменить, программу легко исправить, изменив элементы массива
    RangeLimit. Вы легко можете обобщить метод выставления отметок так, чтобы он принимал из- вне таблицы с отметками и соответствующими граничными значениями баллов.
    При выставлении оценок не обязательно использовать баллы, выраженные в про- центах, их можно поменять на обычные единицы, и это не потребует больших изменений в программе.
    Вот несколько тонкостей, которые надо принимать во внимание, применяя сту- пенчатый метод.
    Следите за граничными точкамиУбедитесь, что вы учитываете верхнюю гра- ницу каждого диапазона. Выполните ступенчатый поиск так, чтобы он находил значения, соответствующие любому интервалу, кроме самого верхнего, а затем за- дайте несколько элементов, попадающих в этот верхний интервал. Иногда требу- ется создать искусственное значение для верхней границы последнего интервала.
    Не ошибитесь с операциями < или <=! Убедитесь, что при значениях, попадаю- щих в верхний диапазон, цикл корректно завершается, а также что границы ин- тервалов обрабатываются правильно.
    Рассмотрите вопрос использования бинарного поиска вместо последова-
    тельногоВ примере с оценками цикл, присваивающий отметки, последователь- но проходит по списку предельных значений баллов. Если у вас будет более длин- ный список, затраты на последовательный поиск могут чрезмерно возрасти. В этом случае можно воспользоваться квази-бинарным поиском. «Квази» он потому, что цель большинства бинарных поисков — нахождение значения. В данном случае вы не намерены найти значение — вы ищете правильную категорию для этого значения. Алгоритм бинарного поиска должен корректно определить, куда это

    422
    ЧАСТЬ IV Операторы значение попадает. Не забывайте также рассматривать граничные точки как спе- циальный случай.
    Рассмотрите вопрос использования индексного доступа вместо ступен-
    чатого методаСхема с индексным доступом (см. раздел 18.3) может быть хо- рошей альтернативой ступенчатому подходу. Поиск, выполняющийся в ступенча- том методе, может увеличить накладные расходы, и если, скорость выполнения имеет значение, вы, возможно, захотите пожертвовать объемом памяти и затра- тами на дополнительную индексную структуру, чтобы получить преимущество в скорости, обусловленное более прямым методом доступа.
    Очевидно, эта альтернатива во всех случаях не годится. В примере с оценками вы,
    возможно, захотите ее использовать. Если у вас всего 100 дискретных процент- ных значений, расходы на дополнительную память для индексного массива не будут чрезмерными. С другой стороны, если вы работаете с данными вероятностного распределения, приведенными выше, вы не сможете применить индексную схе- му, потому что не сможете задействовать в качестве ключей к таблице данных такие числа, как 0,458747 и 0,547651.
    В некоторых случаях подойдет любой из нескольких вари- антов. И задачей проектирования является выбор одного из нескольких хороших вариантов для этого конкретного слу- чая. Не слишком волнуйтесь по поводу выбора наилучше- го. Как говорит Батлер Лемпсон, выдающийся инженер из
    Microsoft, лучше стремиться к хорошему решению и избегать неудач, чем пытать- ся найти наилучшее решение (Lampson, 1984).
    Поместите ступенчатый поиск в таблице в отдельный методКогда вы создаете функцию преобразования, которая трансформирует такое значение, как
    StudentGrade, в табличный ключ, поместите ее в отдельный метод.
    18.5. Другие примеры табличного поиска
    Несколько примеров табличного поиска встречаются в других разделах этой книги.
    Они используются в процессе обсуждения других методик, и поэтому в контек- сте не подчеркивается их принадлежность к табличному поиску. Вот где вы мо- жете их найти:

    поиск ставок в таблице страхования: раздел 16.3;

    применение таблиц решения для замены сложной логики: подраздел «Исполь- зуйте таблицы решений для замены сложных условий» раздела 19.1;

    расходы на страничную организацию памяти при табличном поиске: раздел
    25.3;

    комбинации логических значений (A или B или C): подраздел «Замена слож- ных выражений на обращение к таблице» раздела 26.1;

    предварительное вычисление значения в таблице погашения ссуд: раздел 26.4.
    Перекрестная ссылка О пра- вильных подходах к выбору альтернативных способов про- ектирования см. главу 5.

    ГЛАВА 18 Табличные методы
    423
    Контрольный список: табличные методы
     Рассмотрены ли табличные методы в качестве альтерна- тивы сложной логике?
     Рассмотрены ли табличные методы в качестве альтернативы сложным струк- турам с наследованием?
     Рассмотрен ли вопрос размещения табличных данных отдельно от программы и их чтения во время выполнения, чтобы позволить обновлять данные без изменения кода?
     Если доступ к таблице нельзя осуществить напрямую с помощью простого индекса массива (как в примере с возрастами), помещены ли вычисления ключа доступа в отдельный метод, а не разбросаны по всему коду?
    Ключевые моменты

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

    Основной вопрос при использовании таблиц состоит в выборе способа до- ступа к таблице. Вы можете использовать прямой, индексный или ступенча- тый доступ.

    Другой основной вопрос состоит в выборе того, что конкретно будет поме- щено в таблицу.
    http://cc2e.com/1872

    424
    ЧАСТЬ IV Операторы
    Г Л А В А 1 9
    Общие вопросы управления
    Содержание

    19.1. Логические выражения

    19.2. Составные операторы (блоки)

    19.3. Пустые выражения

    19.4. Укрощение опасно глубокой вложенности

    19.5. Основа программирования: структурное програм- мирование

    19.6. Управляющие структуры и сложность
    Связанные темы

    Последовательный код: глава 14

    Код с условными операторами: глава 15

    Код с циклами: глава 16

    Нестандартные управляющие структуры: глава 17

    Сложность в разработке ПО: подраздел «Главный Технический Императив Раз- работки ПО: управление сложностью» раздела 5.2
    Обсуждение способов управления было бы неполным без углубления в несколь- ко общих вопросов, касающихся управляющих структур. Большая часть этой гла- вы посвящена подробностям их практического применения. Если вам интерес- ней читать о теории управляющих структур, а не о мелких деталях, сосредоточь- тесь на исторической перспективе структурного программирования (раздел 19.5),
    а затем на взаимодействии управляющих структур (раздел 19.6).
    19.1. Логические выражения
    Кроме простейших случаев, таких, в которых происходит последовательное вы- полнение операторов, все управляющие структуры зависят от вычисления логи- ческих выражений.
    http://cc2e.com/1978

    ГЛАВА 19 Общие вопросы управления
    425
    Использование true и false в логических проверках
    В логических выражениях следует использовать идентификаторы
    true и false, а не значения
    0 и 1. Большинство современных языков реализует логический тип дан- ных и предоставляет предопределенные идентификаторы для значений «истина»
    и «ложь». В таких языках все просто: они даже не позволят вам присвоить логиче- ским переменным другие значения, кроме
    true или false. В языках, не содержащих встроенного логического типа, от вас требуется большая дисциплинированность,
    чтобы сделать логические выражения читабельными. Приведем пример:
    Примеры использования неоднозначных флажков
    для логических значений (Visual Basic)
    Dim printerError As Integer
    Dim reportSelected As Integer
    Dim summarySelected As Integer
    If printerError = 0 Then InitializePrinter()
    If printerError = 1 Then NotifyUserOfError()
    If reportSelected = 1 Then PrintReport()
    If summarySelected = 1 Then PrintSummary()
    If printerError = 0 Then CleanupPrinter()
    Если использование флажков
    0 и 1 — общая практика, то чем она плоха? При чте- нии кода неочевидно, когда выполняются вызовы функций: когда проверки усло- вия истинны или когда они ложны. Ничего в этом фрагменте не говорит о том,
    представляет ли
    1 «истину», а 0 — «ложь» или же все наоборот. Нельзя даже утвер- ждать, что
    1 и 0 служат в качестве значений «истина» и «ложь». Например, в строке
    If reportSelected = 1 число 1 может легко обозначать первый отчет, 2 — второй, 3
    третий; ничто в коде не наводит на мысль, что
    1 означает либо «истину», либо «ложь».
    Кроме того, легко можно написать
    0, имея в виду 1, и наоборот.
    Используйте термины
    true и false для проверки логических выражений. Если ваш язык не поддерживает их напрямую, создайте их с помощью макросов препроцес- сора или глобальных переменных. Вот как можно переписать предыдущий пример,
    используя встроенные идентификаторы
    True и False языка Visual Basic:
    Хорошие, но не превосходные примеры использования True
    и False вместо числовых значений для проверок условий (Visual Basic)
    Dim printerError As Boolean
    Dim reportSelected As ReportType
    Dim summarySelected As Boolean
    If ( printerError = False ) Then InitializePrinter()
    If ( printerError = True ) Then NotifyUserOfError()
    If ( reportSelected = ReportType_First ) Then PrintReport()
    If ( summarySelected = True ) Then PrintSummary()
    Перекрестная ссылка Еще луч- ший подход к выполнению тех же проверок можно найти в сле- дующем примере кода.

    426
    ЧАСТЬ IV Операторы
    If ( printerError = False ) Then CleanupPrinter()
    Применение констант
    True и False проясняет назначение кода. Вам не нужно по- мнить, что обозначают
    1 и 0, и вы не сможете их случайно перепутать. Более того,
    в переписанном коде стало понятно, что в некоторых случаях
    1 и 0 в исходном примере на Visual Basic не являлись логическими флагами. Выражение
    If report-
    Selected = 1 было не проверкой логического значения, а проверкой того, выбран ли первый отчет.
    Этот подход сообщает читателю, что вы выполняете логическую проверку. Кро- ме того, сложнее написать
    true, подразумевая false, чем 1, подразумевая 0. Также вы избежите распространения магических чисел
    0 и 1 по всему коду. Далее при- ведены советы по использованию
    true и false в логических проверках.
    Используйте неявное сравнение логических величин с true или false
    Вы сможете сделать проверку условия более понятной, если будете рассматривать про- веряемые выражения как логические. Например, пишите:
    while ( not done ) ...
    while ( a > b ) ...
    вместо:
    while ( done = false ) ...
    while ( (a > b) = true ) ...
    Использование неявных сравнений уменьшает число элементов, которые придется помнить при чтении кода, и приближает получаемое выражение к разговорному английскому. Вот как улучшить стиль предыдущего примера:
    Улучшенные примеры неявных проверок True или False (Visual Basic)
    Dim printerError As Boolean
    Dim reportSelected As ReportType
    Dim summarySelected As Boolean
    If ( Not printerError ) Then InitializePrinter()
    If ( printerError ) Then NotifyUserOfError()
    If ( reportSelected = ReportType_First ) Then PrintReport()
    If ( summarySelected ) Then PrintSummary()
    If ( Not printerError ) Then CleanupPrinter()
    Если ваш язык не поддерживает логические переменные и вам приходится их эмулировать, то, вероятно, вы не смо- жете использовать эту технологию, поскольку искусствен- ные
    true и false не всегда могут проверяться в таких выра- жениях, как
    while ( not done ).
    Перекрестная ссылка О логи- ческих переменных см. раздел
    12.5.

    1   ...   48   49   50   51   52   53   54   55   ...   104


    написать администратору сайта