Bir dizenin sözcüklerini nasıl yineleyebilirim?


2986

Bir dizenin kelimeleri üzerinde yinelemeye çalışıyorum.

Dizenin boşlukla ayrılmış kelimelerden oluştuğu varsayılabilir.

C string fonksiyonları veya karakter manipülasyonu / erişimi bu tür ilgilenmiyorum unutmayın. Ayrıca, cevabınızdaki verim üzerinde zarafete öncelik verin.

Şu anda sahip olduğum en iyi çözüm:

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main()
{
    string s = "Somewhere down the road";
    istringstream iss(s);

    do
    {
        string subs;
        iss >> subs;
        cout << "Substring: " << subs << endl;
    } while (iss);
}

Bunu yapmanın daha zarif bir yolu var mı?


617
Dostum ... Zerafet, kitabımda "bu güzel görünüyor verimlilik" demenin sadece süslü bir yolu. Sadece bir şablonda yer almadığı için herhangi bir şeyi başarmak için C fonksiyonlarını ve hızlı yöntemlerini kullanmaktan çekinmeyin;)

14
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; }
pyon

21
@Eduardo: bu da yanlış ... başka bir değer yayınlamaya çalışmak ve bu değeri kullanmak arasında denemeleri test etmeniz gerekiyor, yanistring sub; while (iss >> sub) cout << "Substring: " << sub << '\n';
Tony Delroy

9
C ++ 'da bunu varsayılan olarak yapmak için çeşitli seçenekler: cplusplus.com/faq/sequences/strings/split
hB0

14
Zarafet için oldukça verimli olmaktan daha fazlası var. Zarif özellikler arasında düşük satır sayısı ve yüksek okunabilirlik sayılabilir. IMHO Elegance verimlilik için bir vekil değil sürdürülebilirlik içindir.
Matt

Yanıtlar:


1369

Değer için, sadece standart kütüphane tesislerine dayanarak bir giriş dizesinden jeton çıkarmanın başka bir yolu. Bu, STL tasarımının arkasındaki güç ve zarafetin bir örneğidir.

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>

int main() {
    using namespace std;
    string sentence = "And I feel fine...";
    istringstream iss(sentence);
    copy(istream_iterator<string>(iss),
         istream_iterator<string>(),
         ostream_iterator<string>(cout, "\n"));
}

Çıkarılan belirteçleri bir çıkış akışına kopyalamak yerine, aynı genel copyalgoritmayı kullanarak bunları bir kaba ekleyebiliriz .

vector<string> tokens;
copy(istream_iterator<string>(iss),
     istream_iterator<string>(),
     back_inserter(tokens));

... veya vectordoğrudan oluşturun :

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};

164
Bunun için bir sınırlayıcı belirtmek mümkün mü? Örneğin virgülle bölmek gibi mi?
l3dx

15
@Jonathan: \ n bu durumda sınırlayıcı değil, cout'a çıkış için sınırlayıcıdır.
huy

772
Bu, başka bir sınırlayıcı almadığı için zayıf bir çözümdür, bu nedenle ölçeklendirilemez ve bakımı mümkün değildir.
MerhabaDünya

37
Aslında bu olabilir (bazı yapıyor biraz çirkin olsa da) diğer sınırlayıcı sadece para cezası çalışır. İstediğiniz sınırlayıcıları boşluk olarak sınıflandıran bir ctype faseti yaratırsınız, o faseti içeren bir yerel ayar yaratırsınız, ardından stringleri çıkarmadan önce stringstream'i o yerelle birleştirirsiniz.
Jerry Coffin

53
@Kinderchocolate "Dizenin boşlukla ayrılmış kelimelerden oluştuğu varsayılabilir" - Hmm, sorunun problemine zayıf bir çözüm gibi gelmiyor. "ölçeklenebilir değil ve bakımı kolay" - Hah, güzel bir tane.
Christian Rau

2426

Ben bir sınırlayıcı tarafından dize bölmek için kullanın. Birincisi sonuçları önceden oluşturulmuş bir vektöre koyar, ikincisi yeni bir vektör döndürür.

#include <string>
#include <sstream>
#include <vector>
#include <iterator>

template <typename Out>
void split(const std::string &s, char delim, Out result) {
    std::istringstream iss(s);
    std::string item;
    while (std::getline(iss, item, delim)) {
        *result++ = item;
    }
}

std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, std::back_inserter(elems));
    return elems;
}

Bu çözümün boş belirteçleri atlamadığını unutmayın, bu nedenle aşağıdakilerden biri boş olan 4 öğe bulacaktır:

std::vector<std::string> x = split("one:two::three", ':');

86
Boş belirteçleri atlamasını önlemek için bir empty()kontrol yapın:if (!item.empty()) elems.push_back(item)
0x499602D2

11
Delim iki karakter içeriyor ->mu?
herohuyongtao

7
@herohuyongtao, bu çözüm yalnızca tek karakter sınırlayıcıları için çalışır.
Evan Teran

4
@JeshwanthKumarNK, bu gerekli değildir, ancak sonucu doğrudan böyle bir işleve geçirmenize izin verir: İsterseniz f(split(s, d, v))önceden ayrılmış bir avantajdan yararlanmaya devam ederken vector.
Evan Teran

8
Uyarı: split ("one: two :: three", ':') ve split ("one: two :: three:", ':') aynı değeri döndürür.
dshin

834

Boost kullanarak olası bir çözüm şunlar olabilir:

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

Bu yaklaşım, stringstreamyaklaşımdan bile daha hızlı olabilir . Ve bu genel bir şablon işlevi olduğundan, her türlü sınırlayıcıyı kullanarak diğer dizeleri (wchar, vb. Veya UTF-8) bölmek için kullanılabilir.

