implement iterator based join_string()
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Thu, 19 Jul 2018 10:54:06 +0000 (12:54 +0200)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Thu, 2 Aug 2018 12:31:20 +0000 (14:31 +0200)
For more idiomatic C++, add a version of join_string () that
accepts a pair of iterators and templatize it, to supersede the
existing boilerplate variants. Good riddance.

src/stringfunc.cpp
src/stringfunc.hxx
test/stringfunc.cpp

index a8c501b..d611abf 100644 (file)
@@ -430,78 +430,6 @@ std::list<std::string> split_string(
 } // eo split_string(const std::string&,const std::string&,bool,const std::string&)
 
 
-/**
- * @brief joins a list of strings into a single string.
- *
- * This funtion is (basically) the reverse operation of @a split_string.
- *
- * @param parts the list of strings.
- * @param delimiter the delimiter which is inserted between the strings.
- * @return the joined string.
- */
-std::string join_string(
-   const std::list< std::string >& parts,
-   const std::string& delimiter
-)
-{
-   std::string result;
-   if (! parts.empty() )
-   {
-      std::list< std::string >::const_iterator it= parts.begin();
-      result = *it;
-      while ( ++it != parts.end() )
-      {
-         result+= delimiter;
-         result+= *it;
-      }
-   }
-   return result;
-} // eo join_string(const std::list< std::string >&,const std::string&)
-
-
-/** @brief same as join_string for list, except uses a vector */
-std::string join_string(
-   const std::vector< std::string >& parts,
-   const std::string& delimiter
-)
-{
-   std::string result;
-   if (! parts.empty() )
-   {
-      std::vector< std::string >::const_iterator it= parts.begin();
-      result = *it;
-      while ( ++it != parts.end() )
-      {
-         result+= delimiter;
-         result+= *it;
-      }
-   }
-   return result;
-} // eo join_string(const std::vector< std::string >&,const std::string&)
-
-/** @brief same as join_string for list, except uses a set */
-std::string join_string(
-   const std::set< std::string >& parts,
-   const std::string& delimiter
-)
-{
-   std::string result;
-
-   if (! parts.empty() )
-   {
-       BOOST_FOREACH(const std::string &part, parts)
-       {
-           if (!result.empty ())
-           {
-               result += delimiter;
-           }
-           result += part;
-       }
-   }
-
-   return result;
-} // eo join_string(const std::vector< std::string >&,const std::string&)
-
 std::string join_string (
    const char *const parts[], /* assumed NULL-terminated */
    const std::string& delimiter
index 29b8aba..bb5a960 100644 (file)
@@ -34,7 +34,9 @@ on this file might be covered by the GNU General Public License.
 #ifndef __STRINGFUNC_HXX
 #define __STRINGFUNC_HXX
 
+#include <stdio.h>
 #include <list>
+#include <numeric>
 #include <set>
 #include <vector>
 #include <string>
@@ -130,21 +132,46 @@ std::list< std::string > split_string(
    const std::string& trim_list= std::string()
 );
 
+struct concatenator {
+    std::string delim;
 
-std::string join_string(
-   const std::list< std::string >& parts,
-   const std::string& delimiter = "\n"
-);
+    concatenator (const std::string &delim) : delim (delim) { }
 
-std::string join_string(
-   const std::vector< std::string >& parts,
-   const std::string& delimiter = "\n"
-);
+    inline std::string operator() (const std::string &acc, const std::string &elt) const
+    { return acc + delim + elt; }
+};
 
-std::string join_string(
-   const std::set< std::string >& parts,
+template<typename Iter>
+std::string
+join_string (
+    Iter first,
+    Iter last,
+    const std::string &delimiter = "\n"
+)
+{
+    if (first == last) { return ""; }
+
+    const std::string &init = *first++;
+    if (first == last) { return init; }
+
+    return std::accumulate (first, last, init, concatenator (delimiter));
+}
+
+/**
+ * @brief joins a container of strings into a single string.
+ *
+ * This funtion is (basically) the reverse operation of @a split_string.
+ *
+ * @param parts         the container of strings.
+ * @param delimiter     the delimiter to insert between the strings.
+ * @return              the joined string.
+ */
+template<typename Cont>
+inline std::string join_string(
+   const Cont& parts,
    const std::string& delimiter = "\n"
-);
+)
+{ return join_string (parts.begin (), parts.end (), delimiter); }
 
 std::string join_string(
    const char *const parts [],
index 38c8e71..7771b55 100644 (file)
@@ -614,9 +614,17 @@ BOOST_AUTO_TEST_CASE(SplitToVector)
 BOOST_AUTO_TEST_CASE(JoinString1)
 {
     std::list< std::string > parts;
-    get_push_back_filler(parts)("1")("2")("drei");
-
     std::string joined_string= join_string(parts,"/");
+    BOOST_CHECK_EQUAL( std::string("") , joined_string );
+
+    parts.push_back ("1");
+    joined_string= join_string(parts,"/");
+    // we should have slashes between the strings:
+    BOOST_CHECK_EQUAL( std::string("1") , joined_string );
+
+    get_push_back_filler(parts)("2")("drei");
+
+    joined_string= join_string(parts,"/");
     // we should have slashes between the strings:
     BOOST_CHECK_EQUAL( std::string("1/2/drei") , joined_string );
 
@@ -655,6 +663,70 @@ BOOST_AUTO_TEST_CASE(JoinStringVector)
 } // eo JoinStringVector
 
 
+BOOST_AUTO_TEST_CASE(JoinStringSet)
+{
+    std::set< std::string > parts;
+
+    std::string joined_string= join_string(parts,"/");
+    BOOST_CHECK_EQUAL( std::string() , joined_string );
+
+    parts.insert ("foo");
+    joined_string= join_string(parts,"/");
+    BOOST_CHECK_EQUAL( std::string("foo") , joined_string );
+
+    parts.insert ("bar");
+    parts.insert ("baz");
+
+    joined_string= join_string(parts,"/");
+    // we should have slashes between the strings:
+    BOOST_CHECK_EQUAL( std::string("bar/baz/foo") , joined_string );
+
+    parts.insert( std::string() );
+    joined_string= join_string(parts,"/");
+    // now we should have an additional trailing slash:
+    BOOST_CHECK_EQUAL( std::string("/bar/baz/foo") , joined_string );
+} // eo JoinStringSet
+
+
+BOOST_AUTO_TEST_CASE(JoinStringIterSet_Empty)
+{
+    std::set< std::string > parts;
+
+    // empty sequence → empty string
+    BOOST_CHECK_EQUAL(join_string (parts.begin (), parts.end ()     ), "");
+    BOOST_CHECK_EQUAL(join_string (parts.begin (), parts.end (), "/"), "");
+} // eo JoinStringSet
+
+BOOST_AUTO_TEST_CASE(JoinStringIterSet_One)
+{
+    std::set< std::string > parts;
+
+    parts.insert ("foo");
+
+    // cardinality == 1 → no delimiter
+    BOOST_CHECK_EQUAL(join_string (parts.begin (), parts.end ()     ), "foo");
+    BOOST_CHECK_EQUAL(join_string (parts.begin (), parts.end (), "/"), "foo");
+} // eo JoinStringSet
+
+BOOST_AUTO_TEST_CASE(JoinStringIterSet)
+{
+    std::set< std::string > parts;
+
+    parts.insert ("foo");
+    parts.insert ("bar");
+    parts.insert ("baz");
+
+    std::string joined_string= join_string(parts.begin (), parts.end (), "/");
+    // we should have slashes between the strings:
+    BOOST_CHECK_EQUAL( std::string("bar/baz/foo") , joined_string );
+
+    parts.insert( std::string() );
+    joined_string= join_string(parts.begin (), parts.end (),"/");
+    // now we should have an additional trailing slash:
+    BOOST_CHECK_EQUAL( std::string("/bar/baz/foo") , joined_string );
+} // eo JoinStringSet
+
+
 BOOST_AUTO_TEST_CASE(ConversionStringInt)
 {
     std::string s1("24");