ฉันจะค้นหา / ค้นหาและแทนที่ในสตริงมาตรฐานได้อย่างไร


95

มีวิธีแทนที่การเกิดขึ้นทั้งหมดของสตริงย่อยด้วยสตริงอื่นstd::stringหรือไม่?

ตัวอย่างเช่น:

void SomeFunction(std::string& str)
{
   str = str.replace("hello", "world"); //< I'm looking for something nice like this
}

คำตอบ:


75

ทำไมไม่ใช้การแทนที่ของคุณเอง?

void myReplace(std::string& str,
               const std::string& oldStr,
               const std::string& newStr)
{
  std::string::size_type pos = 0u;
  while((pos = str.find(oldStr, pos)) != std::string::npos){
     str.replace(pos, oldStr.length(), newStr);
     pos += newStr.length();
  }
}

3
คุณกำลังยุ่งกับหน่วยความจำที่นี่ด้วยการเรียก "แทนที่" ทั้งหมด: ความซับซ้อนจะเป็นn²ถ้าคุณลบ "o" จาก "ooooooo ... o" ฉันเดาว่าหนึ่งสามารถทำได้ดีกว่า แต่การแก้ปัญหานี้มีข้อดีของการเข้าใจง่าย
Zonko

1
เหตุใดจึงไม่เป็นจริงสำหรับลูปแทนที่จะสับสนสำหรับลูป
ชิริก

ฉันเคยใช้หลักการ 'เซอร์ไพรส์น้อยที่สุด' สำหรับลูปใช้สำหรับการเพิ่มดัชนีอย่างง่ายโดยส่วนใหญ่ ตามที่ฉันพูดในขณะที่ลูปชัดเจนกว่า
yves Baumes

1
@aldo ตามกฎทั่วไปควรหลีกเลี่ยงความซับซ้อนและตัวอย่างเช่นใช้ regex ตามที่กล่าวไว้ในการตอบกลับ แต่ขึ้นอยู่กับความต้องการของคุณคุณอาจต้องการควบคุมการอ้างอิงโครงการของคุณ ข้อมูลโค้ดเล็ก ๆ น้อย ๆ ที่ทำในสิ่งที่คุณต้องการไม่ต้องใช้อีกต่อไปในบางครั้งก็ดีกว่า
yves Baumes

159
#include <boost/algorithm/string.hpp> // include Boost, a C++ library
...
std::string target("Would you like a foo of chocolate. Two foos of chocolate?");
boost::replace_all(target, "foo", "bar");

นี่คือเอกสารอย่างเป็นทางการเกี่ยวกับ replace_all


1
โปรดทราบว่าคุณไม่จำเป็นต้องสร้าง std :: string สำหรับรูปแบบและการแทนที่อย่างชัดเจน: boost :: replace_all (target, "foo", "bar");
Alexis Wilke

4
+1 โดยมีข้อแม้: replace_allจะ segfault สำหรับรุ่นที่เพิ่ม> 1.43 ใน Sun Studio สำหรับเวอร์ชันใด ๆ <12.3
Brian Vandenberg

3
boostเพิ่มเวลาในการคอมไพล์อย่างมากบนอุปกรณ์ฝังตัว แม้แต่ ARMv7 quad core โค้ด 100 บรรทัดคอมไพล์ใน 2 นาทีโดยไม่ต้องบูสต์ 2 วินาที
Piotr Kula

4
@ppumkin: นั่นหมายความว่าคอมไพเลอร์ของคุณ (หรือบิลด์เซ็ตอัพหรืออะไรก็ตาม) มันห่วยไม่ใช่สถาปัตยกรรมเป้าหมายซึ่งไม่มีส่วนเกี่ยวข้องกับมัน
Daniel Kamil Kozar

หากคอมไพเลอร์ของคุณรองรับส่วนหัวที่คอมไพล์ไว้ล่วงหน้าขอแนะนำอย่างยิ่งให้ใช้เมื่อใช้บูสต์ ประหยัดเวลาจริงๆ
Alexey Omelchenko

33

ใน C ++ 11 คุณสามารถทำสิ่งนี้เป็นซับเดียวโดยเรียกไปที่regex_replace:

#include <string>
#include <regex>

using std::string;

string do_replace( string const & in, string const & from, string const & to )
{
  return std::regex_replace( in, std::regex(from), to );
}

string test = "Remove all spaces";
std::cout << do_replace(test, " ", "") << std::endl;

เอาต์พุต:

Removeallspaces

ขอบคุณมากใช้งานง่ายและจำได้!
Julian Declercq