Ayrıntılar için belgelere bakın.


35
Buradaki her iki durum da strtok benzeri bir işlevden çok daha yavaş olduğu için burada hız önemsizdir.
Tom

45
Ve zaten güçlendirmesi olmayanlar için ... bcp bunun için 1.000'den fazla dosyayı kopyalar :)
Roman Starkov

12
Uyarı, boş bir dize ("") verildiğinde, bu yöntem "" dizesini içeren bir vektör döndürür. Bu yüzden bölünmeden önce bir "if (! String_to_split.empty ())" ekleyin.
Offirmo

29
@Ian Gömülü geliştiricilerin tümü takviye kullanmıyor.
ACK_stoverflow

31
bir ek olarak: Ben sadece gerektiğinde artırmak kullanın, normalde ben belirli bir amacı gerçekleştirmek küçük kesin belirli kodu elde böylece bağımsız ve taşınabilir kendi kod kitaplığı eklemeyi tercih. Bu şekilde kod herkese açık değil, performans sergiliyor, önemsiz ve taşınabilir. Boost onun yeri var ama ben onun dizeleri tokenize için biraz overkill olduğunu öneririz: tüm evin bir resim asmak için duvara dövülmüş yeni bir çivi almak için bir mühendislik firmasına taşınmış olmaz .... onlar yapabilir son derece iyi, ama artıları eksileri tarafından çok daha ağır basmış.
GMasucci

362
#include <vector>
#include <string>
#include <sstream>

int main()
{
    std::string str("Split me by whitespaces");
    std::string buf;                 // Have a buffer string
    std::stringstream ss(str);       // Insert the string into a stream

    std::vector<std::string> tokens; // Create vector to hold our words

    while (ss >> buf)
        tokens.push_back(buf);

    return 0;
}

12
Kullanmak durumunda da diğer sınırlayıcı üzerinde ayırabilirsiniz getlineiçinde whilevirgül kullanımı ile bölünmeye durum örneğin while(getline(ss, buff, ',')).
Ali

181

Kod boyutu için tüm verimliliği feda etmek ve "şık" bir zarafet türü olarak görmek için iyi oturmayanlar için, aşağıdakiler tatlı bir noktaya çarpmalıdır (ve ben şablon konteyner sınıfının inanılmaz zarif bir ek olduğunu düşünüyorum.):

template < class ContainerT >
void tokenize(const std::string& str, ContainerT& tokens,
              const std::string& delimiters = " ", bool trimEmpty = false)
{
   std::string::size_type pos, lastPos = 0, length = str.length();

   using value_type = typename ContainerT::value_type;
   using size_type  = typename ContainerT::size_type;

   while(lastPos < length + 1)
   {
      pos = str.find_first_of(delimiters, lastPos);
      if(pos == std::string::npos)
      {
         pos = length;
      }

      if(pos != lastPos || !trimEmpty)
         tokens.push_back(value_type(str.data()+lastPos,
               (size_type)pos-lastPos ));

      lastPos = pos + 1;
   }
}

Genellikle kullanmayı tercih std::vector<std::string>benim ikinci parametre (aynı tip ContainerT) ... ama list<>yolu daha hızlı olduğu vector<>doğrudan erişim gerekmediğinde için ve benzeri hatta kendi dize sınıf ve kullanım şey oluşturabilir std::list<subString>nerede subStringinanılmaz hız için herhangi bir kopyasını yapmaz artışlar.

Bu sayfadaki en hızlı tokenizden iki kat daha hızlı ve diğerlerinden neredeyse 5 kat daha hızlı. Ayrıca mükemmel parametre türleriyle, ek hız artışları için tüm dize ve liste kopyalarını kaldırabilirsiniz.

Ek olarak (son derece verimsiz) sonucun geri dönüşünü yapmaz, bunun yerine jetonları referans olarak geçirir, böylece dilerseniz birden fazla çağrı kullanarak jeton oluşturmanıza izin verir.

Son olarak, son isteğe bağlı bir parametre aracılığıyla sonuçlardan boş belirteçlerin kırpılıp kırpılmayacağını belirlemenizi sağlar.

Tek ihtiyacı olan std::string... gerisi isteğe bağlı. Akışları veya destek kütüphanesini kullanmaz, ancak bu yabancı türlerin bazılarını doğal olarak kabul edebilecek kadar esnektir.


5
Ben oldukça hayranıyım, ama g ++ (ve muhtemelen iyi uygulama) için bunu kullanan herkes typedefs ve typenames isteyecektir: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; Sonra buna göre value_type ve size_types yerine.
aws

11
Şablon şeyler ve ilk yorumu tamamen yabancı olan bizler için, gerekli kapsama sahip bir kullanım örneği güzel olurdu.
Wes Miller

3
Ahh, anladım. C ++ satırlarını tokenize () işlev gövdesinin içine aws 'açıklamasından sonra kota.push_back () satırlarını yalnızca ContainerT :: value_type değerini yalnızca ValueType olarak değiştirmek ve (ContainerT :: value_type :: size_type) öğesini ( Beden Çeşidi). G ++ ile sızlanan bitler düzeltildi. Sadece tokenize olarak çağırın (some_string, some_vector);
Wes Miller

2
Örnek veriler üzerinde birkaç performans testi çalıştırmanın yanı sıra, öncelikle mümkün olan en az talimatlara ve yalnızca diğer dizelerde ofsetleri / uzunlukları referans alan bir alt dize sınıfının kullanılmasıyla mümkün olduğunca az bellek kopyasına indirgedim. (Kendimi yuvarladım, ama başka uygulamalar da var). Ne yazık ki, bu konuda iyileştirmek için yapabileceği çok fazla bir şey yok, ancak artımlı artışlar mümkün oldu.
Marius

