The user enters his age, and the program issues it with the words "year", "year", "years" (for example, 23 years, 70 years, 4 years).

C code:

#include <stdio.h> int main(void) { unsigned short age = 0; printf("Введите ваш возраст: "); scanf("%u", &age); printf("Вам %d ", age); if(age > 4 && age < 21) puts("лет."); else if((age %= 10) == 1) puts("год."); else if(age > 1 && age < 5) puts("года."); else puts("лет."); return 0; } 

Is it possible to make the code more efficient, more compact? And how would you solve this problem?

Closed due to the fact that off-topic participants Kromster , Vladimir Glinskikh , aleksandr barakin , Peter Olson , Nick Volynkin 4 Aug '15 at 9:03 .

It seems that this question does not correspond to the subject of the site. Those who voted to close it indicated the following reason:

  • " Questionnaires are forbidden on Stack Overflow in Russian . To get an answer, rephrase your question so that it can be given an unambiguously correct answer." - Kromster, Vladimir Glinskikh, aleksandr barakin
If the question can be reformulated according to the rules set out in the certificate , edit it .

  • 7
    a small remark - the program must first of all be understandable to the person. With the help of ternary operators, it can be made a little shorter, but this will only make it more confusing, and this is a very significant drawback (despite the fact that this whole chain of ternary operators obviously will not make it faster, which could somehow justify these actions). Therefore, it is worth thinking three times before making the code less readable to suit the source size - DreamChild
  • @dr_kraken, the format "%u" in scanf() refers to the input of variables of type unsigned int (and you have unsigned short age ). You need to write if (scanf("%hu" , & age) == 1) {... `- For x86 and one variable in the program, this will work. And in other cases - not a fact (it will definitely not work in big-endian architectures (read about the order of bytes )). About the difference in the sizes of variables (2 bytes short and 4 bytes int, I just keep quiet). - avp
  • Thank you, I will consider. - dr_kraken
  • @dr_kraken, but what, now I will outline ... - avp

4 answers 4

 puts(((age % 10) == 1) ? "год." : ((age > 1 && age < 5) ? "года." : "лет.")) 

As an option.

 include <stdio.h> int main(void) { unsigned short age = 0; printf("Введите ваш возраст: "); scanf("%u", &age); printf("Вам %d ", age); puts(((age % 10) == 1) ? "год." : ((age > 1 && age < 5) ? "года." : "лет.")); return 0; } 

Or

 printf("Вам %d %s", age, ((age % 10) == 1) ? "год." : ((age > 1 && age < 5) ? "года." : "лет.")); 

Super compact and unreadable :)

  • Thanks for the answer. I like your option more than mine. - dr_kraken
  • In the second version, the code does not work correctly. At input, for example 44, 4 years are deduced. Because changing the value at age% = 10, changed the age in the first argument. - dr_kraken
  • Remove '=' and ok - Deadkenny
  • It works incorrectly (actually, like the original, only the errors are different). Test with 32 and 114. - avp
  • Deadkenny If you remove = from age% = 10, the program will work incorrectly, as in the first case. - dr_kraken

Note that we have 3 options:

  • years old
  • of the year
  • year

which will be the same in every hundred years.

Moreover, the word "years" is more common. It can also be noted that the whole of the second dozen is “years” and in all ten years from 5 onwards - these are also “years”.

The word "year" is used once in every ten (except the second ( 11 years )).

Therefore, we start with the word "years", then select the "year", and the remaining options will correspond to the word "year".

 #include <stdio.h> const char * years_name (unsigned int age) { const char *t = "лет"; if (age % 100 < 5 || age % 100 > 20) // 4 < age < 21 это точно "лет" if ((age %= 10) == 1) // здесь ИЗМЕНЯЕМ переменную age !!! // для вычисления условия с ЕДИНИЦАМИ лет в следующем if t = "год"; else if (age && age < 5) // если кончается на 0, то тоже оставим "лет" t = "года"; return t; } static inline int get_age (unsigned short *p) { return fputs("> ", stdout), fflush(stdout), scanf("%hu", p) == 1; } int main () { unsigned short age; while (get_age(&age)) printf ("Вам %d %s.\n", age, years_name(age)); return 0; } 

And we make all this in the form of a pair of functions and a trivial cycle in main .

  • avp Very interesting approach. I like it. Thank. - dr_kraken
  • Perhaps this (functionally equivalent) version of const char * years_name (unsigned int age) {const char * t = "years"; unsigned int d = age% 10, dd = age% 100; if (! (d == 0 || d> 4 || (dd> 4 && dd <21))) t = (d == 1)? "year": "year"; return t; } even better. - avp
  • one
    @avp: If I understand correctly, you forgot to take the remainder % 100 in the else if (age && age < 5) check. Since only it is important, I would start the function with age %= 100 . --- Update: Ah, no, it seemed you were taking the remainder % 10 . - VladD
  • one
    @VladD, I didn’t like this age change inside if , so I made 2 more options (in the comments). - avp
  • one
    @dr_kraken, practically in this case, the qualifier (or what is its correct name?) static is not needed, since we do not hook any external modules (libraries). Those. I often write it "out of habit" so that the local function does not overlap by name with some external one (otherwise there will be problems with linking). For the sake of interest, you can study the output of the nm utility (read man nm ) for the object (ie .o (get gcc -c)) and bootable modules with variables and functions with static and without. - avp

Let me have a word about this.

The code is so simple that it cannot be made more efficient . A good optimizing compiler from any source design will make almost the same object code. To improve the efficiency of such a simple code manually today is impossible, and even if it were, you should not set such a goal: it is micro-optimization, optimize better at the level of algorithms.

Now about the compactness . What is the purpose of writing more compact code? Efficiency will not add, readability - maybe, yes, and maybe not. More precisely, up to a certain point, a smaller amount of code means greater clarity, but starting from a certain moment clarity is lost. (An example will be at the end.)

So, the only purpose of improving the code may be improving its readability , and hence the likelihood of errors , and ease of support and modification .

Here is my option to improve readability.

 enum RussianNumberCase { Single, Some, Many }; RussianNumberCase get_case_for(unsigned int n) { unsigned int lastTwoDigits = n % 100; unsigned int ones = lastTwoDigits % 10; unsigned int tens = lastTwoDigits / 10; // 10..19 have special rules if (tens == 1) return Many; // otherwise, last digit determines the case switch (ones) { case 1: return Single; case 2: case 3: case 4: return Some; default: return Many; } } const char* years(int n) { // таблицу с индексацией пусть за меня построит оптимизатор, // не хочу ухудшать читаемость switch (get_case_for(n)) { case Single: return "год"; case Some: return "года"; case Many: return "лет"; } } 

The promised example of how compactness kills code comprehensibility. Compare:

 unsigned int SumOfBits(unsigned long long arg) { unsigned int result = 0; while (arg != 0) { unsigned int lastbit = arg & 1; if (lastbit != 0) result++; arg >>= 1; // delete last bit } return result; } 

and

 int r=0;do r+=arg&1;while(arg>>=1);return r; 
  • @Gimka: I would like to hear constructive criticism. - VladD
  • @VladD, good refactoring. Here, football in parallel with the rest of the cases will end, we will return to this topic and check how well the compiler can optimize everything. - If the younger digits of ages are evenly distributed, then returning Many by the first digit (calculated immediately) will work in half of the calls. - avp
  • one
    @Gimka: 1. First, a decent compiler will throw out all the code, including the pure strlen function (the human compiler knows about the cleanliness of library functions). 2. Secondly, the main idea of ​​my answer is not blind confidence in the optimizer, but the uselessness of micro-optimizations . If you do not understand the fact that readability is three orders of magnitude more important than micro-optimization, and carry out optimization without profiling, I would not take you even with middles. 3. Third, the real speed is achieved by a simple unreadable code: < pastebin.com/kx5NgenY >. Benchmark on health. - VladD
  • one
    @VladD, @Gimka, well, you have a heated argument ... It is clear that you can combine the options with beautiful and understandable logic and a table into one program. If the table is not yet initialized, the beautiful code fills it. Next, an efficient tabular algorithm works. - By the way, @Gimka, you are obviously exaggerating the inefficiency of the @VladD code ( RussianNumberCase get_case_for(unsigned int n) function RussianNumberCase get_case_for(unsigned int n) ). If you do not believe, then just write a meter and check. - avp
  • one
    @avp: Yeah, faced with this. To combat this, I’ll accumulate the result in all the benchmarks I’ve used here on the site and output it after stopping the timer to the console. But the compiler still has the right to perform all calculations in compile-time, so I also take the initial data from (for example) a random number generator or the current time. - VladD pm

This is a wow, from the most inconspicuous task they have inflated almost a war :) I’ll bring my Game of Life to mind and also bring it to the benefit of the public. And if, in fact, there is nothing special to optimize here, except to take into account statistical data and start checking with the most frequent ones, mine the rest is trifle, unless of course you are very rude. Well, collect all the ify in one switch.

  • one
    Well, we have a forum, any question on it is just a reason to talk. :-D - VladD