โปรดสังเกตด้วยว่าfromอาจเป็นนิพจน์ทั่วไปดังนั้นคุณสามารถใช้เกณฑ์การจับคู่ที่ซับซ้อนมากขึ้นได้หากต้องการ สิ่งที่ฉันไม่เห็นคือวิธีการทำโดยไม่ใช้รูปแบบของการแยกวิเคราะห์นิพจน์ทั่วไป - แทนที่จะใช้การตีความfromตัวละครโดยตรง
Brent Bradburn

ซึ่งอาจต้องใช้คอมไพเลอร์ที่ทันสมัย ใช้งานได้กับ gcc 5.0 แต่ฉันมีปัญหากับ gcc 4.8.4
Brent Bradburn

@nobar ใช่ถ้าฉันจำได้อย่างถูกต้องการสนับสนุน regex ใน 4.8.x ยังไม่สมบูรณ์ นอกจากนี้คุณยังสามารถค้นหาที่ซับซ้อนกว่านี้ได้ แต่คุณจะได้รับเวลาลงโทษอย่างชาญฉลาด ... มันจะช้ากว่าการค้นหาแบบตรงไปตรงมาและแทนที่ฟังก์ชันอื่น ๆ
Alexis Wilke

2
โปรดทราบว่าสิ่งนี้จะใช้ได้เฉพาะกับอักขระที่เป็นตัวเลขและตัวอักษรพื้นฐานเท่านั้นและไม่มีอะไรอื่นโดยไม่ต้องทำการประมวลผลล่วงหน้าจำนวนมากขึ้นอยู่กับประเภทของสตริง ฉันยังไม่พบการแทนที่สตริงตาม regex สำหรับวัตถุประสงค์ทั่วไป
Piyush Soni

17

ทำไมไม่ส่งคืนสตริงที่แก้ไข

std::string ReplaceString(std::string subject, const std::string& search,
                          const std::string& replace) {
    size_t pos = 0;
    while((pos = subject.find(search, pos)) != std::string::npos) {
         subject.replace(pos, search.length(), replace);
         pos += replace.length();
    }
    return subject;
}

หากคุณต้องการประสิทธิภาพนี่คือฟังก์ชันที่ปรับให้เหมาะสมซึ่งจะปรับเปลี่ยนสตริงอินพุตโดยจะไม่สร้างสำเนาของสตริง:

void ReplaceStringInPlace(std::string& subject, const std::string& search,
                          const std::string& replace) {
    size_t pos = 0;
    while((pos = subject.find(search, pos)) != std::string::npos) {
         subject.replace(pos, search.length(), replace);
         pos += replace.length();
    }
}

การทดสอบ:

std::string input = "abc abc def";
std::cout << "Input string: " << input << std::endl;

std::cout << "ReplaceString() return value: " 
          << ReplaceString(input, "bc", "!!") << std::endl;
std::cout << "ReplaceString() input string not changed: " 
          << input << std::endl;

ReplaceStringInPlace(input, "bc", "??");
std::cout << "ReplaceStringInPlace() input string modified: " 
          << input << std::endl;

เอาท์พุต:

Input string: abc abc def
ReplaceString() return value: a!! a!! def
ReplaceString() input string not modified: abc abc def
ReplaceStringInPlace() input string modified: a?? a?? def

6

การค้นหาและแทนที่แบบอินไลน์ในเทมเพลตของฉัน:

template<class T>
int inline findAndReplace(T& source, const T& find, const T& replace)
{
    int num=0;
    typename T::size_t fLen = find.size();
    typename T::size_t rLen = replace.size();
    for (T::size_t pos=0; (pos=source.find(find, pos))!=T::npos; pos+=rLen)
    {
        num++;
        source.replace(pos, fLen, replace);
    }
    return num;
}

จะส่งคืนจำนวนรายการที่แทนที่ (สำหรับใช้หากคุณต้องการรันสิ่งนี้ต่อเนื่อง ฯลฯ ) วิธีใช้:

std::string str = "one two three";
int n = findAndReplace(str, "one", "1");

4
ฉันลองใช้ตัวอย่างนี้ภายใต้ GCC แต่มันไม่รวบรวม - ไม่ชอบการใช้ T :: size_t การแทนที่ T :: size_t ด้วย typename T :: size_type ช่วยแก้ปัญหาได้
Andrew Wyatt

3

วิธีที่ง่ายที่สุด (นำเสนอสิ่งที่อยู่ใกล้กับสิ่งที่คุณเขียน) คือการใช้Boost.Regexเฉพาะregex_replace

std :: string มีเมธอด find () และ replace () อยู่ในตัว แต่จะยุ่งยากกว่าในการใช้งานเนื่องจากต้องจัดการกับดัชนีและความยาวสตริง