3
Ne zaman için doğru çıktı budur trimEmpty = true. Unutmayın "abo"bu cevabın bir ayırıcı, ancak sınırlayıcı karakter listesi değildir. Tek bir sınırlayıcı karakter dizisi almak için değiştirmek basit olurdu (Sanırım str.find_first_ofdeğişmeli str.find_first, ama yanlış olabilir ... test edemiyorum)
Marius

158

İşte başka bir çözüm. Kompakt ve oldukça verimli:

std::vector<std::string> split(const std::string &text, char sep) {
  std::vector<std::string> tokens;
  std::size_t start = 0, end = 0;
  while ((end = text.find(sep, start)) != std::string::npos) {
    tokens.push_back(text.substr(start, end - start));
    start = end + 1;
  }
  tokens.push_back(text.substr(start));
  return tokens;
}

Dize ayırıcılarını, geniş telleri, vb. İşlemek için kolayca değiştirilebilir.

Bölmenin ""tek bir boş dize ve bölünmenin ","(yani sep) iki boş dizeyle sonuçlandığını unutmayın .

Boş belirteçleri atlamak için kolayca genişletilebilir:

std::vector<std::string> split(const std::string &text, char sep) {
    std::vector<std::string> tokens;
    std::size_t start = 0, end = 0;
    while ((end = text.find(sep, start)) != std::string::npos) {
        if (end != start) {
          tokens.push_back(text.substr(start, end - start));
        }
        start = end + 1;
    }
    if (end != start) {
       tokens.push_back(text.substr(start));
    }
    return tokens;
}

Boş simgeleri atlarken bir dizeyi birden çok sınırlayıcıya bölmek isteniyorsa, bu sürüm kullanılabilir:

std::vector<std::string> split(const std::string& text, const std::string& delims)
{
    std::vector<std::string> tokens;
    std::size_t start = text.find_first_not_of(delims), end = 0;

    while((end = text.find_first_of(delims, start)) != std::string::npos)
    {
        tokens.push_back(text.substr(start, end - start));
        start = text.find_first_not_of(delims, end);
    }
    if(start != std::string::npos)
        tokens.push_back(text.substr(start));

    return tokens;
}

10
İlk sürüm basittir ve işi mükemmel bir şekilde yapar. Yapacağım tek değişiklik, sonucu parametre olarak iletmek yerine doğrudan döndürmektir.
gregschlom

2
Çıktı, verimlilik için bir parametre olarak geçirilir. Sonuç döndürülürse, vektörün bir kopyası veya daha sonra serbest bırakılması gereken bir yığın tahsisi gerekir.
Alec Thomas

2
Yukarıdaki yorumuma hafif bir ek: C ++ 11 hareket semantiği kullanılıyorsa, bu işlev vektörü ceza olmadan döndürebilir.
Alec Thomas

7
@AlecThomas: C ++ 11'den önce bile, çoğu derleyici NRVO yoluyla iade kopyasını optimize etmez mi? (Yine de +1; çok özlü)
Marcelo Cantos

11
Tüm cevaplardan en çekici ve esnek olanlardan biri gibi görünüyor. Bir sınırlayıcı ile getline ile birlikte, daha az belirgin bir çözüm olmasına rağmen. C ++ 11 standardında bunun için bir şey yok mu? C ++ 11 bugünlerde delikli kartları destekliyor mu?
Spacen Jasset

123

Bu bir dizeden yineleme yapmak için en sevdiğim yol Kelime başına ne istersen yapabilirsin.

string line = "a line of text to iterate through";
string word;

istringstream iss(line, istringstream::in);

while( iss >> word )     
{
    // Do something on `word` here...
}

wordOlarak ilan etmek mümkün mü char?
abatishchev

Üzgünüm abatishchev, C ++ benim güçlü yanım değil. Ancak, her kelimedeki her karakter arasında bir döngü oluşturmak için zor olmayacağını düşünüyorum. Ama şu anda mevcut döngünün kelime ayırma boşluklarına bağlı olduğuna inanıyorum. Her boşluk arasında sadece tek bir karakter olduğunu bilmiyorsanız, bu durumda sadece bir karakter "kelime" döküm olabilir ... üzgünüm daha fazla yardım olamaz, ive benim C ++ fırçalamak için anlam oldu
gnomed

11
kelimeyi karakter olarak bildirirseniz, boşluk olmayan her karakterin üzerinde yinelenir. Denemek için yeterince basit:stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c;
Wayne Werner

79

Bu yığın taşması sorusuna benzer C ++ bir dize nasıl tokenize edebilirim? .

#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int argc, char** argv)
{
    string text = "token  test\tstring";

    char_separator<char> sep(" \t");
    tokenizer<char_separator<char>> tokens(text, sep);
    for (const string& t : tokens)
    {
        cout << t << "." << endl;
    }
}

Bu, tüm belirteçlerin bir kopyasını gerçekleştiriyor mu, yoksa yalnızca geçerli belirtecin başlangıç ​​ve bitiş konumunu mı koruyor?
einpoklum

66

Sonuçları bir vektör içine koyar, bir dize bir delim olarak destekler ve boş değerleri tutmak üzerinde kontrol sağlar çünkü aşağıdaki gibi. Ancak, o zaman iyi görünmüyor.

#include <ostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

