Можете ли вы дать мне комментарии на элегантность? - PullRequest
1 голос
/ 10 апреля 2020

Я только начал изучать, как кодировать, и мне интересно получить отзывы о моем коде (кроме того, правильно ли он выполняет задачу). Я беру онлайн курс CS50. Задача состоит в том, чтобы создать жадный алгоритм для возврата изменений.

#include <math.h>
#include <cs50.h>
#include <stdio.h>

int main(void)
{
   //Prompt user for money value they want in change
   float dollars;
   do 
   {
      dollars = get_float("Change owed: ");
   }
   while (dollars < 0.0099);  //Has to be more than 1 cent

   // Convert total change value to cents, to avoid float/float division
   int cents = round(dollars * 100);

   // Use floor function to round down to closest integer
   int x = floor(cents / 25);

   // Introduce modulus, so that the remainder of previous step division 
   // can be used to determine the next step for number of coins
   int y = floor((cents % 25) / 10);
   int z = floor(((cents - (x * 25) - (y * 10)) % 10) / 5);
   int w = floor(((cents - (x * 25) - (y * 10) - (z * 5)) % 5) / 1);

   //Output is an integer, which is an addition of previous steps
   int a = x + y + z + w;
   printf ("%d\n", a);
}

Приведенный выше код работает, но мне интересно, будет ли al oop быстрее выдавать результат?

1 Ответ

0 голосов
/ 11 апреля 2020

Elegance - большое, но довольно обобщенное c слово. Я пытаюсь решить сразу несколько задач, касающихся элегантности программирования:

1. Algorithmi c elegance

Приведенный выше код работает, но мне интересно, быстрее ли будет al oop при выводе?