3
นอกจากนี้ยังมีอัลกอริธึมสตริงการเพิ่มซึ่งรวมถึง replace_all (regex อาจมีน้ำหนักมากสำหรับการแทนที่แบบง่ายๆเช่นนี้)
UncleBens

3

ฉันเชื่อว่ามันจะได้ผล ใช้ const char * เป็นพารามิเตอร์

//params find and replace cannot be NULL
void FindAndReplace( std::string& source, const char* find, const char* replace )
{
   //ASSERT(find != NULL);
   //ASSERT(replace != NULL);
   size_t findLen = strlen(find);
   size_t replaceLen = strlen(replace);
   size_t pos = 0;

   //search for the next occurrence of find within source
   while ((pos = source.find(find, pos)) != std::string::npos)
   {
      //replace the found string with the replacement
      source.replace( pos, findLen, replace );

      //the next line keeps you from searching your replace string, 
      //so your could replace "hello" with "hello world" 
      //and not have it blow chunks.
      pos += replaceLen; 
   }
}

ระบุว่าsize_typeสำหรับสตริงเป็นunsignedของคุณตรวจสอบในสภาพที่วงจะเป็น>= trueคุณต้องใช้ที่std::string::nposนั่น
Pavel Minaev

size_type ไม่ได้ลงนาม ไม่ได้ลงนามในหลายแพลตฟอร์ม แต่ไม่ใช่ทั้งหมด
อลัน

12
ทำไมในโลกนี้ถึงไม่เป็นส่วนหนึ่งของ std :: string? มีคลาส String ที่จริงจังอื่น ๆ ในโลกของการเขียนโปรแกรมที่ไม่มีการดำเนินการ 'ค้นหาและแทนที่' หรือไม่? แน่นอนว่ามันเป็นเรื่องปกติมากกว่าการมีตัวทำซ้ำสองตัวและต้องการแทนที่ข้อความระหว่างพวกเขา ?? บางครั้ง std :: string ให้ความรู้สึกเหมือนรถที่มีกระจกบังลมแบบปรับสเปกตรัมได้ แต่ไม่มีทางที่จะกลิ้งลงหน้าต่างคนขับ
Spike0xff

@ Spike0xff boost มีroll_down_window
ta.speot.is

1
@gustafr: ความผิดพลาดของฉัน ฉันทำงานในระบบที่คอมไพเลอร์รุ่นเก่ากำหนด size_t ไม่ถูกต้อง
Alan

1
// Replace all occurrences of searchStr in str with replacer
// Each match is replaced only once to prevent an infinite loop
// The algorithm iterates once over the input and only concatenates 
// to the output, so it should be reasonably efficient
std::string replace(const std::string& str, const std::string& searchStr, 
    const std::string& replacer)
{
    // Prevent an infinite loop if the input is empty
    if (searchStr == "") {
        return str;
    }

    std::string result = "";
    size_t pos = 0;
    size_t pos2 = str.find(searchStr, pos);

    while (pos2 != std::string::npos) {
        result += str.substr(pos, pos2-pos) + replacer;
        pos = pos2 + searchStr.length();
        pos2 = str.find(searchStr, pos);
    }

    result += str.substr(pos, str.length()-pos);
    return result;
}

1
เราจำเป็นต้องค้นหาการแข่งขันใหม่จากนัดสุดท้ายเท่านั้นนั่นเป็นเหตุผลที่อัลกอริทึมติดตามการแข่งขันล่าสุดในตำแหน่งอย่างระมัดระวัง pos2 เก็บการจับคู่ครั้งต่อไปเสมอดังนั้นเราจึงเชื่อมสตริงระหว่าง pos และ pos2 เข้ากับผลลัพธ์จากนั้นเลื่อน pos และ pos2 หากไม่พบการจับคู่อื่นเราจะเชื่อมส่วนที่เหลือของสตริงเข้าด้วยกัน
Björn Ganster

1
#include <string>

using std::string;

void myReplace(string& str,
               const string& oldStr,
               const string& newStr) {
  if (oldStr.empty()) {
    return;
  }

  for (size_t pos = 0; (pos = str.find(oldStr, pos)) != string::npos;) {
    str.replace(pos, oldStr.length(), newStr);
    pos += newStr.length();
  }
}

การตรวจสอบ oldStr ว่างเปล่าเป็นสิ่งสำคัญ หากพารามิเตอร์ว่างเปล่าด้วยเหตุผลใดก็ตามคุณจะติดอยู่ในลูปที่ไม่มีที่สิ้นสุด

แต่ให้ใช้โซลูชัน C ++ 11 หรือ Boost ที่ผ่านการทดลองและทดสอบแล้วหากทำได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.