vector<string> split(const string& s, const string& delim, const bool keep_empty = true) {
    vector<string> result;
    if (delim.empty()) {
        result.push_back(s);
        return result;
    }
    string::const_iterator substart = s.begin(), subend;
    while (true) {
        subend = search(substart, s.end(), delim.begin(), delim.end());
        string temp(substart, subend);
        if (keep_empty || !temp.empty()) {
            result.push_back(temp);
        }
        if (subend == s.end()) {
            break;
        }
        substart = subend + delim.size();
    }
    return result;
}

int main() {
    const vector<string> words = split("So close no matter how far", " ");
    copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n"));
}

Elbette, Boost'un split()kısmen böyle çalışan bir modeli var. Ve eğer 'beyaz boşluk' ile, gerçekten Boost'un split ile is_any_of()harika bir şekilde çalıştığı herhangi bir beyaz boşluk demek istersiniz .


Sonunda ipin her iki tarafında boş belirteçleri doğru bir şekilde işleyen bir çözüm
fmuecke

53

STL'nin halihazırda böyle bir yöntemi yoktur.

Ancak, üyeyi kullanarak C'nin strtok()işlevini kullanabilir std::string::c_str()veya kendiniz yazabilirsiniz. Hızlı bir Google aramasından sonra bulduğum bir kod örneği ( "STL string split" ):

void Tokenize(const string& str,
              vector<string>& tokens,
              const string& delimiters = " ")
{
    // Skip delimiters at beginning.
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    // Find first "non-delimiter".
    string::size_type pos     = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos)
    {
        // Found a token, add it to the vector.
        tokens.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters.  Note the "not_of"
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next "non-delimiter"
        pos = str.find_first_of(delimiters, lastPos);
    }
}

Alındığı yer: http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html

Kod örneği hakkında sorularınız varsa, bir yorum bırakın ve ben açıklayacağım.

Ve sadece typedefyinelenen bir yineleyici uygulamadığı veya aşırı yüklenmediği için <<operatör kötü kod olduğu anlamına gelmez. C fonksiyonlarını oldukça sık kullanıyorum. Örneğin, printfve scanfher ikisi birden std::cinve std::cout(önemli ölçüde) daha hızlıdır , fopensözdizimi ikili türler için çok daha dostudur ve ayrıca daha küçük EXE'ler üretme eğilimindedir.

Bu "Performans üzerinde zarafet" anlaşmasında satılmayın .


C string işlevlerinin farkındayım ve performans sorunlarının da farkındayım (her ikisi de sorumu not ettim). Ancak, bu özel soru için zarif bir C ++ çözümü arıyorum.
Ashwin Nanjappa

11
@Nelson LaQuet: Tahmin edeyim: Çünkü strtok evresel değil mi?
paercebal

40
@ Nelson asla string.c_str () 'i strtok'a aktarmaz! strtok girdi dizesini çöker (her foudn sınırlayıcısının yerine '\ 0' karakterlerini ekler) ve c_str () değiştirilemez bir dize döndürür.
Evan Teran

3
@Nelson: Bu dizinin son yorumunuzda str.size () + 1 boyutunda olması gerekiyor. Ancak tezinize, "estetik" nedenlerle C işlevlerinden kaçınmanın saçma olduğunu kabul ediyorum.
j_random_hacker

2
@paulm: Hayır, C ++ akışlarının yavaşlığına fasetler neden olur. Senkronizasyon devre dışı olsa bile (ve senkronize edilemeyen dize akışlarında) stdio.h işlevlerinden hala daha yavaştırlar.
Ben Voigt

42

İşte bir bölünmüş fonksiyon:

  • jenerik
  • standart C ++ kullanır (destek yok)
  • birden fazla sınırlayıcı kabul eder
  • boş belirteçleri yok sayar (kolayca değiştirilebilir)

    template<typename T>
    vector<T> 
    split(const T & str, const T & delimiters) {
        vector<T> v;
        typename T::size_type start = 0;
        auto pos = str.find_first_of(delimiters, start);
        while(pos != T::npos) {
            if(pos != start) // ignore empty tokens
                v.emplace_back(str, start, pos - start);
            start = pos + 1;
            pos = str.find_first_of(delimiters, start);
        }
        if(start < str.length()) // ignore trailing delimiter
            v.emplace_back(str, start, str.length() - start); // add what's left of the string
        return v;
    }

Örnek kullanım:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");

Kullanım listesine ekle unuttun: "son derece verimsiz"
Xander Tulip

1
@XanderTulip, daha yapıcı olabilir ve nasıl ya da neden açıklayabilir misiniz?
Marco

3
@XanderTulip: Vektörü değere döndürerek atıfta bulunduğunuzu varsayıyorum. Dönüş-Değer-Optimizasyonu (RVO, google it) bununla ilgilenmelidir. Ayrıca C ++ 11'de hareket referansı ile dönebilirsiniz.
Joseph Garvin

3
Bu aslında daha da optimize edilebilir: .push_back (str.substr (...)) yerine .emplace_back (str, start, pos - start) kullanılabilir. Bu şekilde dize nesnesi kapta oluşturulur ve böylece bir taşıma işleminden + .substr işlevi tarafından yapılan diğer maskaralıklardan kaçınırız.
Mihai Bişog

@zoopp evet. İyi bir fikir. Bunu yazarken VS10'un emplace_back desteği yoktu. Cevabımı güncelleyeceğim. Teşekkürler
Marco M.12

36

Bu soruna 2 satır çözüm var:

char sep = ' ';
std::string s="1 This is an example";

for(size_t p=0, q=0; p!=s.npos; p=q)
  std::cout << s.substr(p+(p!=0), (q=s.find(sep, p+1))-p-(p!=0)) << std::endl;

Daha sonra yazdırmak yerine bir vektöre koyabilirsiniz.


35

Yine başka bir esnek ve hızlı yol

