Soru Bir dizenin sözlerini yinelemek için en zarif yol [kapalı]


Bir dizenin sözlerini tekrarlamak için en şık yol nedir? Dizenin, boşluk ile ayrılmış kelimelerden oluştuğu varsayılabilir.

C string fonksiyonlarına veya bu tür bir karakter manipülasyonuna / erişimine ilgi duymadığımı unutmayın. Ayrıca, cevabınızda verim açısından zarafete öncelik verin.

Şu an 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);
}

2638


Menşei


Ahbap ... Elegance kitabımda "verim-bu-görünüyor-güzel" demek için sadece süslü bir yoldur. Sadece bir şablon içinde yer almadığı için C işlevlerini ve hızlı yöntemleri kullanarak utangaç olmayın; - nlaq
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; } - pyon
@ Eduardo: bu da yanlış ... başka bir değer akışını denemek ve bu değeri kullanmak arasındaki isleri test etmeniz gerekiyor, yani. string sub; while (iss >> sub) cout << "Substring: " << sub << '\n'; - Tony Delroy
Varsayılan olarak bunu yapmak için C ++ 'daki çeşitli seçenekler: cplusplus.com/faq/sequences/strings/split - hB0
Oldukça verimli olmaktan çok zarafet var. Zarif özellikler düşük çizgi sayısı ve yüksek okunabilirliği içerir. IMHO Elegance, verimlilik, ancak sürdürülebilirlik için bir proxy değildir. - Matt


Cevaplar:


Değeri için, sadece standart kütüphane olanaklarına dayanan bir giriş dizesinden jeton çıkarmanın başka bir yolu. Bu, STL'nin 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"));
}

Ayıklanan simgeleri bir çıktı akışına kopyalamak yerine, aynı jenerik kullanarak bir kapsayıcıya yerleştirilebilir. copy algoritması.

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

... ya da vector direkt olarak:

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

1189



Bunun için bir sınırlayıcı belirtmek mümkün mü? Örneğin virgüllere ayırmak gibi mi? - l3dx
@Jonathan: \ n bu durumda sınırlayıcı değil, cout'u çıkarmak için ayrıştırıcı. - huy
Bu, başka hiçbir sınırlayıcı almadığı, dolayısıyla ölçeklendirilemez ve sürdürülemediği için kötü bir çözümdür. - SmallChess
Aslında bu kutu Diğer sınırlayıcılarla gayet iyi çalışır (bazılarını biraz çirkin olsa da). İstenen sınırlayıcıları boşluk olarak sınıflandıran bir ctype faset yaratırsınız, o faset içeren bir yerel ayarlar yaratırsınız, daha sonra stringstream'i dizgeyi ayıklamadan önce o yerel diziyle emersiniz. - Jerry Coffin
@Kinderchocolate "Dize, boşluk ile ayrılmış kelimelerden oluşmuş olabilir" - Hmm, sorunun problemine kötü bir çözüm gibi gelmiyor. "ölçeklendirilemez ve temel alınamaz" - Hah, hoş olan. - Christian Rau


