Quantcast
Channel: Блогосфера
Viewing all articles
Browse latest Browse all 33007

Васил Колев: 2011-12-09 fizzbuzz

$
0
0

Странен сезон е. Сутринта валя, два дни подред пия, едвам намирам време да си свърша работата (не щото е много, а щото нещата се припокриват и има чакане) и съм пил вечерта, че да блогна.

Всички вероятно са чували какво е fizzbuzz, но пак да го кажа, накратко – задачка да се отпечатат числата от 1 до 100, като ако числото се дели на 3, вместо него се печата “Fizz”, ако се дели на 5 вместо него се печата “Buzz”, а ако се дели на 15 вместо него се печата “FizzBuzz”.
(това се дава на интервюта да се види дали човека изобщо може да програмира)

Оригиналното решение, което всеки може да напише на листче (говорим само за C) изглежда така:

for (i=1;i< =100;i++) {
	if (i%3==0) printf("Fizz");
	if (i%5==0) printf("Buzz");
	if (i%3!=0 && i%5!=0) printf("%d",i);
	printf("\n");
}

(което е почти същото на повечето езици за програмиране)

Оптимизираният вариант, който ми хрумна изглеждаше по следния начин:

int i,p;
char *s[4]= {"%d\n", "Fizz\n", "Buzz\n", "FizzBuzz\n"};
int s3[3]={1,0,0},s5[5]={2,0,0,0,0};

for (i=1;i<=100;i++) {
	p= s3[i%3] | s5[i%5];
	printf(s[p],i);
}

(накратко си избира максимално бързо format string-а, като се прави индекса му от два бита – единия за дали се дели на 3, другия на 5, след което се вика printf() с него и аргумента, а ако във format string-а няма “%”, то вторият аргумент изобщо не се гледа)

Методът с lookup таблиците е известен от зората на програмирането и е един от най-хубавите примери за time-memory trade-off.

Това решение няма branch-ове (т.е. if() и компания) в основния си код, но пък разчита на printf(), който не е особено бърз. На пиенето на Титов му хрумна, че може да се направи масив от функции, които да правят различните неща, но па на мен идеята за call и ret не ми хареса особено, за това се замислих и открих, че в C всъщност има масив от label-и. Съответно, ето извратено и максимално бързо решение:

int i,p;
static void *pos[4]= {&&digit, &&fizz, &&buzz, &&fizzbuzz};
int s3[3]={1,0,0},s5[5]={2,0,0,0,0};
char buff[2048];
char dgts[16]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
int buffpos=0;

for (i=1;i<=100;i++) {
	p= s3[i%3] | s5[i%5];
	goto *pos[p];

fizz:
	memcpy(&buff[buffpos],"Fizz", 4);
	buffpos+=4;
	goto end;
buzz:
	memcpy(&buff[buffpos],"Buzz", 4);
	buffpos+=4;
	goto end;
fizzbuzz:
	memcpy(&buff[buffpos],"FizzBuzz", 8);
	buffpos+=8;
	goto end;
digit:
	buff[buffpos++]=dgts[i/16];
	buff[buffpos++]=dgts[i%16];
end:
	buff[buffpos++]='\n';
}
write(1,buff, buffpos);

(т.е. както в предния пример имаме масив от format string-ове, тук имаме масив от позиции, на които директно можем да скочим. Също така не викаме никакви външни функции (memcpy се inline-ва и е от едни по-бързи инструкции) и единствения branch, който имаме е за for() (който също може да се избегне с една lookup таблица и едни goto-та, ама това вече ще е гадно)

Човек, способен да напише подобен код на интервю вероятно трябва да го пратят в Карлуково.

Ако на някой му хрумне нещо по-забавно, да пише :)

Update: Оптимизация – да се изместят p3, p5 и dgts като глобални променливи, понеже ако са във функцията, компилатора ги прави на много mov-ове.


Viewing all articles
Browse latest Browse all 33007

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>