template<typename Operator>
void tokenize(Operator& op, const char* input, const char* delimiters) {
  const char* s = input;
  const char* e = s;
  while (*e != 0) {
    e = s;
    while (*e != 0 && strchr(delimiters, *e) == 0) ++e;
    if (e - s > 0) {
      op(s, e - s);
    }
    s = e + 1;
  }
}

Bir dizgi vektörü ile kullanmak için (Düzenle: Birisi STL sınıflarını devralmayacağına dikkat çektiğinden ... hrmf;)):

template<class ContainerType>
class Appender {
public:
  Appender(ContainerType& container) : container_(container) {;}
  void operator() (const char* s, unsigned length) { 
    container_.push_back(std::string(s,length));
  }
private:
  ContainerType& container_;
};

std::vector<std::string> strVector;
Appender v(strVector);
tokenize(v, "A number of words to be tokenized", " \t");

Bu kadar! Bu, tokenizörü kullanmanın sadece bir yoludur, tıpkı kelimeleri nasıl sayacağınız gibi:

class WordCounter {
public:
  WordCounter() : noOfWords(0) {}
  void operator() (const char*, unsigned) {
    ++noOfWords;
  }
  unsigned noOfWords;
};

WordCounter wc;
tokenize(wc, "A number of words to be counted", " \t"); 
ASSERT( wc.noOfWords == 7 );

Hayal gücü ile sınırlı;)



32

İşte sadece standart normal ifade kitaplığını kullanan basit bir çözüm

#include <regex>
#include <string>
#include <vector>

std::vector<string> Tokenize( const string str, const std::regex regex )
{
    using namespace std;

    std::vector<string> result;

    sregex_token_iterator it( str.begin(), str.end(), regex, -1 );
    sregex_token_iterator reg_end;

    for ( ; it != reg_end; ++it ) {
        if ( !it->str().empty() ) //token could be empty:check
            result.emplace_back( it->str() );
    }

    return result;
}

Normal ifade bağımsız değişkeni birden çok bağımsız değişkeni (boşluk, virgül vb.) Denetlemeye izin verir

Ben genellikle sadece boşluk ve virgül üzerinde bölmek için kontrol, bu yüzden de bu varsayılan işlevi var:

std::vector<string> TokenizeDefault( const string str )
{
    using namespace std;

    regex re( "[\\s,]+" );

    return Tokenize( str, re );
}

"[\\s,]+"Boşluklar (denetler \\s) ve virgül ( ,).

wstringBunun yerine bölmek istiyorsanız string,

  • hepsini std::regexdeğiştirstd::wregex
  • hepsini sregex_token_iteratordeğiştirwsregex_token_iterator

Ayrıca, derleyicinize bağlı olarak dize bağımsız değişkenini referans olarak almak isteyebilirsiniz.


Bu benim en sevdiğim cevap olurdu, ama std :: regex GCC 4.8'de bozuldu. GCC 4.9'da doğru bir şekilde uyguladıklarını söylediler. Hala sana +
1imi

1
Bu benim küçük değişikliklerle favorim: dediğin gibi referans olarak döndürülen vektör ve "str" ​​ve "regex" argümanları da referanslardan geçti. Teşekkür.
QuantumKarl

1
Regex kalıplarıyla uğraşırken ham dizeler oldukça kullanışlıdır. Bu şekilde kaçış dizilerini kullanmak zorunda kalmazsınız ... Sadece kullanabilirsiniz R"([\s,]+)".
Sam

26

Sahip olduğunuz gibi kullanmak std::stringstreammükemmel çalışıyor ve tam olarak ne istediğinizi yapın. Sadece bir şeyler yapmanın farklı yollarını arıyorsanız, std::find()/ std::find_first_of()ve tuşlarını kullanabilirsiniz std::string::substr().

İşte bir örnek:

#include <iostream>
#include <string>

int main()
{
    std::string s("Somewhere down the road");
    std::string::size_type prev_pos = 0, pos = 0;

    while( (pos = s.find(' ', pos)) != std::string::npos )
    {
        std::string substring( s.substr(prev_pos, pos-prev_pos) );

        std::cout << substring << '\n';

        prev_pos = ++pos;
    }

    std::string substring( s.substr(prev_pos, pos-prev_pos) ); // Last word
    std::cout << substring << '\n';

    return 0;
}

Bu yalnızca tek karakterli sınırlayıcılar için geçerlidir. Basit bir değişiklik, çok karakterli ile çalışmasını sağlar:prev_pos = pos += delimiter.length();
David Doria

25

Boost'u kullanmak istiyorsanız, ancak tüm dizeyi sınırlayıcı olarak kullanmak istiyorsanız (daha önce önerilen çözümlerin çoğunda olduğu gibi tek karakterler yerine) boost_split_iterator.

Uygun şablon içeren örnek kod:

#include <iostream>
#include <vector>
#include <boost/algorithm/string.hpp>

template<typename _OutputIterator>
inline void split(
    const std::string& str, 
    const std::string& delim, 
    _OutputIterator result)
{
    using namespace boost::algorithm;
    typedef split_iterator<std::string::const_iterator> It;

    for(It iter=make_split_iterator(str, first_finder(delim, is_equal()));
            iter!=It();
            ++iter)
    {
        *(result++) = boost::copy_range<std::string>(*iter);
    }
}

int main(int argc, char* argv[])
{
    using namespace std;

    vector<string> splitted;
    split("HelloFOOworldFOO!", "FOO", back_inserter(splitted));

    // or directly to console, for example
    split("HelloFOOworldFOO!", "FOO", ostream_iterator<string>(cout, "\n"));
    return 0;
}

20

