Комментарии 46
Под наиболее простым и красивым решением я понимаю наиболее компактный алгоритм, который использует наименьшее количество условных операторов и операций сравнения. Такой алгоритм будет иметь наименьшую временную сложность, к нему и будем стремиться.
Вы это серьезно?
Например, с помощью строк можно избавиться от одной операции сравнения.
А стоимость операций со строками вы не учитываете вообще?
Ну и да, вы бы все-таки писали результирующую временную сложность (в нотации большого О) для каждого получившегося у вас варианта.
if(str=="") str=to_string(i);
Даже в самой простой реализации
strcmp
тут будет еще одно условие.Полученный алгоритм содержит всего две операции сравнения и два условных оператораИ четыре вызова подпрограмм. Что даёт неограниченный потенциал в неучтённых операциях сравнения и циклов.
FizzBuzz, или почему программисты не умеют программировать
using namespace std;
Невероятное зло. Нет, я понимаю, что во всех дурацких учебниках есть эта строчка, но в реальном коде её использовать очень плохо. Используйте fully-qualified name или импортируйте только то, что вам нужно, а не всё пространство имён, напримерusing cout = std::cout
;- Зачем внесли объявление string в тело цикла? allocation/deallocation на каждую итерацию, очень медленно.
- Строковые операции — это тоже медленно.
n. FizzBuzz на хабре? Серьёзно?
P. S. Первый, "классический" вариант — самый адекватный
Что, если завтра вместо вывода cout, например, нужно будет делать сетевой запрос? Два отдельных запроса "Fizz" и "Buzz" станут неэквивалентны одному "FizzBuzz", и все эти блестящие оптимизации на уменьшение числа условий выйдут боком.
Что, если завтра добавится третий, четвёртый пятый вариант, и нужно будет на любую комбинацию делителей выводить какую-то свою строку? Как быстро этот супер-оптимизированный код станет вконец нечитабельным?
Короче, я бы в этой задаче смотрел на другое. Если кандидат поэкономил условные операторы — попросил бы переписать под новые условия и посмотрел, как он это будет делать.
Если бы я написал примеры реализации алгоритма в виде блок схем, то все вопросы касательно использования строк и отсутствия рассмотрения обработки строк в качестве критерия временной сложности отпали бы.Нет.
при решении этой задачи интересовала именно оптимизация самого алгоритмаТакой подход очень трудно назвать словом «оптимизация».
Один мой старый знакомый, где-то году в 99-м присутствовал при (да, именно так, сам не он в том проектене кодил) при начале разработки некоего сетевого игрового проекта (который так ине взлетел, но то другая история). Протокол сочиняли сами, ну и команды, посылаемые клиентом, были там просто словами, т.е. короткими строками. Тот мой знакомый, как программист старой (уже на тот момент - "старой") закалки, глядя на это, предложил сделать все строки 4-символьными, чтобы их можно было сравнивать, как 32-битовые целые, что ускорило бы разбор сервером входящих пакетов (вместо сравнений строк и if-else-if..., сделать просто switch по этим числам). Ну и сделал пробный пример. Сравнил скорости. И был очень удивлён отсутствием сколько-то осмысленного выигрыша у своего варианта. Это было на процессорах, которые в 99-м были уже уровня "доступно для маленького стартапа".
Хотя, да, я сам ещё помню времена, когда деление было действительно более дорогой по тактам процессора операцией, чем инкремент.
Старый некромант решил на ночь глядя рассказать историю!
ну давайте понекромансим, чего уж там
деление - алгоритмически более накладная операция чем инкремент/сложение. То есть если тут что то смогут ускорить то это будет фундаментальный прорыв. Соотв. моя рекомендация избегать деление основана на фундаменте математики.
предложил сделать все строки 4-символьными, чтобы их можно было сравнивать, как 32-битовые целые, что ускорило бы разбор сервером входящих пакетов (вместо сравнений строк и if-else-if..., сделать просто switch по этим числам)
этими вещами должен заниматься компилятор. То есть тут Ваш знакомый заложился на архитектуру процессора и тупость компилятора - ни то ни другое не постоянны :)
В общем я к тому что поведение мое и Вашего знакомого внешне похожи но мотивы сильно разные :)
for(int i=1; i<=100; ++i)
if(!(i%3)||!(i%5))
std::cout<< (!(i%3)?!(i%5)? «FizzBuzz»:«Fizz»:«Buzz» ) <<std::endl;
else std::cout<<i<<std::endl;
for(int i=1, c=1, d=1; i<=100; ++i, ++c, ++d){
if(3 == c) c = 0;
if(5 == d) d = 0;
if(c && d ) std::cout<< i;
if(!c) std::cout<< «Fizz»;
if(!d) std::cout<< «Buzz»;
std::cout <<std::endl;
}
и никаких делений с остатками и выкрутасов с тернарниками. Потом другие пацаны прийдут — прочитать смогут, предъявлять не будут.
for(int i=1; i<=100; ++i)
if(i%3 && i%5)
{
std::cout<<i<<std::endl;
continue;
}
std::cout<< (!(i%3)?!(i%5)? «FizzBuzz»:«Fizz»:«Buzz» ) <<std::endl;
А как нормальные пацаны отнесуться к этому?
input([ «FizzBuzz» if not x%3 and not x%5 else «Fizz» if not x%3 else «Buzz» if not x%5 else x for x in range(1, 101)])
Напишите программу, которая выводит на экран числа от 1 до 100. При этом вместо чисел, кратных трем, программа должна выводить слово Fizz, а вместо чисел, кратных пяти — слово Buzz. Если число кратно пятнадцати, то программа должна выводить слово FizzBuzz. Задача может показаться очевидной, но нужно получить наиболее простое и красивое решение.
Вообще, надо заметить, что здесь изрядно переврано исходное условие задачи.
Каноничное:
if(i%3==0) cout<<«Fizz»;
if(i%5==0) cout<<«Buzz»;
if(i%3!=0 && i%5!=0) cout<<i;
cout<<endl;
Альтернативное:
if(i%3==0) cout<<«Fizz»;
if(i%5==0) cout<<«Buzz»;
else if(i%3!=0) cout<<i;
cout<<endl;
Например, для .NET оба варианта дадут идентичный итоговый результат, после генерации кода. Правда при одном маленьком, но очень важном условии: если собирать с конфигурацией Release, т.к. компилятор развернет else if в каноничный вариант, что легко увидеть, если посмотреть IL-код. А вот вариант при сборке с конфигурацией Debug будет различаться. В данном случае второй вариант будет короче на 7 байт и 6 операторов.
Я думаю для других языков/компиляторов ситуация будет похожей. Плюс подвох уже в самой постановки вопроса ожидается. Правда я сомневаюсь, что для собеседования это подходящий вопрос, т.к. мало кто на нервах сразу сообразит, где собака зарыта.
С точки зрения результата задача описана верно и даже убрана исходная подсказка
Это не подсказка, в том-то и дело. FizzBuzz — это задача без подвоха.
вместо .NET должно быть C#.NET
В C#.net есть
cout
и <<
в том значении, в котором вы его употребили?Но эта задача мне нравится еще тем, что она легко превращается в веселый квест: сравнить 2 очень похожих реализации алгоритма с использованием любых инструментов.
И зачем?..
Это не подсказка, в том-то и дело. FizzBuzz — это задача без подвоха.
Это намек на преждевременную оптимизацию
В C#.net есть cout и << в том значении, в котором вы его употребили?
Есть Console.Write, но для сохранения общей стилистики я использовал оригинальную нотацию.
И зачем?..
Вы не поверите, но исключительно для своего удовольствия. Я наверно извращенец, но подобного рода мелочи мне оставляют много приятных впечатлений и хорошо разгружают мозги))
Это намек на преждевременную оптимизацию
Неа. В оригинальной задаче никакого намека нет. Там все правда просто.
Правда на php, на на си примерно так же будет выглядеть:
$list = array("", "Fizz", "Buzz", "FizzBuzz");
for($i = 1; $i <= 100; ++$i) {
$index = (($i % 3)?0:1) + (($i % 5)?0:2);
echo ($index > 0?$list[$index]:$i).' ';
}
Можно избавиться от пустого элемента массива, но тогда нужно будет вычитать 1 из $index и немного поменять условие
for(int i = 0; i <= 100; i++)
{
if(i%3 == 0)
std::cout << "Fizz" << std::endl;
if(i%5 == 0)
{
std::cout << "Buzz" << std::endl;
continue;
}
std::cout << i << std::endl;
}
два условных оператора, два оператора сравнения и никаких телодвижений со строками
for(int i = 0; i <= 100; i++)
{
if(i%15 == 0)
{
if(i%3 == 0)
std::cout << "Fizz" << std::endl;
else
std::cout << "Buzz" << std::endl;
continue;
}
std::cout << i << std::endl;
}
for(int i = 0; i <= 100; i++)
{
if(i%3 == 0)
{
std::cout << "Fizz" << std::endl;
if(i%5 == 0)
std::cout << "Buzz" << std::endl;
continue;
}
if(i%5 == 0)
{
std::cout << "Buzz" << std::endl;
continue;
}
std::cout << i << std::endl;
}
Если число кратно пятнадцати, то программа должна выводить слово FizzBuzz.Ваша выводит
Fizz, что совпадает с выводом для конца последовательности (99, 100):
Buzz
Fizz
Buzz
for(int i = 0; i <= 100; i++)
{
if(i%3 == 0)
{
std::cout << "Fizz";
if(i%5 == 0)
std::cout << "Buzz";
std::cout << std::endl;
continue;
}
if(i%5 == 0)
{
std::cout << "Buzz" << std::endl;
continue;
}
std::cout << i << std::endl;
}
А еще так можно, со строками уже с этим дурацкими:
#include <iostream>
#include <vector>
#include <string>
int main()
{
for(int i = 1; i <= 100; i++)
{
std::vector<std::string> str = {std::to_string(i), "Fizz", "Buzz", "FizzBuzz"};
int ind = !(i%3) + (!(i%5)) * 2;
std::cout << str[ind] << std::endl;
}
}
Не правда ли изящно? (C++11 нужен)
Но не за 5 минут последнее решение реализовал. Сначала крутилась в голове чего то, потом только сформулировал принцип и алгоритм. Где то минут 10 — 15 наверно потратил чтобы полностью реализовать и проверить.
Быдлокодер, похоже, я. Либо пишу быстро код с кучей багов, либо трачу дофига времени.
Как научиться решать такие задачки за приемлемое время?
Решение задачи FizzBuzz