Задача: C собеседования для приема на работу

  • Автор темы ensane
  • Дата начала
E

ensane

#1
Вот задачка, которая была предложена на собеседовании при приеме на работу штатного юнита на должность "Программист C++". Из 6 соискателей, только 4 смогли ее решить. И у троих она работала не вполне правильно. То ли Брест совсем скуден на таланты, то ли все смотались в Минск строить парк высоких технологий, то ли что... Предлагаю вам. Чисто из интереса голову поломать ;)
Дано: есть файл, содержащий некую конфигурацию. Файл состоит из строк вида:
1. name=value
Где name - имя параметра, а value - его значение. Значение может быть 4 типов:
1.1: целое число. Считаем параметр целым, если его значение записано целым числом (например val=1)
1.2: вещественное с плавающей запятой. К вещественным относим все числа, которые не попали в п 1.1 ( val=1.0 )
1.3: строка. Считаем параметр строкой, если его значение заключено в двойные кавычки "". Под значением понимаем ВСЕ, ЧТО ВНУТРИ КАВЫЧЕК, КРОМЕ САМИХ КАВЫЧЕК (val="string")
1.4: ссылка на другой параметр. Значение начинается с символа &, за которым следует имя другого параметра. (val2=&val1. )
2. Символы пробела и табуляции игнорируются, за исключением случаем, когда они входят в значение строкового параметра (val=" string").
3. Если строка начинается с символа (с учетом пункта 2) "#", то она считается комментарием и игнорируется.
4. В одной строке не может быть двух пар "имя=значение"
Надо:
Написать класс, который будет реализовывать следующее:
1. Считывать файл конфигурации с диска в память
2. Проверять на наличие ошибок и в случае наличия оных выдавать список строк с указанием на ошибки
3. Реализовывать следующие методы:
3.1. isSet(const std::string param_name) - имеется ли параметр с указанным именем?
3.2. getType(const std::string param_name) - получить тип параметра. (TP_INT, TP_DBL, TP_STR), для ссылки - тип параметра, на который ссылаемся
3.3. getValueAsInt(const std::string param_name), getValueAsDouble, getValueAsStr - получить значение, для ссылки - значение параметра, на который ссылаемся.
3.4. При реализации п3.3. по возможности предусмотреть конвертацию типов.

Условия выполнения - дома. Полученный код должен был быть представлен в двух файлах *.h и *.cpp
Проверялся на MSVS 8.0
 
R

Rififi

#2
на C++ что-то вроде того...
нет первых двух пунктов, ибо очень лень возиться с файловыми потоками, поэтому считывание данных происходит из строки и обработка ошибок максимально упрощена.

.h
C++:
#pragma once

#include <map>
#include <iostream>
#include <sstream>
#include <locale>

#include <boost/spirit/home/qi.hpp>
#include <boost/spirit/home/support/multi_pass.hpp>
#include <boost/spirit/home/phoenix.hpp>

namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace phx = boost::phoenix;

#include <boost/variant.hpp>
#include <boost/fusion/adapted.hpp>
#include <boost/lexical_cast.hpp>

#include <boost/mpl/at.hpp>
#include <boost/mpl/map.hpp>
namespace mpl = boost::mpl;

typedef boost::make_recursive_variant<int, float, std::string, boost::recursive_variant_*>::type data_type;
typedef std::pair<std::string, data_type> key_value_type;

typedef std::vector<std::pair<std::string, std::string> > alias_list;
typedef std::map<std::string, data_type> configuration_map;

enum DataType
{
TP_INT,
TP_FLOAT,
TP_STRING
};

namespace detail {

typedef mpl::map<
mpl::pair<int, mpl::int_<TP_INT> >,
mpl::pair<float, mpl::int_<TP_FLOAT> >,
mpl::pair<std::string, mpl::int_<TP_STRING> >
> DataTypeMapper;

}