Heres normal regex kütüphanesini kullanan bir regex çözümü içerir. (Biraz paslıyım, bu yüzden birkaç sözdizimi hatası olabilir, ancak en azından genel fikir budur)

#include <regex.h>
#include <string.h>
#include <vector.h>

using namespace std;

vector<string> split(string s){
    regex r ("\\w+"); //regex matches whole words, (greedy, so no fragment words)
    regex_iterator<string::iterator> rit ( s.begin(), s.end(), r );
    regex_iterator<string::iterator> rend; //iterators to iterate thru words
    vector<string> result<regex_iterator>(rit, rend);
    return result;  //iterates through the matches to fill the vector
}

Belki daha iyi regex yaklaşımıyla benzer yanıtlar: burada ve burada .
nobar

20

Adlı bir işlev var strtok.

#include<string>
using namespace std;

vector<string> split(char* str,const char* delim)
{
    char* saveptr;
    char* token = strtok_r(str,delim,&saveptr);

    vector<string> result;

    while(token != NULL)
    {
        result.push_back(token);
        token = strtok_r(NULL,delim,&saveptr);
    }
    return result;
}

3
strtokC ++ kütüphanesinden değil, C ++ kütüphanesinden. Çok iş parçacıklı programlarda kullanmak güvenli değildir. Giriş dizesini değiştirir.
Kevin Panko

13
İlk çağrıdaki karakter işaretçisini statik bir değişkende sakladığından, NULL iletildiğinde sonraki çağrılarda hangi işaretçinin kullanılması gerektiğini hatırlar. Başka bir evre strtokişlenirken ikinci bir evre çağrılırsa , bu karakter işaretçisinin üzerine yazılır ve her iki evrenin yanlış sonuçları olur. mkssoftware.com/docs/man3/strtok.3.asp
Kevin Panko

1
Daha önce de belirtildiği gibi strtok güvensizdir ve C'de bile strtok_r kullanılması tavsiye edilir
systemsfault

4
Erişilebilecek bir kod bölümündeyseniz strtok_r kullanılabilir. Bu, "hat gürültüsü" olmayan ve hepsinin tek çözümü, c ++ ile tam olarak neyin yanlış olduğunun bir kanıtı
Erik Aronesty

Böylece C ++ winks'in iş parçacığı güvenliği nedeniyle itiraz olamaz.
Erik Aronesty

17

Stringstream olmayan uzay sembollerle dizesini ayrıştırmak gerekiyorsa uygun olabilir:

string s = "Name:JAck; Spouse:Susan; ...";
string dummy, name, spouse;

istringstream iss(s);
getline(iss, dummy, ':');
getline(iss, name, ';');
getline(iss, dummy, ':');
getline(iss, spouse, ';')

14

Şimdiye kadar bir tane kullanılan Boost , ama bu geldi yüzden, buna bağlı olmayan bir şeyi gerekli:

static void Split(std::vector<std::string>& lst, const std::string& input, const std::string& separators, bool remove_empty = true)
{
    std::ostringstream word;
    for (size_t n = 0; n < input.size(); ++n)
    {
        if (std::string::npos == separators.find(input[n]))
            word << input[n];
        else
        {
            if (!word.str().empty() || !remove_empty)
                lst.push_back(word.str());
            word.str("");
        }
    }
    if (!word.str().empty() || !remove_empty)
        lst.push_back(word.str());
}

İyi bir nokta, içinde separatorsbirden fazla karakter geçirebilmenizdir.


13

Ben strtok kullanarak kendi haddelenmiş ve bir dize bölmek için boost kullandım. Bulduğum en iyi yöntem C ++ String Toolkit Library'dir . İnanılmaz derecede esnek ve hızlı.

#include <iostream>
#include <vector>
#include <string>
#include <strtk.hpp>

const char *whitespace  = " \t\r\n\f";
const char *whitespace_and_punctuation  = " \t\r\n\f;,=";

int main()
{
    {   // normal parsing of a string into a vector of strings
        std::string s("Somewhere down the road");
        std::vector<std::string> result;
        if( strtk::parse( s, whitespace, result ) )
        {
            for(size_t i = 0; i < result.size(); ++i )
                std::cout << result[i] << std::endl;
        }
    }

    {  // parsing a string into a vector of floats with other separators
        // besides spaces

        std::string s("3.0, 3.14; 4.0");
        std::vector<float> values;
        if( strtk::parse( s, whitespace_and_punctuation, values ) )
        {
            for(size_t i = 0; i < values.size(); ++i )
                std::cout << values[i] << std::endl;
        }
    }

    {  // parsing a string into specific variables

        std::string s("angle = 45; radius = 9.9");
        std::string w1, w2;
        float v1, v2;
        if( strtk::parse( s, whitespace_and_punctuation, w1, v1, w2, v2) )
        {
            std::cout << "word " << w1 << ", value " << v1 << std::endl;
            std::cout << "word " << w2 << ", value " << v2 << std::endl;
        }
    }

    return 0;
}

Araç seti, bu basit örneğin göstereceğinden çok daha fazla esnekliğe sahiptir, ancak bir dizeyi yararlı öğelere ayrıştırmadaki faydası inanılmazdır.


13

Kısa ve zarif

#include <vector>
#include <string>
using namespace std;

vector<string> split(string data, string token)
{
    vector<string> output;
    size_t pos = string::npos; // size_t to avoid improbable overflow
    do
    {
        pos = data.find(token);
        output.push_back(data.substr(0, pos));
        if (string::npos != pos)
            data = data.substr(pos + token.size());
    } while (string::npos != pos);
    return output;
}

