使用C++将字符串拆分为键值对
Split string into key-value pairs using C++
我有一个这样的字符串:
"CA: ABCDnCB: ABFGnCC: AFBVnCD: 4567"
现在": "
从值中分离键,而n
则分离对。我想将键值对添加到C++中的映射中。
考虑到优化,有什么有效的方法可以做到这一点吗?
这里有两个方法。第一个是我一直使用的简单、明显的方法(性能很少是问题(。第二种方法可能更有效,但我还没有进行任何正式的计时。
在我的测试中,第二种方法大约快3倍。
#include <map>
#include <string>
#include <sstream>
#include <iostream>
std::map<std::string, std::string> mappify1(std::string const& s)
{
std::map<std::string, std::string> m;
std::string key, val;
std::istringstream iss(s);
while(std::getline(std::getline(iss, key, ':') >> std::ws, val))
m[key] = val;
return m;
}
std::map<std::string, std::string> mappify2(std::string const& s)
{
std::map<std::string, std::string> m;
std::string::size_type key_pos = 0;
std::string::size_type key_end;
std::string::size_type val_pos;
std::string::size_type val_end;
while((key_end = s.find(':', key_pos)) != std::string::npos)
{
if((val_pos = s.find_first_not_of(": ", key_end)) == std::string::npos)
break;
val_end = s.find('n', val_pos);
m.emplace(s.substr(key_pos, key_end - key_pos), s.substr(val_pos, val_end - val_pos));
key_pos = val_end;
if(key_pos != std::string::npos)
++key_pos;
}
return m;
}
int main()
{
std::string s = "CA: ABCDnCB: ABFGnCC: AFBVnCD: 4567";
std::cout << "mappify1: " << 'n';
auto m = mappify1(s);
for(auto const& p: m)
std::cout << '{' << p.first << " => " << p.second << '}' << 'n';
std::cout << "mappify2: " << 'n';
m = mappify2(s);
for(auto const& p: m)
std::cout << '{' << p.first << " => " << p.second << '}' << 'n';
}
输出:
mappify1:
{CA => ABCD}
{CB => ABFG}
{CC => AFBV}
{CD => 4567}
mappify2:
{CA => ABCD}
{CB => ABFG}
{CC => AFBV}
{CD => 4567}
这种格式被称为"标记值"。
行业中使用这种编码的性能最关键的地方可能是金融FIX协议(=
表示键值分隔符,' 01'
表示条目分隔符(。因此,如果你使用的是x86硬件,那么你最好的选择是在谷歌上搜索"SSE4 FIX协议解析器github",并重用HFT商店的开源发现。
如果您仍然想将矢量化部分委托给编译器,并且可以留出几纳秒的可读性,那么最优雅的解决方案是将结果存储在std::string
(数据(+boost::flat_map<boost::string_ref, boost::string_ref>
(视图(中。解析是一个品味问题,而循环或strtok对编译器来说是最容易解析的。对于人类(熟悉boostspirit(来说,基于BoostSpirit的解析器将是最容易阅读的。
C++用于基于循环的解决方案
#include <boost/container/flat_map.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/range/iterator_range_io.hpp>
#include <iostream>
// g++ -std=c++1z ~/aaa.cc
int main()
{
using range_t = boost::iterator_range<std::string::const_iterator>;
using map_t = boost::container::flat_map<range_t, range_t>;
char const sep = ':';
char const dlm = 'n';
// this part can be reused for parsing multiple records
map_t result;
result.reserve(1024);
std::string const input {"hello:worldn bye: world"};
// this part is per-line/per-record
result.clear();
for (auto _beg = begin(input), _end = end(input), it = _beg; it != _end;)
{
auto sep_it = std::find(it, _end, sep);
if (sep_it != _end)
{
auto dlm_it = std::find(sep_it + 1, _end, dlm);
result.emplace(range_t {it, sep_it}, range_t {sep_it + 1, dlm_it});
it = dlm_it + (dlm_it != _end);
}
else throw std::runtime_error("cannot parse");
}
for (auto& x: result)
std::cout << x.first << " => " << x.second << 'n';
return 0;
}
格式足够简单,"手动"解析IMO是最好的选择,总体而言仍然可读。
这也应该是相当有效的(key
和value
字符串总是相同的——尽管是clear
ed,所以主循环内部的重新分配应该在几次迭代后停止(;ret
也应该符合NRVO、OTOH的条件,以防出现问题,您可以随时更改为输出参数。
当然,std::map
可能不是西方最快的枪,但这是问题文本中的一个要求。
std::map<std::string, std::string> parseKV(const std::string &sz) {
std::map<std::string, std::string> ret;
std::string key;
std::string value;
const char *s=sz.c_str();
while(*s) {
// parse the key
while(*s && *s!=':' && s[1]!=' ') {
key.push_back(*s);
++s;
}
// if we quit due to the end of the string exit now
if(!*s) break;
// skip the ": "
s+=2;
// parse the value
while(*s && *s!='n') {
value.push_back(*s);
++s;
}
ret[key]=value;
key.clear(); value.clear();
// skip the newline
++s;
}
return ret;
}
如果担心性能,您可能应该重新考虑最终结果是否需要映射。这可能会导致内存中出现大量的字符缓冲区。理想情况下,只跟踪char*和每个子字符串的长度会更快/更小。
这里有一个解决方案,使用strtok
作为拆分手段。请注意,strtok
会更改您的字符串,它会将"\0"放在拆分字符处。
#include <iostream>
#include <string>
#include <map>
#include <string.h>
using namespace std;
int main (int argc, char *argv[])
{
char s1[] = "CA: ABCDnCB: ABFGnCC: AFBVnCD: 4567";
map<string, string> mymap;
char *token;
token = strtok(s1, "n");
while (token != NULL) {
string s(token);
size_t pos = s.find(":");
mymap[s.substr(0, pos)] = s.substr(pos + 1, string::npos);
token = strtok(NULL, "n");
}
for (auto keyval : mymap)
cout << keyval.first << "/" << keyval.second << endl;
return 0;
}
我怀疑您是否应该担心读取此字符串并将其转换为std::map
的优化问题。如果您真的想优化这个固定内容映射,请将其更改为std::vector<std::pair<>>
并对其进行一次排序。
也就是说,创建具有标准C++功能的std::map
最优雅的方法如下:
std::map<std::string, std::string> deserializeKeyValue(const std::string &sz) {
constexpr auto ELEMENT_SEPARATOR = ": "s;
constexpr auto LINE_SEPARATOR = "n"s;
std::map<std::string, std::string> result;
std::size_t begin{0};
std::size_t end{0};
while (begin < sz.size()) {
// Search key
end = sz.find(ELEMENT_SEPARATOR, begin);
assert(end != std::string::npos); // Replace by error handling
auto key = sz.substr(begin, /*size=*/ end - begin);
begin = end + ELEMENT_SEPARATOR.size();
// Seach value
end = sz.find(LINE_SEPARATOR, begin);
auto value = sz.substr(begin, end == std::string::npos ? std::string::npos : /*size=*/ end - begin);
begin = (end == std::string::npos) ? sz.size() : end + LINE_SEPARATOR.size();
// Store key-value
[[maybe_unused]] auto emplaceResult = result.emplace(std::move(key), std::move(value));
assert(emplaceResult.second); // Replace by error handling
}
return result;
}
尽管每个c++程序员都理解这段代码,但它的性能可能并不理想。
使用boost的一个非常简单的解决方案如下,它也适用于部分令牌(例如,没有值或空对的密钥(。
#include <string>
#include <list>
#include <map>
#include <iostream>
#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace boost;
int main() {
string s = "CA: ABCDnCB: ABFGnCC: AFBVnCD: 4567";
list<string> tokenList;
split(tokenList,s,is_any_of("n"),token_compress_on);
map<string, string> kvMap;
BOOST_FOREACH(string token, tokenList) {
size_t sep_pos = token.find_first_of(": ");
string key = token.substr(0,sep_pos);
string value = (sep_pos == string::npos ? "" : token.substr(sep_pos+2,string::npos));
kvMap[key] = value;
cout << "[" << key << "] => [" << kvMap[key] << "]" << endl;
}
return 0;
}
void splitString(std::map<std::string, std::string> &mymap, const std::string &text, char sep)
{
int start = 0, end1 = 0, end2 = 0;
while ((end1 = text.find(sep, start)) != std::string::npos && (end2 = text.find(sep, end1+1)) != std::string::npos) {
std::string key = text.substr(start, end1 - start);
std::string val = text.substr(end1 + 1, end2 - end1 - 1);
mymap.insert(std::pair<std::string,std::string>(key, val));
start = end2 + 1;
}
}
例如:
std::string text = "key1;val1;key2;val2;key3;val3;";
std::map<std::string, std::string> mymap;
splitString(mymap, text, ';');
将产生大小为3的映射:{key1="val1",key2="val2",key3="val3"}
更多示例:
"key1;val1;key2;"=>{key1="val1"}(没有第二个val,所以第二个密钥不计算在内(
"key1;val1;key2;val2"=>{key1="val1"}(第二个val末尾没有delim,因此不计数(
"key1;val1;key2;;"=>{key1="val1",key2="}(key2包含空字符串(
查看了已接受的答案,并尝试扩展一点,这似乎在更常见的情况下有效。测试运行可以在这里找到。欢迎任何意见或修改。
#include <iostream>
#include <string>
#include <sstream>
#include <map>
#include <algorithm>
#include <vector>
size_t find(const std::string& line, std::vector<std::string> vect, int pos=0) {
int eol1;
eol1 = 0;
for (std::vector<std::string>::iterator iter = vect.begin(); iter != vect.end(); ++iter) {
//std::cout << *iter << std::endl;
int eol2 = line.find(*iter, pos);
if (eol1 == 0 && eol2 > 0)
eol1 = eol2;
else if (eol2 > 0 && eol2 < eol1)
eol1 = eol2;
}
return eol1;
}
std::map<std::string, std::string> mappify(std::string const& s, char delim='=') {
std::map<std::string, std::string> m;
std::string::size_type key_pos = 0, i, j;
std::string::size_type key_end;
std::string::size_type val_pos;
std::string::size_type lim_pos;
std::string::size_type val_end;
while ((key_end = s.find(delim, key_pos)) != std::string::npos) {
if ((val_pos = s.find_first_not_of(delim, key_end + 1)) == std::string::npos)break;
while (key_end - 1 > 0 && (s[key_end - 1] <= 32 || s[key_end - 1] == ';'))
key_end--;
while (val_pos < s.size() && (s[val_pos] <= 32 || s[val_pos] == ';'))
val_pos++;
val_end = s.find('n', val_pos);
i = s.find('"', val_pos);
if (i != std::string::npos)
j = s.find('"', i + 1);
else
j = 0;
lim_pos = find(s.substr(0, i), { " ",";","t" }, val_pos + 1);
//std::cout << "s.substr(j):" << s.substr(j)<<std::endl;
if (lim_pos == 0 && j != std::string::npos)lim_pos = find(s.substr(j), { " ",";","t" }) + j;
if (lim_pos < val_pos)lim_pos = val_pos + 1;
if (j > 0)val_end = j + 1;
if (val_end > lim_pos)val_end = lim_pos;
m.emplace(s.substr(key_pos, key_end - key_pos), s.substr(val_pos, val_end - val_pos));
key_pos = val_end;
while ((key_pos < s.size() && s[key_pos] <= 32 || s[key_pos] == ';'))
++key_pos;
if (val_end == 0)break;
}
return m;
}
int main() {
std::string s ="
File="c:\dir\ocean\nCCS_test.txt"n
iEcho=10000; iHrShift=0 rho_Co2 = 1.15d0;n
Liner=01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";
auto m = mappify(s);
for (auto const& p : m)
std::cout << '{' << p.first << " :=> " << p.second << '}' << 'n';
return 0;
}
- 在C++中共享键值对的最佳方式
- 使用无序映射在STL中存储键值对
- c++同一个键的多个键/值对
- C++中的键值对:<val1, val2> 键<frequency>与值一样
- 在 std::unordered_map 中插入新的键/值对会导致"out of range"异常
- 读取数组中每个对象的所有键值对
- 这C++ unordered_map怎么有四个值?我以为这是一个键值对
- 使用 k 个键值对为零的存储桶初始化 c++14 unordered_map
- 从配置文件qsetting中删除键/值对
- 如何使用卡萨布兰卡在现有的web::json::value对象中附加新的键值对?
- 是否可以创建字符串和原子<int>键值对的unordered_map?
- 检索16k键值对的最快方法
- 将键值对文件读入 std::map
- 为什么用Spirit解析一个空行会在映射中产生一个空的键值对
- 从unordered_map C++中删除键/值对
- 如何将键值对引用从一个映射复制到同一类型的另一个映射
- 从键值对进行双向查找
- 如何从映射中修改键值对的值,而我不知道该键是否存在于映射中
- 提升精神 - 跳过键值对中不需要的行
- 使用C++将字符串拆分为键值对