Bunu bir sınırlayıcı ile dizgeyi ayırmak için kullanıyorum. İlk önce 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::stringstream ss(s);
    std::string item;
    while (std::getline(ss, 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ş jetonları atlamayacağından, aşağıdakilerden birinin boş olan 4 öğeyi bulacağını unutmayın:

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

2308



Boş jetonları atlamaktan kaçınmak için empty() Kontrol: if (!item.empty()) elems.push_back(item) - 0x499602D2
Sınırın iki karaktere sahip olması nasıl olur? ->? - herohuyongtao
@herohuyongtao, bu çözüm sadece tek char sınırlayıcılar için çalışır. - Evan Teran
@JeshwanthKumarNK, bu gerekli değil, ama sonuç gibi doğrudan bir işleve geçmek gibi şeyler yapmanıza izin verir: f(split(s, d, v)) hala önceden tahsis edilmiş olanın avantajına sahip olmak vector Eğer hoşuna giderse. - Evan Teran
Caveat: split ("one: two :: three", ":") ve split ("one: two :: three:", ":") aynı değeri döndürür. - dshin


Boost kullanarak olası bir çözüm:

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

Bu yaklaşım daha hızlı olabilir stringstream yaklaşım. Ve bu genel bir şablon işlevi olduğundan, her çeşit sınırlayıcıyı kullanarak diğer dizge türlerini (wchar, vb. Veya UTF-8) ayırmak için kullanılabilir.

Bakın belgeleme detaylar için.


794



Hız, bu olguların her ikisi de strtok benzeri bir işleve göre çok daha yavaş olduğundan, bu konuyla ilgisizdir. - Tom
Ve zaten destek olmayanlar için ... bcp bunun için 1,000'den fazla dosya kopyalıyor :) - Roman Starkov
strtok bir tuzaktır. iş parçacığı güvenli değil. - tuxSlayer
@Ian Embedded geliştiricileri, tüm destek kullanmıyor. - ACK_stoverflow
Bir ek olarak: Ben sadece zorunlu olarak kullanıyorum, normalde kendim ve taşınabilir olan kod kütüphaneme eklemeyi tercih ederim, böylece belirli bir amacı yerine getiren küçük hassas özel kodlar elde edebilirim. Bu şekilde kod kamuya açık değil, performans, önemsiz ve taşınabilir. Boost'un bir yeri var ama ben onun dizginleme ipleri için bir miktar fazlalık olmasını tavsiye ederim: bir evin asılması için bir mühendislik firmasına nakledilmiş yeni bir çiviyi duvara asmak için bir çivi çakmak zorunda kalmazsınız. Son derece iyi, ama prosare eksileri tarafından çok ağır basıyordu. - GMasucci


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

321



çok kötü sadece boşluklara ayrılıyor ' '... - Offirmo


Kod boyutu için tüm verimliliği feda etmek ve şıklık türü olarak "verimli" görmek için iyi oturmayanlar için, aşağıdakiler tatlı bir noktaya çarpmalıdır (ve şablon kapsayıcı sınıfının son derece şık bir ekleme 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 ederim std::vector<std::string> ikinci parametrem olarak türleriContainerT)... fakat list<> daha hızlı vector<> Doğrudan erişim gerekmediğinde ve hatta kendi string sınıfınızı oluşturabileceğiniz ve std::list<subString> nerede subString inanılmaz hız artışları için herhangi bir kopya yapmaz.

Bu sayfadaki en hızlı belirteçlerden iki kat daha hızlı ve diğerlerinden yaklaşık 5 kat daha hızlı. Ayrıca mükemmel parametre tipleri ile ek hız artışları için tüm dizgeyi ve liste kopyalarını kaldırabilirsiniz.

Buna ek olarak, sonucun (aşırı derecede verimsiz) geri dönüşünü yapmaz, ancak referanslar olarak simgeleri geçirir, böylece dilediğinizde birden fazla çağrı kullanarak belirteçleri oluşturmanıza izin verir.

Son olarak, boş jetonları son isteğe bağlı bir parametre ile sonuçlardan kesilip kesilmeyeceğini belirtmenize izin verir.

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


168



Ben bunun bir hayranıyım, ama g ++ (ve muhtemelen iyi uygulama) için bunu kullanan herkes typedefs ve typenames ister: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType;  Daha sonra buna göre value_type ve size_types yerine koymak. - aws
Şablonlar ve ilk yorumların tamamen yabancı olduğu bizler için, gerekli olan bir kullanım örneği cmplete hoş olurdu. - Wes Miller
Ahh, ben anladım. C ++ satırlarını aws'ın yorumundan tokenize () işlev gövdesine koydum, daha sonra totkens.push_back () satırlarını ContainerT :: value_type değerini sadece ValueType olarak değiştirip (ContainerT :: value_type :: size_type) olarak değiştirdim. Beden Çeşidi). Sabitlenmiş bitler g ++ hakkında sızlanmaktaydı. Sadece tokenize olarak çağırmak (some_string, some_vector); - Wes Miller
Örnek verilere ilişkin bir kaç performans testi yürütmenin yanı sıra öncelikle mümkün olduğunca az sayıda talimatı azaltmış oldum ve aynı zamanda diğer dizelerdeki sadece ofsetleri / uzunlukları referans alan bir alt-sınıf sınıfının kullanılmasıyla mümkün olan en az bellek kopyalarını azalttım. (Kendi kendimi yuvarladım ama başka bazı uygulamalar var). Ne yazık ki, bunu geliştirmek için yapabileceğimiz çok fazla şey yok, ama artan artışlar mümkün. - Marius
Bu ne zaman için doğru çıktı trimEmpty = true. Unutmayın ki "abo" bu cevapta bir sınırlayıcı değil, sınırlayıcı karakterlerin listesidir. Tek bir sınırlayıcı karakter dizisi almak için onu değiştirmek kolay olurdu (bence str.find_first_of değişmeli str.find_firstama yanılıyor olabilirim ... test edemiyorum - Marius


İşte başka bir çözüm. Kompakt ve makul derecede 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;
}

Yaylı ayırıcıları, geniş telleri vb. İşlemek için kolayca ayarlanabilir.

Bölme not "" tek bir boş dizge ve bölme ile sonuçlanır "," (yani sep) iki boş dizeyle sonuçlanır.

Boş jetonları 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ş tokenleri atlarken bir dizgiyi çoklu sınırlayıcılara bölmek istenirse, 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;
}

150



İlk versiyon basit ve işi mükemmel bir şekilde tamamlıyor. Yapacağım tek değişiklik, bir parametre olarak geçmek yerine sonucu doğrudan döndürmek olacaktır. - gregschlom
Çıkış, verimlilik için bir parametre olarak geçirilir. Sonuç döndürülürse, ya vektörün bir kopyasını ya da daha sonra serbest bırakılması gereken bir yığın tahsisini gerektirir. - Alec Thomas
Yukarıda yaptığım yorumun hafif bir eki: C ++ 11 hareket semantiği kullanılıyorsa, bu işlev cezasız bir şekilde vektörü iade edebilir. - Alec Thomas
@AlecThomas: C ++ 11'den önce bile, çoğu derleyici NRVO üzerinden dönüş kopyasını optimize etmiyor mu? (Zaten +1; çok özlü) - Marcelo Cantos
Tüm cevaplardan bu, en çekici ve esnek biri olarak görünüyor. Daha az belirgin bir çözüm olmasına rağmen, bir sınırlayıcı ile getline birlikte. C ++ 11 standardının bunun için bir önemi yok mu? C ++ 11 bu günlerde yumruk kartlarını destekliyor mu? - Spacen Jasset


Bu, bir ip üzerinden yinelemenin en sevdiğim yoludur. Her kelime için 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...
}