sınırlayıcı olarak herhangi bir dizeyi kullanabilir, ayrıca ikili verilerle kullanılabilir (std :: string, null'ler dahil ikili verileri destekler)

kullanarak:

auto a = split("this!!is!!!example!string", "!!");

çıktı:

this
is
!example!string

1
Ben ayırıcı bir karakter değil bir dize olmasını sağlar, ancak bu dize yerine, bu nedenle orijinal dize bir kopyasını oluşturulmasını zorluyor çünkü bu çözümü seviyorum.
Alessandro Teruzzi

11

Bunu yaptım çünkü dizeleri ve c tabanlı dizeleri bölmek için kolay bir yola ihtiyacım vardı ... Umarım başka biri de faydalı olabilir. Ayrıca jetonlara dayanmaz ve alanları sınırlayıcı olarak kullanabilirsiniz, bu da ihtiyacım olan başka bir anahtar.

Eminim zarafetini daha da artırmak için yapılabilecek iyileştirmeler var ve lütfen elbette yapın

StringSplitter.hpp:

#include <vector>
#include <iostream>
#include <string.h>

using namespace std;

class StringSplit
{
private:
    void copy_fragment(char*, char*, char*);
    void copy_fragment(char*, char*, char);
    bool match_fragment(char*, char*, int);
    int untilnextdelim(char*, char);
    int untilnextdelim(char*, char*);
    void assimilate(char*, char);
    void assimilate(char*, char*);
    bool string_contains(char*, char*);
    long calc_string_size(char*);
    void copy_string(char*, char*);

public:
    vector<char*> split_cstr(char);
    vector<char*> split_cstr(char*);
    vector<string> split_string(char);
    vector<string> split_string(char*);
    char* String;
    bool do_string;
    bool keep_empty;
    vector<char*> Container;
    vector<string> ContainerS;

    StringSplit(char * in)
    {
        String = in;
    }

    StringSplit(string in)
    {
        size_t len = calc_string_size((char*)in.c_str());
        String = new char[len + 1];
        memset(String, 0, len + 1);
        copy_string(String, (char*)in.c_str());
        do_string = true;
    }

    ~StringSplit()
    {
        for (int i = 0; i < Container.size(); i++)
        {
            if (Container[i] != NULL)
            {
                delete[] Container[i];
            }
        }
        if (do_string)
        {
            delete[] String;
        }
    }
};

StringSplitter.cpp:

#include <string.h>
#include <iostream>
#include <vector>
#include "StringSplit.hpp"

using namespace std;

void StringSplit::assimilate(char*src, char delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }

        }
        else
        {
            delete[] temp;
        }
    }
}

void StringSplit::assimilate(char*src, char* delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }
        }
        else
        {
            delete[] temp;
        }
    }
}

long StringSplit::calc_string_size(char* _in)
{
    long i = 0;
    while (*_in++)
    {
        i++;
    }
    return i;
}

bool StringSplit::string_contains(char* haystack, char* needle)
{
    size_t len = calc_string_size(needle);
    size_t lenh = calc_string_size(haystack);
    while (lenh--)
    {
        if (match_fragment(haystack + lenh, needle, len))
        {
            return true;
        }
    }
    return false;
}

bool StringSplit::match_fragment(char* _src, char* cmp, int len)
{
    while (len--)
    {
        if (*(_src + len) != *(cmp + len))
        {
            return false;
        }
    }
    return true;
}

int StringSplit::untilnextdelim(char* _in, char delim)
{
    size_t len = calc_string_size(_in);
    if (*_in == delim)
    {
        _in += 1;
        return len - 1;
    }

    int c = 0;
    while (*(_in + c) != delim && c < len)
    {
        c++;
    }

    return c;
}

int StringSplit::untilnextdelim(char* _in, char* delim)
{
    int s = calc_string_size(delim);
    int c = 1 + s;

    if (!string_contains(_in, delim))
    {
        return calc_string_size(_in);
    }
    else if (match_fragment(_in, delim, s))
    {
        _in += s;
        return calc_string_size(_in);
    }

    while (!match_fragment(_in + c, delim, s))
    {
        c++;
    }

    return c;
}

