افراز رشته ها (Strings) در ++C – به زبان ساده


یکی از وظایف رایج در برنامهنویسی، افراز رشته های جدا شده با یک کاراکتر خاص به صورت آرایهای از توکنها است. برای نمونه ممکن است لازم باشد که یک رشته را از کاراکترهای فاصله جدا کنیم و به صوت آرایهای از کلمات دربیاوریم. زبانهایی مانند جاوا و پایتون در این زمینه برنامهنویسی، ++C را شکست میدهند، زیرا هر دو این زبانهای برنامهنویسی، کتابخانههای استانداردی بدین منظور دارند؛ در حالی که ++C چنین ابزارهایی ندارد. با این وجود، این وظیفه را نیز میتوان در ++C به روشهای مختلف انجام داد.
در جاوا این وظیفه را میتوان با استفاده از متد String.split به صورت زیر اجرا کرد:
String Str = "The quick brown fox jumped over the lazy dog."; String[] Results = Str.split(" ");
این وظیفه در زبان پایتون با استفاده از متد str.split به صورت زیر انجام میشود:
Str = "The quick brown fox jumped over the lazy dog." Results = Str.split()
در ++C کار به این سادگیها نیست؛ البته نشدنی هم نیست و میتوان این کار را به روشهای مختلفی انجام داد.
استفاده از کلاس basic_istringstream
احتمالاً سادهترین روش اجرای این وظیفه در ++C، استفاده از کلاس basic_istringstream به صورت زیر باشد:
size_t splitWithStringStream( const std::basic_string &str, std::vector< std::basic_string > &tokens) { typedef std::basic_string my_string; typedef std::vector< std::basic_string > my_vector; typedef std::basic_istringstream my_istringstream; tokens.clear(); if (str.empty()) { return 0; } my_istringstream iss(str); std::copy( std::istream_iterator(iss), std::istream_iterator(), std::back_inserter(tokens)); return tokens.size(); }
این تابع را میتوان به صورت زیر استفاده کرد:
std::string str("The quick brown fox jumped over the lazy dog."); std::vector<std::string> tokens; size_t s = splitWithStringStream(str, tokens);
تابع فوق این مزیت را دارد که لازم نیست از چیزی به جز خود تابع که بخشی از کتابخانه قالب استاندارد (Standard Template) یا STL است، استفاده کرد. با این حال، این روش دو عیب دارد. نخست این که این تابع به طور بالقوه ناکافی و کُند است و از آنجا که کل رشته در حافظه کپی میشود، حافظهای به اندازه رشته اصلی اشغال میکند. دوم این که تنها از رشتههای جدا شده با کاراکتر فاصله (Space) پشتیبانی میکند.
تابع زیر با ایجاد امکان استفاده از کاراکتری به جز فاصله به عنوان جداکننده، مشکل دوم را حل میکند.
template<typename charType> size_t splitWithStringStream( const std::basic_string<charType> &str, const charType delim, std::vector< std::basic_string<charType> > &tokens) { typedef std::basic_string<charType> my_string; typedef std::basic_istringstream<charType> my_istringstream; tokens.clear(); if (str.empty()) { return 0; } my_istringstream iss(str); my_string token; while (std::getline(iss, token, delim)) { tokens.push_back(token); } return tokens.size(); }
تابع فوق را میتوان به صورت زیر استفاده کرد:
std::wstring str(L"This is a test.||This is only a test.|This concludes this test."); std::vector<std::wstring> tokens; size_t s = splitWithStringStream(str, L'|', tokens);
توجه کنید که این تابع از ایجاد توکنهای خالی اجتناب نمیکند و از این رو مثال فوق توکنهایی با چهار آیتم تولید میکند که یکی از آنها رشتهای خالی است.
این تابع مانند نسخه تابع splitWithStringStream که فاقد پارامتر کاراکتر جداساز است، از مشکل ناکافی و کند بودن به صورت بالقوه رنج میبرد. این تابع امکان تعریف کاراکتر جداساز را فراهم میکند. با این وجود، تنها از یک کاراکتر جداساز منفرد پشتیبانی میکند. این تابع از رشتههایی که در آن چندین کاراکتر جداساز استفاده شده باشند پشتیبانی نمیکند.
تنها کاری که باید انجام دهیم این است که دو تابع splitWithStringStream مختلف را طوری کامپایل کنیم که شامل فایلهای هدر iostream، sstream، string، vector، و iterator باشند.
راهحل اول برای استفاده از صرفاً اعضای کلاس basic_string
اجرای وظیفه فوق از طریق استفاده از صرفاً اعضای تابع کلاس basic_string ممکن است.
تابع زیر امکان تعیین کاراکتر جداساز را فراهم میسازد و تنها از اعضای ind_first_not_of، find و substr کلاس basic_string استفاده میکند. این تابع پارامترهای اختیاری نیز دارد که میتوان تعیین کرد توکنهای خالی مجاز هستند یا نه و این که تعداد بیشینه قطعههایی که رشته باید به آن افراز شود چقدر است.
template<typename charType> size_t splitWithBasicString( const std::basic_string<charType> &str, const charType delim, std::vector< std::basic_string<charType> > &tokens, const bool trimEmpty = false, const size_t maxTokens = (size_t)(-1)) { typedef std::basic_string<charType> my_string; typedef typename my_string::size_type my_size_type; tokens.clear(); if (str.empty()) { return 0; } my_size_type len = str.length(); // Skip delimiters at beginning. my_size_type left = str.find_first_not_of(delim, 0); size_t i = 1; if (! trimEmpty && 0 != left) { tokens.push_back(my_string()); ++ i; } while (i < maxTokens) { my_size_type right = str.find(delim, left); if (right == my_string::npos) { break; } if (! trimEmpty || right - left > 0) { tokens.push_back(str.substr(left, right - left)); ++ i; } left = right + 1; } if (left < len) { tokens.push_back(str.substr(left)); } return tokens.size(); }
این تابع از آن مشکلات عملکردی تابعهای مبتنی بر stream قبلی رنج نمیبرد و امکان تعیین کاراکتر جداساز را نیز فراهم ساخته است. با این وجود، تنها از یک کاراکتر جداساز منفرد پشتیبانی میکند. این تابع از رشتههایی که به وسیله چند کاراکتر جداساز تقسیم میشوند، پشتیبانی نمیکند.
تنها کار مورد نیاز برای کامپایل این تابع، این است که فایلهای هدر string و vector را include کنیم.
راهحل دوم برای استفاده از صرفاً اعضای کلاس basic_string
برخی اوقات رشتهای که باید افراز شود از چندین کاراکتر جداساز مختلف استفاده کرده است. در برخی موارد دیگر دانستن این که کدام کاراکتر جداساز در رشته استفاده شده، از قبل ناممکن است. در این موارد باید بدانیم که کاراکتر جداساز میتواند حالتهای مختلفی داشته باشد. در این صورت لازم است که تابع ما بتواند رشتهای که شامل کاراکترهای جداساز احتمالی است را بپذیرد. این وضعیت نیز با استفاده صرف از اعضای تابع کلاس basic_string ممکن است.
تابع زیر امکان تعیین کاراکتر جداساز و استفاده از صرفاً اعضای ind_first_not_of، find_first_of و substr کلاس basic_string را فراهم میسازد. ضمناً این تابع پارامترهای اختیاری دارد که امکان تعیین نادیده گرفتن توکنهای خالی و همچنین تعیین تعداد بیشینه قطعههایی که رشته باید افراز شود را دارد.
template<typename charType> size_t splitWithBasicString( const std::basic_string<charType> &str, const std::basic_string<charType> &delim, std::vector< std::basic_string<charType> > &tokens, const bool trimEmpty = false, const size_t maxTokens = (size_t)(-1)) { typedef std::basic_string<charType> my_string; typedef typename my_string::size_type my_size_type; tokens.clear(); if (str.empty()) { return 0; } my_size_type len = str.length(); // Skip delimiters at beginning. my_size_type left = str.find_first_not_of(delim, 0); size_t i = 1; if (! trimEmpty && 0 != left) { tokens.push_back(my_string()); ++ i; } while (i < maxTokens) { my_size_type right = str.find_first_of(delim, left); if (right == my_string::npos) { break; } if (! trimEmpty || right - left > 0) { tokens.push_back(str.substr(left, right - left)); ++ i; } left = right + 1; } if (left < len) { tokens.push_back(str.substr(left)); } return tokens.size(); }
استفاده از Boost
Boost مجموعهای از کتابخانههای متن-باز، چند پلتفرمی و بازبینیشده از سوی برنامهنویسان دیگر (peer-reviewed) است که برای تکمیل و گسترش کتابخانه قالب استاندارد ++C طراحی شده است. Boost دستکم دو متد برای افراز یک رشته ارائه میکند.
یک گزینه استفاده از تابع split در کتابخانه الگوریتم رشته Boost است.
به منظور استفاده از تابع split کافی است <boost/algorithm/string.hpp> را include کنید و سپس تابع را به صورت زیر فراخوانی کنید:
std::string str(" The quick brown fox\tjumped over the lazy dog."); std::vector<std::string> strs; boost::split(strs, str, boost::is_any_of("\t "));
گزینه دیگر استفاده از کتابخانه توکنایزر Boost به نام «BoostTokenizer» است. به منظور استفاده از این کتابخانه باید <boost/tokenizer.hpp> را include کنید و سپس به صورت زیر از آن استفاده کنید:
typedef boost::char_separator<char> my_separator; typedef boost::tokenizer<my_separator> my_tokenizer; std::string str(" The quick brown fox\tjumped over the lazy dog."); my_separator sep(" \t"); my_tokenizer tokens(str, sep); my_tokenizer::iterator itEnd = tokens.end(); for (my_tokenizer::iterator it = tokens.begin(); it != itEnd; ++ it) { std::cout << *it << std::endl; }
استفاده از کتابخانه کیت ابزار رشته ++C
گزینه دیگر استفاده از کتابخانه C++ String Toolkit است. در مثال زیر روش استفاده از تابع strtk::parse برای افراز یک رشته را مشاهده میکنید:
std::string str("The quick brown fox jumped over the lazy dog."); std::vector<std::string> tokens; strtk::parse(str, " ", tokens);
البته گزینههای زیاد دیگری نیز وجود دارند که میتوانید برای افراز رشتهها در ++C استفاده کنید؛ اما مواردی که در این راهنما معرفی کردیم تقریباً همه مواردی که ممکن است مورد نیاز شما باشد را پوشش میدهند.
اگر این نوشته برای شما مفید بوده است، پیشنهاد میکنیم از منابع زیر نیز استفاده کنید:
- مجموعه آموزشهای برنامهنویسی
- آموزش پیشرفته C++ (شی گرایی در سی پلاس پلاس)
- مجموعه آموزشهای دروس مهندسی کامپیوتر
- مجموعه آموزشهای پروژه محور برنامهنویسی
- آموزش اشاره گر در برنامه نویسی پیشرفته ++C
- آموزش پروژه محور پردازش تصویر با OpenCV در ++C – تشخیص چهره
==
سلام وقت بخیر. من تاریخ لاگ این و لاگ اوت شدن دو کارمند رو گرفتم. حالا قرار که این دوتا رو از توی فایل دربیارم و از هم کم کنم و ساعات حضورش رو بدست بیارم. شما به نظرتون با ید چیکار کنم؟