class Conf
{
public:
Conf(const std::string& source);

class TypeCaster
{
public:
TypeCaster(const data_type& val) : val_(val)
{
}

template <typename T>
T As() const
{
return boost::apply_visitor(TypeCasterVis<T>(), val_);
}

DataType Type() const
{
return boost::apply_visitor(TypeFinderVis(), val_);
}

protected:
template <typename T>
struct TypeCasterVis : public boost::static_visitor<T>
{
template <typename U>
T operator()(const U& u) const
{
return cast(u, boost::is_convertible<U, T>());
}

template <>
T operator()(data_type* const& val) const
{
if (val == NULL)
return T();

return boost::apply_visitor(*this, *val);
}

template <typename U>
static T cast(U u, mpl::true_)
{				
return static_cast<T>(u);
}

template <typename U>
static T cast(U u, mpl::false_)
{
return boost::lexical_cast<T>(u);
}
};

struct TypeFinderVis : public boost::static_visitor<DataType>
{
template <typename T>
DataType operator()(const T&) const
{
using detail::DataTypeMapper;
return (DataType) typename mpl::at<DataTypeMapper, T>::type::value;
}

template <>
DataType operator()(data_type* const& val) const
{
return boost::apply_visitor(*this, *val);
}
};

protected:
const data_type& val_;
};

const TypeCaster operator[](const std::string& name) const
{
const configuration_map::const_iterator found = conf_.find(name);
if (found == conf_.end())
throw std::runtime_error("Variable is not found.");

return TypeCaster(found->second);
}

size_t size() const
{
return conf_.size();
}

protected:
template <typename Range, typename Arg>
void parse_impl(const Range& rng, Arg& arg, alias_list& aliases);

public:
configuration_map conf_;
};
.cpp
C++:
#include "parse_config.h"

#include <math.h>
#include <assert.h>

template <typename InputIterator, typename Skipper>
struct grammar : public qi::grammar<InputIterator, configuration_map(), Skipper>
{
grammar(alias_list& aliases) : grammar::base_type(start_)
{
unesc_char.add
("\\a", '\a')("\\b", '\b')("\\f", '\f')("\\n", '\n')
("\\r", '\r')("\\t", '\t')("\\v", '\v')
("\\\\", '\\')("\\\'", '\'')("\\\"", '\"')
;

start_ = +pair_;
pair_ %= key_ >> '=' >> value_(phx::at_c<0>(qi::_val)) >> qi::eol;
key_ = qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z_0-9");
value_ = string_ | float_ | qi::int_ | ref_(qi::_r1);
ref_ = '&' >> key_ [
qi::_val = nullptr,
phx::push_back(phx::ref(aliases), phx::construct<alias_list::value_type>(qi::_r1, qi::_1))
];
string_ = '\"' >> *(unesc_char | "\\x" >> qi::hex | (qi::print - '\"')) >> '\"';
}

#pragma region Rules

qi::rule<InputIterator, configuration_map(), Skipper> start_;
qi::rule<InputIterator, key_value_type(), Skipper> pair_;

qi::rule<InputIterator, std::string()> key_;
qi::rule<InputIterator, data_type(const std::string&)> value_;
qi::rule<InputIterator, data_type*(const std::string&)> ref_;

qi::rule<InputIterator, std::string()> string_;

template <typename T>
struct strict_real_policies : qi::real_policies<T>
{
static bool const expect_dot = true;
};

template <typename T, template <typename> class policy>
struct real_parser
{
typedef qi::real_parser<T, policy<T> > type;
};

typename real_parser<float, strict_real_policies>::type float_;

qi::symbols<const char, const char> unesc_char;

#pragma endregion
};

Conf::Conf(const std::string& source)
{
alias_list aliases;
parse_impl(source, conf_, aliases);

for (alias_list::const_iterator it = aliases.begin(); it != aliases.end(); ++it)
{
const configuration_map::iterator origin = conf_.find(it->second);
if (origin != conf_.end())
conf_[it->first] = &origin->second;
}
}