Как Вы пояснили в своем комментарии, что вы не спрашиваете о бесконечном l oop в начале, когда вы повторяете, пока входные данные не будут действительными. Следовательно, я понимаю, что вопрос заключается в том, следует ли отдавать предпочтение al oop по сравнению с закрытыми вычислениями, дающими число четверть / десять центов / никель / цент.

  • Прямое вычисление (с использованием деления / модуля) определенно более элегантно, чем вычитать количество другого, сравнивая, достаточно ли остатка для другой монеты такого количества. Такой al oop не улучшит ни эффективность, ни удобочитаемость. Если используется большое количество денег (по сравнению с числовой c точностью типа float, такая oop может даже ухудшить стабильность численной c (поскольку операции с плавающей запятой не ассоциативны) - см. Также «Переменная» Напечатайте "ниже.
  • Если вы используете массив для количества монет в типе и (отсортированный) константный массив для центовых сумм для типа монеты, al oop можно использовать для разделения текущая сумма в наборе монет текущего типа плюс остаток, повторяющийся от больших монет к более мелким.

    Только для четырех типов монет (= l oop итераций) я нахожу это превышение. Эффективность процессора будет немного ниже (управление l oop, индексирование массива), а потребление памяти fla sh может быть немного (игнорируемым) немного меньше.

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

  • Вы можете сохранить некоторую сложность, если вам не нужно повторно вычитать остаток, выплаченный более крупными монетами, но обновлять оставшуюся сумму денег.
  • Если Вас интересует только общее количество монет, но не их типы. Вам нужно только сосчитать монеты и сохранить в памяти оставшиеся деньги.
  • Если вы хотите оптимизировать производительность за пределы переносимости ANSI C, вы можете посмотреть на инструкции процессора, которые вычисляют целочисленное отношение и модуль одновременно. Проверьте ваш набор команд процессора и соответствующие функции C intrinsi c (или операторы ассемблера, соответственно). Но тогда вы жертвуете элегантностью в отношении портативности и читабельности!

Тип переменной

  • Для денежного баланса в долларах тип переменной float был выбрано - вероятно, для того, чтобы разделить число доллара на число центов. В целом, целочисленная арифметика является более стабильной, чем арифметика с плавающей запятой, потому что арифметика с плавающей запятой включает в себя округление даже во время сложения / вычитания, умножения и преобразования типов из целых чисел. Некоторые очень немногие исключения из этого правила применяются, когда вы работаете с действительно большими числами, и вы можете пожертвовать некоторой точностью, но ваш пример выбора монет не достигнет этой области.

    Вместо этого вы должны конвертировать начальную сумму в долларах в центы (умножьте на 100) и начните с центовых сумм. Тогда нет необходимости в округлении. Как отмечено в комментарии @WeatherVane, вызовы floor() в коде не были необходимы в первой версии кода, потому что это поведение для преобразования с плавающей точкой в ​​целое число, требуемое стандартом. При запуске из целого числа возникает вопрос "floor или нет?" даже не подходит.

  • Внимание, у целых чисел и чисел с плавающей точкой есть верхний предел, который определяется выбранным вами фактическим типом (int, short, long, long long, unsigned / signed, uint64_t et c. - есть что исследовать, если вы еще этого не знаете ...). Пожалуйста, проверьте заголовок <limits.h> для деталей. Поскольку вы (более или менее) сравнивали входное значение с нулем, вы должны также сравнивать его с максимальным числом, которое вы можете обработать.
  • Кстати, float/float деление не является большой проблемой. Числа с плавающей точкой предназначены для деления, в то время как целочисленное деление заметно дорого для многих архитектур.

Элегантность стиля кодирования

Для всех тех программ, которые не критичны для производительности ЦП или объем памяти (RAM / ROM), самый важный аспект элегантности - это удобство обслуживания - как гласит известная поговорка,

Код пишется только один раз, но читается много раз.

Этот аспект частично затрагивает вопросы вкуса, но мнения о SO устарели. Следовательно, мы должны ограничить это некоторыми объективными критериями:

  • Авторы руководств по стилю кодирования категорически не согласны, когда открывать новую строку, особенно вокруг циклов и других блоков { }. Тем не менее, подавляющее большинство из них согласны с тем, что каждому l oop телу следует указывать хотя бы одну выделенную строку.
  • Литералы, такие как числовые константы в коде, часто требуется назначать макросам. Это не зря, так как снижает вероятность ошибиться в одном месте и использовать правильное значение в других случаях. Кроме того, это помогает, когда меняются требования к программному обеспечению: если бы США решили заплатить 26 центов за четверть монеты, нужно было бы изменить одну строку, и нам не пришлось бы беспокоиться о согласованности.
  • Переменные будет иметь говорящие имена. Это вопрос вкуса, как далеко это заходит. Одно практическое правило, которое я нашел в этом вопросе, гласит:

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

Заключение

Этот код мы получаем, когда применяем все приведенные выше правила.

#include <cs50.h>
#include <stdio.h>
#include <limits.h>

#define CENTS_PER_DOLLAR  100uL
#define CENTS_PER_QUARTER  25uL
#define CENTS_PER_DIME     10uL
#define CENTS_PER_NICKEL    5uL

int main(void)
{
    // Prompt user for money value they want in change
   unsigned dollars;
   float    flt_dollars;
   do {
       // I don't know your <cs50.h> library - if there is sth like get_int(), please use it!
       flt_dollars = get_float("Change owed: ");
       dollars     = (unsigned) flt_dollars;
   } while ((flt_dollars >= 0.0) && (flt_dollars < (float)UINT_MAX) && (dollars < UINT_MAX / CENTS_PER_DOLLAR);

   // Convert total change value to cents
   unsigned cents = dollars * CENTS_PER_DOLLAR;

#ifdef COIN_TYPES
   unsigned num_quarters = cents / CENTS_PER_QUARTER;
   // Introduce modulus, so that the remainder of previous step division can be used to determine the next step for number of coins
   unsigned num_dimes    = (cents % CENTS_PER_QUARTER) / CENTS_PER_DIME;
   unsigned num_nickels  = ((cents - (x * CENTS_PER_QUARTER) - (y * CENTS_PER_DIME)) % CENTS_PER_DIME) / CENTS_PER_NICKEL;
   unsigned num_cents    = ((cents - (x * CENTS_PER_QUARTER) - (y * CENTS_PER_DIME) - (z * CENTS_PER_NICKEL)) % CENTS_PER_NICKEL);

   // Total number of coins is sum of previous steps
   unsigned num_coins    = num_quarters + num_dimes + num_nickels + num_cents;

   printf ("%d\n", num_coins);
   printf ("%d\n", num_quarters);
   printf ("%d\n", num_dimes);
   printf ("%d\n", num_nickels);
   printf ("%d\n", num_cents);
#else
   // Quarters
   unsigned num_coins_tmp =  cents / CENTS_PER_QUARTER;
   unsigned num_coins     =  num_coins_tmp;
   cents                  -= num_coins_tmp * CENTS_PER_QUARTER;
   // Dimes
   num_coins_tmp =  cents / CENTS_PER_DIME;
   num_coins     += num_coins_tmp;
   cents                  -= num_coins_tmp * CENTS_PER_DIME;
   // Nickels
   num_coins_tmp =  cents / CENTS_PER_NICKEL;
   num_coins     += num_coins_tmp;
   cents                  -= num_coins_tmp * CENTS_PER_NICKEL;
   // Cent pieces
   num_coins     += cents;

   printf ("%d\n", num_coins);
#endif /* COIN_TYPES */
}
...