문자열 벡터를 문자열로 내파하는 방법 (우아한 방법)


83

문자열 벡터를 문자열로 내파하는 가장 우아한 방법을 찾고 있습니다. 다음은 지금 사용중인 솔루션입니다.

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);
}

거기에 다른 사람이 있습니까?


이 함수를 implode라고 부르는 이유는 무엇입니까?
Colonel Panic

5
@ColonelPanic, 배열 요소를 결합하여 단일 문자열로 출력하는 PHP의 implode () 메서드와 유사합니다. 왜이 질문을
하시는지

답변:


133

사용 boost::algorithm::join(..):

#include <boost/algorithm/string/join.hpp>
...
std::string joinedString = boost::algorithm::join(elems, delim);

이 질문을 참조하십시오 .


57
간단한 문자열을 만들기 위해 대규모 부스트 라이브러리를 포함하고 이에 대해 링크하도록 제안하는 것은 터무니 없습니다.
Julian

8
@Julian 대부분의 프로젝트는 이미 이것을하고 있습니다. 그러나 STL에 이미이를 수행하는 방법이 포함되어 있지 않다는 것은 어리석은 일이라는 데 동의합니다. 나는 또한 이것이 최고 답변 이 아니어야한다는 데 동의 할 수 있지만 다른 답변을 명확하게 사용할 수 있습니다.
River Tam

@Julian과 동의합니다. Boost는 사용에있어서 우아 할 수 있지만 오버 헤드 측면에서 "가장 우아한 방법"은 아닙니다. 이 경우 질문 자체에 대한 해결책이 아니라 OP 알고리즘에 대한 해결 방법입니다.
Azeroth2b

3
대부분의 Boost 라이브러리는 헤더 전용이므로 연결할 것이 없습니다. 일부는 표준에 진입하기도합니다.
jbruni

28
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));

(등 <string>, <vector>, <sstream><iterator>)

깔끔한 끝 (후행 구분 기호 없음)을 원한다면 여기를 살펴보십시오.


9
그러나 추가 구분 기호 ( std::ostream_iterator스트림 끝의 생성자에 두 번째 매개 변수)가 추가된다는 점을 명심 하십시오.
Michael Krelin-hacker 2011-04-16

9
"내파"의 요점은 구분 기호를 마지막에 추가해서는 안된다는 것입니다. 이 대답은 불행히도 구분 기호를 마지막에 추가합니다.
Jonny

다행히도 마지막에 토큰을 추가해야합니다! 솔루션에 감사드립니다.
Константин Ван

20

출력을 빌드 하기 std::ostringstream보다는를 사용해야합니다 std::string(그런 다음 str()끝에 해당 메서드를 호출 하여 문자열을 얻을 수 있으므로 인터페이스를 변경할 필요가 없으며 임시 만 변경할 수 있습니다 s).

거기에서 다음 std::ostream_iterator과 같이 를 사용하여 변경할 수 있습니다 .

copy(elems.begin(), elems.end(), ostream_iterator<string>(s, delim)); 

그러나 여기에는 두 가지 문제가 있습니다.

  1. delim이제 const char*하나가 아니라이어야합니다 char. 별거 아니야.
  2. std::ostream_iterator마지막을 포함하여 모든 단일 요소 뒤에 구분 기호를 씁니다. 따라서 마지막에있는 마지막 항목을 지우거나 이러한 성가심이없는 고유 한 버전의 반복기를 작성해야합니다. 이와 같은 것을 필요로하는 코드가 많은 경우 후자를 수행하는 것이 좋습니다. 그렇지 않으면 전체 혼란을 피하는 것이 가장 좋습니다 (예 : 사용 ostringstream하지만 사용 하지 않음 ostream_iterator).

1
또는 이미 작성된 것을 사용하십시오 : stackoverflow.com/questions/3496982/…
Jerry Coffin

13

나는 한 줄짜리를 좋아하기 때문에 (끝에서 볼 수 있듯이 모든 종류의 이상한 일에 매우 유용합니다), 여기 std :: accumulate 및 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; 
    } )

이 구문은 스트림 연산자에 유용하다는 것을 알았습니다. 스트림 작업의 범위를 벗어나는 모든 종류의 이상한 논리를 원하지 않는 단순한 문자열 조인을 수행합니다. 예를 들어 스트림 연산자 (std; 사용)를 사용하여 문자열을 형식화하는 메소드의 다음 return 문을 고려하십시오.

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();

최신 정보:

주석에서 @plexando가 지적했듯이, 위 코드는 "첫 실행"에 대한 검사에서 추가 문자가없는 이전 실행이 누락되어 배열이 빈 문자열로 시작될 때 잘못된 동작을 겪습니다. 모든 실행에서 "is first run"검사를 실행하는 것은 이상합니다 (즉, 코드가 최적화되지 않음).

목록에 적어도 하나의 요소가 있다는 사실을 알면이 두 가지 문제에 대한 해결책은 쉽습니다. OTOH, 목록에 하나 이상의 요소 가 없다는 사실을 알고 있다면 실행을 더 단축 할 수 있습니다.

결과 코드가 예쁘지 않다고 생각하므로 여기에 The Correct Solution 으로 추가하고 있지만 위의 논의에는 여전히 장점이 있다고 생각합니다.

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; });

