Estoy buscando la forma más elegante de convertir un vector de cuerdas en una cuerda. A continuación se muestra la solución que estoy usando ahora:
static std::string& implode(const std::vector<std::string>& elems, char delim, std::string& s)
{
for (std::vector<std::string>::const_iterator ii = elems.begin(); ii != elems.end(); ++ii)
{
s += (*ii);
if ( ii + 1 != elems.end() ) {
s += delim;
}
}
return s;
}
static std::string implode(const std::vector<std::string>& elems, char delim)
{
std::string s;
return implode(elems, delim, s);
}
¿Hay otros ahí fuera?
Respuestas:
Utilizar
boost::algorithm::join(..)
:#include <boost/algorithm/string/join.hpp> ... std::string joinedString = boost::algorithm::join(elems, delim);
Vea también esta pregunta .
fuente
std::vector<std::string> strings; const char* const delim = ", "; std::ostringstream imploded; std::copy(strings.begin(), strings.end(), std::ostream_iterator<std::string>(imploded, delim));
(incluya
<string>
,<vector>
,<sstream>
y<iterator>
)Si desea tener un final limpio (sin delimitador final), eche un vistazo aquí
fuente
std::ostream_iterator
constructor al final de la transmisión.Debería usar en
std::ostringstream
lugar destd::string
generar la salida (luego puede llamar a sustr()
método al final para obtener una cadena, por lo que su interfaz no necesita cambiar, solo la temporals
).A partir de ahí, podría cambiar a usar
std::ostream_iterator
, así:copy(elems.begin(), elems.end(), ostream_iterator<string>(s, delim));
Pero esto tiene dos problemas:
delim
ahora debe ser unconst char*
, en lugar de uno solochar
. No es gran cosa.std::ostream_iterator
escribe el delimitador después de cada elemento, incluido el último. Por lo tanto, necesitaría borrar el último al final o escribir su propia versión del iterador que no tiene esta molestia. Valdría la pena hacer lo último si tienes mucho código que necesita cosas como esta; de lo contrario, sería mejor evitar todo el lío (es decir, usarostringstream
pero noostream_iterator
).fuente
Como me encantan las frases ingeniosas (son muy útiles para todo tipo de cosas raras, como verá al final), aquí hay una solución usando std :: acumular y C ++ 11 lambda:
std::accumulate(alist.begin(), alist.end(), std::string(), [](const std::string& a, const std::string& b) -> std::string { return a + (a.length() > 0 ? "," : "") + b; } )
Encuentro esta sintaxis útil con el operador de flujo, donde no quiero tener todo tipo de lógica extraña fuera del alcance de la operación de flujo, solo para hacer una combinación de cadena simple. Considere, por ejemplo, esta declaración de retorno del método que formatea una cadena usando operadores de flujo (usando std;):
return (dynamic_cast<ostringstream&>(ostringstream() << "List content: " << endl << std::accumulate(alist.begin(), alist.end(), std::string(), [](const std::string& a, const std::string& b) -> std::string { return a + (a.length() > 0 ? "," : "") + b; } ) << endl << "Maybe some more stuff" << endl )).str();
Actualizar:
Como lo señaló @plexando en los comentarios, el código anterior sufre un mal comportamiento cuando la matriz comienza con cadenas vacías debido al hecho de que en la verificación de la "primera ejecución" faltan las ejecuciones anteriores que no han dado como resultado caracteres adicionales, y también - es extraño ejecutar una comprobación de "es la primera ejecución" en todas las ejecuciones (es decir, el código no está optimizado).
La solución para ambos problemas es fácil si sabemos con certeza que la lista tiene al menos un elemento. OTOH, si sabemos con certeza que la lista no tiene al menos un elemento, podemos acortar aún más la ejecución.
Creo que el código resultante no es tan bonito, así que lo agrego aquí como La solución correcta , pero creo que la discusión anterior todavía tiene merrit:
alist.empty() ? "" : /* leave early if there are no items in the list std::accumulate( /* otherwise, accumulate */ ++alist.begin(), alist.end(), /* the range 2nd to after-last */ *alist.begin(), /* and start accumulating with the first item */ [](auto& a, auto& b) { return a + "," + b; });
Notas:
alist[0]
para los vectores.[](auto&& a, auto&& b) -> auto& { a += ','; a += b; return a; })
que (en GCC 10) mejora el rendimiento en más de x10. Gracias a @Deduplicator por la sugerencia. Todavía estoy tratando de averiguar qué está pasando aquí.fuente
accumulate
para cuerdas. La mayoría de las otras respuestas son O (n) peroaccumulate
es O (n ^ 2) porque hace una copia temporal del acumulador antes de agregar cada elemento. Y no, la semántica de movimientos no ayuda.T
tiene un sobrecargadooperator+
(como lostring
hace) o si proporciona su propio functor, todas las apuestas están canceladas. Aunque me apresuré a decir que la semántica de movimientos no ayuda, no resuelven el problema en las dos implementaciones que he comprobado. Vea mis respuestas a preguntas similares .implode
ejemplo del OP ) están haciendo lo correcto. Son O (n), incluso si no llamanreserve
a la cuerda. Solo la solución que usa acumular es O (n ^ 2). No es necesario un código de estilo C.¿Qué pasa con una solución simple y estúpida?
std::string String::join(const std::vector<std::string> &lst, const std::string &delim) { std::string ret; for(const auto &s : lst) { if(!ret.empty()) ret += delim; ret += s; } return ret; }
fuente
string join(const vector<string>& vec, const char* delim) { stringstream res; copy(vec.begin(), vec.end(), ostream_iterator<string>(res, delim)); return res.str(); }
fuente
Me gusta usar esta acumulación de una sola línea (sin delimitador final):
std::accumulate( std::next(elems.begin()), elems.end(), elems[0], [](std::string a, std::string b) { return a + delimiter + b; } );
fuente
Especialmente con colecciones más grandes, desea evitar tener que verificar si todavía está agregando el primer elemento o no para asegurarse de que no haya separadores finales ...
Entonces, para la lista vacía o de un solo elemento, no hay iteración en absoluto.
Los rangos vacíos son triviales: devuelve "".
Los elementos individuales o multielemento se pueden manejar perfectamente mediante
accumulate
:auto join = [](const auto &&range, const auto separator) { if (range.empty()) return std::string(); return std::accumulate( next(begin(range)), // there is at least 1 element, so OK. end(range), range[0], // the initial value [&separator](auto result, const auto &value) { return result + separator + value; }); };
Ejecución de muestra ( requiere C ++ 14 ): http://cpp.sh/8uspd
fuente
Una versión que usa
std::accumulate
:#include <numeric> #include <iostream> #include <string> struct infix { std::string sep; infix(const std::string& sep) : sep(sep) {} std::string operator()(const std::string& lhs, const std::string& rhs) { std::string rz(lhs); if(!lhs.empty() && !rhs.empty()) rz += sep; rz += rhs; return rz; } }; int main() { std::string a[] = { "Hello", "World", "is", "a", "program" }; std::string sum = std::accumulate(a, a+5, std::string(), infix(", ")); std::cout << sum << "\n"; }
fuente
Aquí hay otro que no agrega el delimitador después del último elemento:
std::string concat_strings(const std::vector<std::string> &elements, const std::string &separator) { if (!elements.empty()) { std::stringstream ss; auto it = elements.cbegin(); while (true) { ss << *it++; if (it != elements.cend()) ss << separator; else return ss.str(); } } return "";
fuente
El uso de parte de esta respuesta a otra pregunta le da un esto unido, basado en un separador sin una coma al final,
Uso:
std::vector<std::string> input_str = std::vector<std::string>({"a", "b", "c"}); std::string result = string_join(input_str, ","); printf("%s", result.c_str()); /// a,b,c
Código:
std::string string_join(const std::vector<std::string>& elements, const char* const separator) { switch (elements.size()) { case 0: return ""; case 1: return elements[0]; default: std::ostringstream os; std::copy(elements.begin(), elements.end() - 1, std::ostream_iterator<std::string>(os, separator)); os << *elements.rbegin(); return os.str(); } }
fuente
Solución un poco larga, pero no se usa
std::ostringstream
y no requiere un truco para eliminar el último delimitador.http://www.ideone.com/hW1M9
Y el codigo:
struct appender { appender(char d, std::string& sd, int ic) : delim(d), dest(sd), count(ic) { dest.reserve(2048); } void operator()(std::string const& copy) { dest.append(copy); if (--count) dest.append(1, delim); } char delim; mutable std::string& dest; mutable int count; }; void implode(const std::vector<std::string>& elems, char delim, std::string& s) { std::for_each(elems.begin(), elems.end(), appender(delim, s, elems.size())); }
fuente
Esto es lo que uso, simple y flexible.
string joinList(vector<string> arr, string delimiter) { if (arr.empty()) return ""; string str; for (auto i : arr) str += i + delimiter; str = str.substr(0, str.size() - delimiter.size()); return str; }
utilizando:
string a = joinList({ "a", "bbb", "c" }, "!@#");
salida:
a!@#bbb!@#c
fuente
solo agrega !! String s = "";
for (int i = 0; i < doc.size(); i++) //doc is the vector s += doc[i];
fuente
prueba esto, pero usando vector en lugar de lista
template <class T> std::string listToString(std::list<T> l){ std::stringstream ss; for(std::list<int>::iterator it = l.begin(); it!=l.end(); ++it){ ss << *it; if(std::distance(it,l.end())>1) ss << ", "; } return "[" + ss.str()+ "]"; }
fuente