106



Beyan etmek mümkün mü word olarak char? - abatishchev
Üzgünüm abatishchev, C ++ benim güçlü noktam değil. Ancak, her kelimedeki her karakterden bir döngü oluşturmak için bir iç döngü eklemenin zor olmayacağını hayal ediyorum. Fakat şu anda mevcut döngünün kelime ayırma boşluklarına bağlı olduğuna inanıyorum. Her uzay arasında sadece tek bir karakter olduğunu bilmiyorsanız, bu durumda, bir char'a "kelime" yazabilirsin ... daha fazla yardım edemediğim için üzgünüm, C ++'mda fırçalamak anlamına geldim - gnomed
Eğer sözcüğü bir char olarak ilan ederseniz, her bir boşluk olmayan karakter üzerinde yineleyecektir. Denemek için yeterince basit: stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c; - Wayne Werner


Bu Stack Overflow sorusuna benzer C ++'daki bir dizeyi nasıl belirtebilirim?.

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

76



Bu, tüm simgelerin bir kopyasını oluşturur mu, yoksa yalnızca geçerli belirtecin başlangıç ​​ve bitiş konumunu tutuyor mu? - einpoklum


Aşağıdakileri beğendim çünkü sonuçları bir vektöre koyuyor, bir ipi bir sınır olarak destekliyor ve boş değerleri tutmayı kontrol ediyor. Ama o kadar 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"));
}

Tabii ki, Boost bir split() Bu kısmen böyle çalışır. Ve, eğer 'beyaz boşluk' ile, gerçekten Boost'un bölünmesini kullanarak her türlü beyaz boşluk anlamına gelirsiniz. is_any_of() harika çalışıyor.


65



Sonunda dizenin her iki tarafında boş jetonları doğru şekilde kullanan bir çözüm - fmuecke