template <typename Range, typename Arg>
void Conf::parse_impl(const Range& rng, Arg& arg, alias_list& aliases)
{
typedef typename boost::range_iterator<const Range>::type InputIterator;

InputIterator begin = boost::const_begin(rng);
const InputIterator end = boost::const_end(rng);

typedef qi::rule<InputIterator> skipper_type;
const skipper_type skipper = qi::char_(" \t") | '#' >> *(qi::char_ - qi::eol) >> qi::eol;

const grammar<InputIterator, skipper_type> g(aliases);
const bool ok = qi::phrase_parse(begin, end, g, skipper, arg);
if (!(ok && begin == end))
throw std::runtime_error("Parsing failed.");
}
Использование:
C++:
	const std::string source = 
"#comment \n"
"name_int = 100\n"
"name_float = 258.23\n"
"name_str = \"123 #some \\\"string\\\" 456.0\"\n"
"str_alias = &name_str\n"
"int_alias = &name_int\n"
"none = &zzz\n"
"# more comment ...\n"
"int_alias2 = &int_alias\n"
"# more comment ...\n"
;

const Conf conf(source);

assert(conf.size() == 7);

assert(conf["name_int"].Type() == TP_INT);
assert(conf["name_float"].Type() == TP_FLOAT);
assert(conf["name_str"].Type() == TP_STRING);

assert(conf["int_alias"].Type() == TP_INT);
assert(conf["int_alias2"].Type() == TP_INT);	

assert(conf["name_int"].As<int>() == 100);
assert(conf["name_int"].As<std::string>() == std::string("100"));
assert(conf["name_float"].As<long>() == 258);
assert(fabs(conf["name_float"].As<double>() - 258.23) < 0.001);

assert(conf["name_str"].As<std::string>() == std::string("123 #some \"string\" 456.0"));
assert(conf["str_alias"].As<std::string>() == std::string("123 #some \"string\" 456.0"));

assert(conf["none"].As<int>() == 0);
assert(conf["none"].As<std::string>() == std::string());

assert(conf["name_int"].As<float>() == 100.0f);
assert(conf["name_float"].As<float>() == 258.23f);

assert(conf["int_alias"].As<int>() == 100);
assert(conf["int_alias2"].As<float>() == 100.0f);
 
E

ensane

#3
Rififi
Фэйл. Даже не начало собираться, выругавшись на отсутствие boost'а. После такого я предлагал соискателю переделать и придти на другой день.
 

Kmet

Java Team
25.05.2006
1 036
8
#4
ensane
не смогли собрать приложение с бустом в зависимостях?! ай яй яй. Фейл ваш. Ограничений на использование сторонних библиотек в задание нету. Наличие билд скрипта тоже не прописано.
 
R

Rififi

#6
ensane

Даже не начало собираться, выругавшись на отсутствие boost'а.

В предложение перед словом boost нужно добавить местоимение "какого-то". Тогда будет совсем шикарно.
 
E

ensane

#7
Rififi,
Kmet
Есть более 18 либ, почти реализующих функционал, требуемый задачей. (18 - это то, что я нашел в гугле за несколько минут поисков). Может, сразу подключим их и не будем париться? Вся задачка решится за 10 минут и сотню строчек кода, а то и меньше. Так что абсолютный фэйл.
 

Kmet

Java Team
25.05.2006
1 036
8
#8
А вот не надо передергивать. Rififi выбрал правильный инструмент. Парсить без использование граматик и генераторов парсеров... Это надо сильно себя не любить. Имхо, это вообще тест на адекватность. Вот Rififi его прошел, а вы,ensane, нет.
 
E

ensane

#9
Kmet
Еще раз намекаю: если бы это была задача на выбор инструмента для разработки, то и в этом случае использование boost'а было бы фэйлом. Потому как есть более подходящие для данной задачи инструменты.
Кстати, это второй фэйл. Первый фэйл - не были заданы необходимые уточняющие вопросы перед началом решения. На собеседовании мы за это сразу снижали баллы.
 

hosm

* so what *
18.05.2009
2 442
6
#10
Первый фэйл - не были заданы необходимые уточняющие вопросы перед началом решения.
хм, а к чему это? А разве не вы отвечаете за точную постановку задачи перед тем, как предоставить ее?
Почему вы считаете, что умолчав или не описав четко требования к решению, вы вправе осуждать других, что они сделали так, как им было удобней?
 