메모:

  • 첫 번째 요소에 대한 직접 액세스를 지원하는 컨테이너의 경우 대신 세 번째 인수에 사용하는 것이 좋습니다 alist[0].
  • 댓글 및 채팅의 토론에 따라 람다는 여전히 일부 복사 작업을 수행합니다. 대신이 (덜 예쁜) 람다를 사용하여 최소화 할 수 있습니다. [](auto&& a, auto&& b) -> auto& { a += ','; a += b; return a; })(GCC 10에서) x10 이상으로 성능을 향상시킵니다. 제안에 대해 @Deduplicator에게 감사드립니다. 나는 아직도 여기서 무슨 일이 일어나고 있는지 알아 내려고 노력하고 있습니다.

4
accumulate문자열 에는 사용하지 마십시오 . 다른 대답의 대부분은 O (n)이지만 accumulate각 요소를 추가하기 전에 누산기의 임시 복사본을 만들기 때문에 O (n ^ 2)입니다. 그리고 아니요, 이동 의미론은 도움이되지 않습니다.
Oktalist 2013 년

2
@Oktalist, 왜 그렇게 말하는지 잘 모르겠습니다. cplusplus.com/reference/numeric/accumulate 는 "복잡성은 처음과 마지막 사이의 거리에서 선형입니다"라고 말합니다.
Guss

1
이는 각각의 개별 추가에 일정한 시간이 걸린다고 가정합니다. 경우 T가 과부하가 operator+(같은 string않습니다) 또는 당신이 당신의 자신의 펑터를 제공하는 경우 모든 베팅은 꺼져 있습니다. 이동 시맨틱이 도움이되지 않는다고 성급하게 말했지만, 내가 확인한 두 가지 구현에서 문제를 해결하지 못합니다. 유사한 질문에 대한 내 답변을 참조하십시오 .
Oktalist

1
skwllsp의 의견은 그것과 관련이 없습니다. 내가 말했듯이 대부분의 다른 답변 (및 OP의 implode예)은 올바른 일을하고 있습니다. reserve문자열을 호출하지 않더라도 O (n) 입니다. accumulate를 사용하는 솔루션 만 O (n ^ 2)입니다. C 스타일 코드가 필요 없습니다.
Oktalist

12
나는 벤치 마크 를했고 accumulate는 실제로 O (n) 문자열 스트림보다 빠릅니다.
kirbyfan64sos

11

간단한 멍청한 해결책은 어떻습니까?

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;
}

8
string join(const vector<string>& vec, const char* delim)
{
    stringstream res;
    copy(vec.begin(), vec.end(), ostream_iterator<string>(res, delim));
    return res.str();
}

7

이 한 줄짜리 누적을 사용하고 싶습니다 (후행 구분 기호 없음).

std::accumulate(
    std::next(elems.begin()), 
    elems.end(), 
    elems[0], 
    [](std::string a, std::string b) {
        return a + delimiter + b;
    }
);

4
비어있을 때 조심하십시오.
Carlos Pinzón

6

특히 더 큰 컬렉션의 경우 여전히 첫 번째 요소를 추가하는지 확인하지 않고 후행 구분 기호가 없는지 확인하지 않으려 고합니다.

따라서 비어 있거나 단일 요소 목록의 경우 반복이 전혀 없습니다.

빈 범위는 간단합니다. ""를 반환합니다.

단일 요소 또는 다중 요소는 다음을 통해 완벽하게 처리 할 수 ​​있습니다 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;
         });
};

샘플 실행 ( C ++ 14 필요 ) : http://cpp.sh/8uspd


3

다음을 사용하는 버전 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";
}

2

다음은 마지막 요소 뒤에 구분 기호를 추가하지 않는 또 다른 요소입니다.

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 "";

2

답변의 일부 를 다른 질문에 사용하면 후행 쉼표가없는 구분 기호를 기반으로 결합 된 결과를 얻을 수 있습니다.

용법:

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

암호:

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();
    }
}

1

내가 사용하는 것은 간단하고 유연합니다.

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;
}

사용 :

string a = joinList({ "a", "bbb", "c" }, "!@#");

산출:

a!@#bbb!@#c

0

약간 긴 솔루션이지만을 사용 std::ostringstream하지 않으며 마지막 구분 기호를 제거하기 위해 해킹이 필요하지 않습니다.

http://www.ideone.com/hW1M9

그리고 코드 :

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()));
}

0

삼항 연산자로 가능한 솔루션 ?:.

std::string join(const std::vector<std::string> & v, const std::string & delimiter = ", ") {
    std::string result;

    for (size_t i = 0; i < v.size(); ++i) {
        result += (i ? delimiter : "") + v[i]; 
    }

    return result;
}

join({"2", "4", "5"})당신에게 줄 것 2, 4, 5입니다.


0

fmt를 사용하면 할 수 있습니다.

#include <fmt/format.h>
auto s = fmt::format("{}",fmt::join(elems,delim)); 

그러나 join이 std :: format으로 만들지 모르겠습니다.


-1

그냥 추가하세요 !! 문자열 s = "";

for (int i = 0; i < doc.size(); i++)   //doc is the vector
    s += doc[i];

-1

이것을 시도하지만 목록 대신 벡터 사용

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()+ "]";
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.