При формировании строки из каких-нибудь флагов, например, или ещё из чего-то, что требует разделителя между равнозначными частями, приходится проверять, первый ли это элемент, надо ли вставлять разделитель. Это как-то надоедает. Есть ли какой-то стандартный паттерн с реализованным класс для такого?
Я сначала написал функцию:
std::string appendWithSep(std::string str, const std::string &sAppend, const std::string &sSep = " ")
{
if (sAppend.empty()) // Do nothing on empty appendreturn str;
if (str.empty())
{
str.append(sAppend);
}
else
{
str.append(sSep);
str.append(sAppend);
}
return str;
}
Но она меня тоже стала утомлять — надо туда передавать строку, к которой добавляем, и разделитель. Написал классик:
SP>со StringBuilder врядли много общего. В шарпах есть отдельный метод String.Join
Join сливает вместе несколько переданных значений, или содержимое контейнера? Не совсем то. Мне либо в цикле обычно надо, с каким-то условием, и конечно наверное правильно бы делать std::transform, foreach и тп, но я как-то попроще привык. Ну, или флаги какие проверить, и сделать текстовое представление для них. В принципе, флаги тоже можно в контейнер загнать, и строки для флагов рядом, и всё в цикле, но если надо один раз прямо тут и всё — то это какой-то оверхед
оно не сложно, оно по-другому. Я делал упор на функциональщину и неизменяемость. Для ФП каррировать какие-то функции в порядке вещей.
Подобный код я реализовывал как-то так:
enum class Mask {...};
enum class Flag {...};
MaskIter split (const Mask m) {...}
std::string_view to_string(const Flag f) {...}
split(mask) // разделяет битовое поле на элементы
| transform(to_string)
| join(", ")
В проекте Mask и Flag подчинялись единым требованиям. Поэтому можно было сделать общую функцию split.
А так конечно на любителя. Лично мне решения с мутабельным StringBuilder не нравятся. Особенно когда кто-то посреди веселья захочет вызвать modifySep.
Здравствуйте, пффф, Вы писали:
П>При формировании строки из каких-нибудь флагов, например, или ещё из чего-то, что требует разделителя между равнозначными частями, приходится проверять, первый ли это элемент, надо ли вставлять разделитель. Это как-то надоедает. Есть ли какой-то стандартный паттерн с реализованным класс для такого?
Видел/использовал такой 'паттерн':
string sep = ""; //пустая строка(или что-то другое, например "\t\t\t"), тут по желанию string, string_view, const char *, char, ... генератор разделителей
string result;
for (const auto& sflag : containerFlags)
{
result += sep;
result += sflag;
sep = ", ";
}
Здравствуйте, пффф, Вы писали:
П>При формировании строки из каких-нибудь флагов, например, или ещё из чего-то, что требует разделителя между равнозначными частями, приходится проверять, первый ли это элемент, надо ли вставлять разделитель. Это как-то надоедает. Есть ли какой-то стандартный паттерн с реализованным класс для такого?
Я бы сделал проще:
class separated
{
public:
enum class Empty { Skip, Add };
// при желании добавить конструкторы с char / string_viewexplicit separated( const std::string& separator, Empty e = Empty::Skip ): sep(separator), empty(e) {}
separated& operator<<( std::string_view s )
{
if ( !s.empty() || empty == Empty::Add )
{
if ( !first )
buffer.append( sep );
buffer.append( s );
first = false;
}
return *this;
}
const std::string& as_string() const & { return buffer; }
std::string as_string() const && { return std::move(buffer); }
private:
std::string sep;
std::string buffer;
Empty empty;
bool first = true;
};
Здравствуйте, пффф, Вы писали:
П>Привет!
П>При формировании строки из каких-нибудь флагов, например, или ещё из чего-то, что требует разделителя между равнозначными частями, приходится проверять, первый ли это элемент, надо ли вставлять разделитель. Это как-то надоедает. Есть ли какой-то стандартный паттерн с реализованным класс для такого?
... П>Ну, и вообще, может, как-то получше можно сделать?
Здравствуйте, пффф, Вы писали:
П>При формировании строки из каких-нибудь флагов, например, или ещё из чего-то, что требует разделителя между равнозначными частями, приходится проверять, первый ли это элемент, надо ли вставлять разделитель. Это как-то надоедает. Есть ли какой-то стандартный паттерн с реализованным класс для такого?
Прямо совсем стандартных нет, хотя можно наделать из готовых алгоритмов или диапазонов
П>В целом, функциональность устраивает, но название какое-то корявенькое. Может, как-то лучше можно назвать?
В разных языках это по-разному называется, тут уже накидали вариантов.
Так что называй как хочешь, лишь бы самому было понятно и приятно.
П>Ну, и вообще, может, как-то получше можно сделать?
П>ЗЫ Я не очень в си-шарпах и прочих явах разбираюсь, но там вроде есть всякие StringBuilder'ы. Вот это моё не имеет с ними сходства?
А это зависит от того, как хочется обращаться.
// поштучно вызывать - нужен объект с состоянием
ListBuilder b(",");
b(first);
b(second);
b(third);
return (string)b;
// применять к контейнеру / диапазону / паре итераторов - можно состояние засунуть в функцию
vector<string> vs = {first, second, third};
return build_separated_list(",", vs);
return build_separated_list(",", begin(vs), end(vs));
В первом случае, очевидно, у объекта есть параметр "чем чередовать" и состояние: аккумулятор и "добавлен ли первый"
struct ListBuilder {
std::string separator; // или string_view, но тогда нужно следить за временем жизни
std::string empty_case = "";
std::ostringstream ost; // string builderbool added = false;
auto& operator()(auto&& elem) {
if (added) ost << separator;
ost << std::forward<decltype(elem)>(elem);
added = true;
return *this;
}
/*explicit*/operator std::string() const {
if (!added) {
return empty_case; // либо кидаться ошибкой
}
return ost.str();
}
};
Во втором случае — всё ещё проще, состояние определяется местом в коде
template<class It>
void print_separated_list(std::ostream& ost, std::string_view separator, It begin, It end) {
if (begin == end) {
// return либо ошибка
}
ost << *begin;
++begin;
for ( ; begin != end; ++begin) ost << separator << *begin;
}
template<class It>
std::string build_separated_list(std::string_view separator, It begin, It end) {
std::ostringstream ost;
print_separated_list(ost, separator, begin, end);
return ost.str();
}
Здравствуйте, пффф, Вы писали:
П>При формировании строки из каких-нибудь флагов, например, или ещё из чего-то, что требует разделителя между равнозначными частями, приходится проверять, первый ли это элемент, надо ли вставлять разделитель. Это как-то надоедает. Есть ли какой-то стандартный паттерн с реализованным класс для такого?