04.09.2006
2 566
3
#11
хм, а к чему это? А разве не вы отвечаете за точную постановку задачи перед тем, как предоставить ее?
ensane клонит к тому, что идеальный кандидат (с его точки зрения) должен был сначала уточнить требования, а только потом кидаться на реализацию. Т.е. сначала думать, а потом делать. Кстати, это очень распространённая практика на собеседованиях: заставить кандидата думать
 

lazybiz

Well-known member
03.11.2010
1 339
0
#12
Надо:
Написать класс, который будет реализовывать следующее:
...
3.3. getValueAsInt(const std::string param_name), getValueAsDouble, getValueAsStr...
Kmet
Если он все сделал правильно, то почему он не реализовал это?

Вот Rififi его прошел, а вы,ensane, нет.
Это вот как он его прошел позвольте узнать. Условия задания для кого даны?
 
E

ensane

#13
OKEN
Почему вы считаете, что умолчав или не описав четко требования к решению, вы вправе осуждать других, что они сделали так, как им было удобней?
Потому что это не олимпиадная задачка и не задача на зачет по предмету.
European
В точку.
 

hosm

* so what *
18.05.2009
2 442
6
#14
ensane Задача выбора, уточнения и согласования требований - это задача аналитика, технического консультанта и/или архитектора системы. И вот как раз программист не обязательно должен играть постоянно в проекте роль аналитика и проводить кучу времени на телефоне/в скайпе/по мейлу, уточняя каждый раз требования у заказчика. И ваша задача как заказчика тестового задания - предоставить всю требуемую для решения задачи информацию. Если это не оговорено в требованиях заказчика, то программист вправе выбирать средства и методы решения на свое усмотрение, и качество выбора уже зависит от квалификации программиста.
У вас настолько простые проекты, что нет никакого распределения ролей в проекте - каждый программист отвечает только за один проект, где он сам себе руководитель, технический консультант, аналитик, архитектор, кодировщик, тестировщик, технический писатель и внедренец в одном лице? Просто у нас есть такое разделение, и человек, занимающийся уточнением требований с заказчиком - это либо бизнес-аналитик, либо программист-аналитик или конструктор системы, и не всякому пришедшему на работу программисту это стоит позволять делать. Если написание тех.документации и переговоры с заказчиком - это было оговорено в требованиях к вакансии - да, пожалуйста, претензии снимаются...
European И 6 человек, пришедших на собеседование, должны были долго и нудно выяснять у автора темы практически одно и то же, что они поленились описать в первоначальной постановке? Рациональное использование времени, да, нефиг делать =)
 
04.09.2006
2 566
3
#15
European И 6 человек, пришедших на собеседование, должны были долго и нудно выяснять у автора темы практически одно и то же, что они поленились описать в первоначальной постановке? Рациональное использование времени, да, нефиг делать =)
Из 6-ти только 1-2 уточнят требования, а вот все остальные, с большОй долей вероятности - изобретатели велосипедов. То что ты говоришь все очень правильно, но программист должен уметь работать и без аналитика/консультанта/архитектора. А то вечером перед релизом он тебе такой велосипед с квадратными колесами в репозиторий зальёт, что будешь полночи откатывать, а потом перед заказчиками оправдываться
 

Akupaka

А че я?.. О.о
04.10.2007
3 360
1
#16
ensane клонит к тому, что идеальный кандидат (с его точки зрения) должен был сначала уточнить требования, а только потом кидаться на реализацию. Т.е. сначала думать, а потом делать. Кстати, это очень распространённая практика на собеседованиях: заставить кандидата думать
А по-моему эта задача никак не отображает умение думать. Разве только знание синтаксиса языка и умение использовать его конструкции.

Первый фэйл - не были заданы необходимые уточняющие вопросы перед началом решения. На собеседовании мы за это сразу снижали баллы
Так, а с чего вы там у себя решили, что эти "необходимые уточняющие вопросы" являются абсолютно необходимыми для решения поставленной задачи?

Потому что это не олимпиадная задачка и не задача на зачет по предмету
А чего ж тогда оценка кандидатов идет по этому принципу? "Мы не смогли открыть Вашу программу, поэтому Ваша работа не защитана."

А OKEN права во всем.