void StringSplit::copy_fragment(char* dest, char* src, char delim)
{
    if (*src == delim)
    {
        src++;
    }

    int c = 0;
    while (*(src + c) != delim && *(src + c))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

void StringSplit::copy_string(char* dest, char* src)
{
    int i = 0;
    while (*(src + i))
    {
        *(dest + i) = *(src + i);
        i++;
    }
}

void StringSplit::copy_fragment(char* dest, char* src, char* delim)
{
    size_t len = calc_string_size(delim);
    size_t lens = calc_string_size(src);

    if (match_fragment(src, delim, len))
    {
        src += len;
        lens -= len;
    }

    int c = 0;
    while (!match_fragment(src + c, delim, len) && (c < lens))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

vector<char*> StringSplit::split_cstr(char Delimiter)
{
    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char Delimiter)
{
    do_string = true;

    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

vector<char*> StringSplit::split_cstr(char* Delimiter)
{
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while(*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String,Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char* Delimiter)
{
    do_string = true;
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while (*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

Örnekler:

int main(int argc, char*argv[])
{
    StringSplit ss = "This:CUT:is:CUT:an:CUT:example:CUT:cstring";
    vector<char*> Split = ss.split_cstr(":CUT:");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

Çıktı olacak:

Bu
ise
, bir
örnek,
CString

int main(int argc, char*argv[])
{
    StringSplit ss = "This:is:an:example:cstring";
    vector<char*> Split = ss.split_cstr(':');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This[SPLIT]is[SPLIT]an[SPLIT]example[SPLIT]string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string("[SPLIT]");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This|is|an|example|string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string('|');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

Boş girişleri tutmak için (varsayılan olarak boşluklar hariç tutulacaktır):

StringSplit ss = mystring;
ss.keep_empty = true;
vector<string> Split = ss.split_string(":DELIM:");

Amaç, dizeyi bölmenin şu kadar kolay olduğu C # 'ın Split () yöntemine benzer hale getirmekti:

String[] Split = 
    "Hey:cut:what's:cut:your:cut:name?".Split(new[]{":cut:"}, StringSplitOptions.None);

foreach(String X in Split)
{
    Console.Write(X);
}

Umarım başka biri bunu benim kadar faydalı bulabilir.


10

Peki buna ne dersin:

#include <string>
#include <vector>

using namespace std;

vector<string> split(string str, const char delim) {
    vector<string> v;
    string tmp;

    for(string::const_iterator i; i = str.begin(); i <= str.end(); ++i) {
        if(*i != delim && i != str.end()) {
            tmp += *i; 
        } else {
            v.push_back(tmp);
            tmp = ""; 
        }   
    }   

    return v;
}

Sadece tek bir sınırlayıcı karaktere bölmek istiyorsanız, bu en iyi cevaptır. Orijinal soru, beyaz boşlukta bölünmek istedi, yani bir veya daha fazla ardışık boşluk veya sekmenin herhangi bir kombinasyonu anlamına geliyor. Aslında cevap vermiş stackoverflow.com/questions/53849
Oktalist

10

Bu cevap dizeyi alır ve dizelerin bir vektörüne koyar. Destek kütüphanesini kullanır.

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

9

İşte bunu yapmanın başka bir yolu ..

void split_string(string text,vector<string>& words)
{
  int i=0;
  char ch;
  string word;

  while(ch=text[i++])
  {
    if (isspace(ch))
    {
      if (!word.empty())
      {
        words.push_back(word);
      }
      word = "";
    }
    else
    {
      word += ch;
    }
  }
  if (!word.empty())
  {
    words.push_back(word);
  }
}

9

Bölme ölçütlerini belirlemek için maksimum esneklik sağladıkları için bu görev için boost / regex yöntemlerini kullanmayı seviyorum.

#include <iostream>
#include <string>
#include <boost/regex.hpp>

int main() {
    std::string line("A:::line::to:split");
    const boost::regex re(":+"); // one or more colons

    // -1 means find inverse matches aka split
    boost::sregex_token_iterator tokens(line.begin(),line.end(),re,-1);
    boost::sregex_token_iterator end;

    for (; tokens != end; ++tokens)
        std::cout << *tokens << std::endl;
}

9

Son zamanlarda deve kasalı bir kelimeyi alt kelimelere bölmek zorunda kaldım. Sınırlayıcı yok, sadece üst karakterler var.

#include <string>
#include <list>
#include <locale> // std::isupper

template<class String>
const std::list<String> split_camel_case_string(const String &s)
{
    std::list<String> R;
    String w;

    for (String::const_iterator i = s.begin(); i < s.end(); ++i) {  {
        if (std::isupper(*i)) {
            if (w.length()) {
                R.push_back(w);
                w.clear();
            }
        }
        w += *i;
    }

    if (w.length())
        R.push_back(w);
    return R;
}

Örneğin, bu "AQueryTrades" i "A", "Query" ve "Trades" e böler. İşlev dar ve geniş tellerle çalışır. Mevcut yerel ayara saygı duyduğu için "RaumfahrtÜberwachungsVerordnung" u "Raumfahrt", "Überwachungs" ve "Verordnung" a böler.

Not std::uppergerçekten işlev şablonu argümanı olarak iletilmelidir. Daha sonra bu fonksiyondan daha genelleştirilmiş olan ",", ";"veya gibi sınırlayıcılara bölünebilir " ".


2
2 devir oldu. Bu iyi. İngilizcem çok fazla "Alman" vardı sanki. Bununla birlikte, revizyonist iki küçük hatayı düzeltmedi, çünkü yine de açıktılar: std::isupperargüman olarak geçilebilirdi, değil std::upper. İkinci bir koyun typenameönce String::const_iterator.
Andreas Spindler

9
#include<iostream>
#include<string>
#include<sstream>
#include<vector>
using namespace std;

    vector<string> split(const string &s, char delim) {
        vector<string> elems;
        stringstream ss(s);
        string item;
        while (getline(ss, item, delim)) {
            elems.push_back(item);
        }
        return elems;
    }

int main() {

        vector<string> x = split("thi is an sample test",' ');
        unsigned int i;
        for(i=0;i<x.size();i++)
            cout<<i<<":"<<x[i]<<endl;
        return 0;
}

9

Kullanılması std::string_viewve Eric Niebler en range-v3kütüphanesi:

https://wandbox.org/permlink/kW5lwRCL1pxjp2pW

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"
#include "range/v3/algorithm.hpp"

int main() {
    std::string s = "Somewhere down the range v3 library";
    ranges::for_each(s  
        |   ranges::view::split(' ')
        |   ranges::view::transform([](auto &&sub) {
                return std::string_view(&*sub.begin(), ranges::distance(sub));
            }),
        [](auto s) {std::cout << "Substring: " << s << "\n";}
    );
}

Algoritma foryerine bir aralık döngüsü kullanarak ranges::for_each:

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"

int main()
{
    std::string str = "Somewhere down the range v3 library";
    for (auto s : str | ranges::view::split(' ')
                      | ranges::view::transform([](auto&& sub) { return std::string_view(&*sub.begin(), ranges::distance(sub)); }
                      ))
    {
        std::cout << "Substring: " << s << "\n";
    }
}

Yepp, menzil aralığı daha iyi görünüyor - Kabul ediyorum
Porsche9II
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.