C++ - 如何在字符串中提取有效字符串?
Posted
技术标签:
【中文标题】C++ - 如何在字符串中提取有效字符串?【英文标题】:C++ - How can I extract a valid string within a string? 【发布时间】:2009-06-12 12:35:16 【问题描述】:问题:我正在尝试从使用 C++ 的游戏名称中提取适用于古代防御 (DotA) 的有效游戏模式。
详情:
游戏名称最长为 31 个字符 共有三种游戏模式类别:主要、次要和杂项 只能选择 1 个主要游戏模式 某些主要游戏模式与某些次要游戏模式不兼容 某些次要游戏模式与其他次要游戏模式不兼容 其他游戏模式可以与所有其他游戏模式结合使用以下是各种游戏模式的列表,并附有一张图表,显示了每种模式兼容的辅助模式(X 表示不兼容):
// Only 1 primary allowed
static char *Primary[] =
// Compatible with > | dm | rv | mm | du | sh | aa | ai | as | id | em | np | sc | om | nt | nm | nb | ro | mo | sp |
"ap", // All Pick | | | | | | | | | | | | | | | | | | | |
"ar", // All Random | | X | | | | | | | | | | | | | | | | | |
"tr", // Team Random | X | X | | | | | | | | | | | | | | | | | |
"mr", // Mode Random | X | X | | | X | X | X | X | | | | | | | | | X | X | |
"lm", // League Mode | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | |
"rd", // Random Draft | X | X | X | | X | X | X | X | | | | | | | | | X | X | |
"vr", // Vote Random | X | X | X | | X | X | X | X | | | | | | | | | X | X | |
"el", // Extended League | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | |
"sd", // Single Draft | X | X | X | | X | X | X | X | | | | | | | | | X | X | |
"cm", // Captains Mode | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X |
"cd" // Captains Draft | X | X | X | | X | X | X | X | | | | | | | | | X | X | |
;
static char *Secondary[] =
// Compatible with > | dm | rv | mm | du | sh | aa | ai | as | id | em | np | sc | om | nt | nm | nb | ro | mo | sp |
"dm", // Death Match | | X | X | | X | X | X | X | | | | | | | | | X | X | |
"rv", // Reverse Mode | X | | | | X | | | | | | | | | | | | | | |
"mm", // Mirror Match | X | | | | X | | | | | | | | | | | | | | |
"du", // Duplicate Mode | | | | | | | | | | | | | | | | | | | |
"sh", // Same Hero | X | X | X | | | | | | | | | | | | | | | | |
"aa", // All Agility | X | | | | | | X | X | | | | | | | | | | | |
"ai", // All Intelligence | X | | | | | X | | X | | | | | | | | | | | |
"as", // All Strength | X | | | | | X | X | | | | | | | | | | | | |
"id", // Item Drop | | | | | | | | | | | | | | | | | | | |
"em", // Easy Mode | | | | | | | | | | | | | | | | | | | |
"np", // No Powerups | | | | | | | | | | | | | | | | | | | |
"sc", // Super Creeps | | | | | | | | | | | | | | | | | | | |
"om", // Only Mid | | | | | | | | | | | | | | | | | | | |
"nt", // No Top | | | | | | | | | | | | | | | | | | | |
"nm", // No Middle | | | | | | | | | | | | | | | | | | | |
"nb", // No Bottom | | | | | | | | | | | | | | | | | | | |
"ro", // Range Only | X | | | | | | | | | | | | | | | | | X | |
"mo", // Melee Only | X | | | | | | | | | | | | | | | | X | | |
"sp" // Shuffle Players | | | | | | | | | | | | | | | | | | | |
;
// These options are always available
static char *Misc[] =
"ns", // No Swap
"nr", // No Repick
"ts", // Terrain Snow
"pm", // Pooling Mode
"oi", // Observer Info
"mi", // Mini Heroes
"fr", // Fast Respawn
"so" // Switch On
;
示例:以下是一些示例输入,以及所需的输出:
“DotA v6.60 -RDSOSP USA/CA LC!” ->“rdsosp”
“DOTA AREMDM USA LC”->“aremdm”
“DotA v6.60 -ApEmDuSpId USA BL”->“apemduspid”
注意事项:该解决方案不一定必须提供实际代码、伪代码,甚至只是说明您将如何处理它是可以接受和首选的。此外,该解决方案需要足够灵活,以便我可以相当轻松地添加另一种游戏模式。还可以假设在游戏名称中,游戏模式将始终以主要游戏模式开始。
结果:
#include <cstdarg>
#include <algorithm>
#include <iostream>
#include <string>
#include <sstream>
#include <map>
#include <vector>
std::map<std::string, std::vector<std::string> > ModeCompatibilityMap;
static const unsigned int PrimaryModesCount = 11;
static char *PrimaryModes[] =
"ap", "ar", "tr", "mr", "lm", "rd", "vr", "el", "sd", "cm", "cd"
;
static const unsigned int SecondaryModesCounts = 19;
static char *SecondaryModes[] =
"dm", "rv", "mm", "du", "sh", "aa", "ai", "as", "id", "em", "np",
"sc", "om", "nt", "nm", "nb", "ro", "mo", "sp"
;
static const unsigned int MiscModesCount = 8;
static char *MiscModes[] =
"ns", "nr", "ts", "pm", "oi", "mi", "fr", "so"
;
std::vector<std::string> Vectorize( int count, ... )
std::vector<std::string> result;
va_list vl;
va_start( vl, count );
for ( int i = 0; i < count; ++i )
char *buffer = va_arg( vl, char * );
result.push_back( buffer );
va_end( vl );
return result;
void InitializeModeCompatibilityMap()
// Primary
ModeCompatibilityMap["ar"] = Vectorize( 1, "rv" );
ModeCompatibilityMap["tr"] = Vectorize( 2, "dm", "rv" );
ModeCompatibilityMap["mr"] = Vectorize( 8, "dm", "rv", "sh", "aa", "ai", "as", "ro", "mo" );
ModeCompatibilityMap["lm"] = Vectorize( 18, "dm", "rv", "mm", "du", "sh", "aa", "ai", "as", "id", "em", "np", "sc", "om", "nt", "nm", "nb", "ro", "mo" );
ModeCompatibilityMap["rd"] = Vectorize( 9, "dm", "rv", "mm", "sh", "aa", "ai", "as", "ro", "mo" );
ModeCompatibilityMap["vr"] = Vectorize( 9, "dm", "rv", "mm", "sh", "aa", "ai", "as", "ro", "mo" );
ModeCompatibilityMap["el"] = Vectorize( 18, "dm", "rv", "mm", "du", "sh", "aa", "ai", "as", "id", "em", "np", "sc", "om", "nt", "nm", "nb", "ro", "mo" );
ModeCompatibilityMap["sd"] = Vectorize( 9, "dm", "rv", "mm", "sh", "aa", "ai", "as", "ro", "mo" );
ModeCompatibilityMap["cm"] = Vectorize( 19, "dm", "rv", "mm", "du", "sh", "aa", "ai", "as", "id", "em", "np", "sc", "om", "nt", "nm", "nb", "ro", "mo", "sp" );
ModeCompatibilityMap["cd"] = Vectorize( 9, "dm", "rv", "mm", "sh", "aa", "ai", "as", "ro", "mo" );
// Secondary
ModeCompatibilityMap["dm"] = Vectorize( 8, "rv", "mm", "sh", "aa", "ai", "as", "ro", "mo" );
ModeCompatibilityMap["rv"] = Vectorize( 2, "dm", "sh" );
ModeCompatibilityMap["mm"] = Vectorize( 2, "dm", "sh" );
ModeCompatibilityMap["sh"] = Vectorize( 3, "dm", "rv", "mm" );
ModeCompatibilityMap["aa"] = Vectorize( 3, "dm", "ai", "as" );
ModeCompatibilityMap["ai"] = Vectorize( 3, "dm", "aa", "as" );
ModeCompatibilityMap["as"] = Vectorize( 3, "dm", "aa", "ai" );
ModeCompatibilityMap["ro"] = Vectorize( 2, "dm", "mo" );
ModeCompatibilityMap["mo"] = Vectorize( 2, "dm", "ro" );
std::vector<std::string> Tokenize( const std::string &string )
std::vector<std::string> tokens;
std::string token;
std::stringstream ss( string );
while ( ss >> token )
tokens.push_back( token );
return tokens;
void SanitizeString( std::string &in )
std::transform( in.begin(), in.end(), in.begin(), tolower );
for ( size_t i = 0; i < in.size(); ++i )
if ( in[i] < 'a' || in[i] > 'z' )
in.erase( i--, 1 );
std::vector<std::string> SplitString( const std::string &in, int count )
std::vector<std::string> result;
if ( in.length() % count != 0 )
return result;
for ( std::string::const_iterator i = in.begin(); i != in.end(); i += count )
result.push_back( std::string( i, i + count ) );
return result;
bool IsPrimaryGameMode( const std::string &in )
for ( int i = 0; i < PrimaryModesCount; ++i )
if ( strcmp( PrimaryModes[i], in.c_str() ) == 0 )
return true;
return false;
bool IsSecondaryGameMode( const std::string &in )
for ( int i = 0; i < SecondaryModesCounts; ++i )
if ( strcmp( SecondaryModes[i], in.c_str() ) == 0 )
return true;
return false;
bool IsMiscGameMode( const std::string &in )
for ( int i = 0; i < MiscModesCount; ++i )
if ( strcmp( MiscModes[i], in.c_str() ) == 0 )
return true;
return false;
bool IsValidGameMode( std::string in, std::string &out )
// 1. Strip all non-letters from the string and convert it to lower-case
SanitizeString( in );
// 2. Confirm that it is a multiple of 2
if ( in.length() == 0 || in.length() % 2 != 0 )
return false;
// 3. Split the string further into strings of 2 characters
std::vector<std::string> modes = SplitString( in, 2 );
// 4. Verify that each game mode is a valid game mode and is compatible with the others
bool primaryModeSet = false;
for ( size_t i = 0; i < modes.size(); ++i )
if ( IsPrimaryGameMode( modes[i] ) || IsSecondaryGameMode( modes[i] ) )
if ( IsPrimaryGameMode( modes[i] ) )
if ( primaryModeSet )
return false;
primaryModeSet = true;
if ( ModeCompatibilityMap.count( modes[i] ) > 0 )
std::vector<std::string> badModes = ModeCompatibilityMap[modes[i]];
for ( size_t j = 0; j < badModes.size(); ++j )
for ( size_t k = 0; k < modes.size(); ++k )
if ( badModes[j] == modes[k] )
return false;
else if ( !IsMiscGameMode( modes[i] ) )
return false;
// 5. Assign the output variable with the game mode and return true
out = in;
return true;
std::string ExtractGameMode( const std::string &gameName )
std::vector<std::string> tokens = Tokenize( gameName );
std::string gameMode;
for ( size_t i = 0; i < tokens.size(); ++i )
if ( IsValidGameMode( tokens[i], gameMode ) )
return gameMode;
return "";
int main( int argc, char *argv[] )
InitializeModeCompatibilityMap();
std::string gameName = "DotA v6.60 -RDEM USA/CA LC";
std::string gameMode = ExtractGameMode( gameName );
std::cout << "Name: " << gameName << std::endl;
std::cout << "Mode: " << gameMode << std::endl;
return 0;
输出:
名称:DotA v6.60 -RDEM USA/CA LC
模式:rdem
如果有人想查看此代码并让我知道他们将进行哪些更改,我们将不胜感激。
谢谢。
【问题讨论】:
我没完全理解,你需要: 1.从游戏名称中提取游戏模式。 2. 验证它们是否相互兼容? 【参考方案1】:创建用于复制您放入 cmets 的表的 bool 数组。除了不是“X”或空白,而是“真”或“假”(所以“真”表示模式组合有效,“假”表示无效)。
使用此表查找组合是否有效:
bool IsSecondaryValidWithPrimary(unsigned int primaryIndex, unsigned int secondaryIndex)
static bool secondaryValidWithPrimary[numPrimaryModes][numSecondaryModes] = ...
if (primaryIndex < numPrimaryModes && secondaryIndex < numSecondaryModes)
return secondaryValidWithPrimary[primaryIndex][secondaryIndex]
else
//... this should never happen, throw your favorite exception
这自然要求您将每 2 个字符串转换为要测试的正确数组索引。循环遍历所有可能的组合并检查其是否有效。我怀疑您是否真的关心此设置中的性能,所以这应该可以很好地工作。
对其他有效性检查(次要与另一个次要)或您具有兼容性规则的任何其他模式组合执行相同操作。
【讨论】:
【参考方案2】:如果没有更多规则,从主机的游戏名称中提取游戏类型将会很困难。如果您真的想避免为最终用户提供更多规则,您可以尝试以下方法...
ToLower() 整个游戏名称字符串。 使用空格分隔符分隔游戏名称。 分析每个单词,执行以下操作。如果有任何失败,请转到下一个单词。取 [0] 并确定它的 ascii 值是否为 97-122(确保它是一个字母)。如果它不在这些值之内,则转到下一个字符,依此类推,直到它出现(显然不超过数组边界)。这会删除任何用户格式,例如连字符 -apem。 strcmp() 每种主要游戏类型的下 2 个字符,直到匹配。否则失败并继续下一个单词。 对于剩余的字符,使用每个次要或其他游戏类型 strcmp 下一对字符。如果有任何不匹配的单词失败,或者只剩下 1 个字符,则失败到下一个单词
这应该提取游戏类型,或者你可以责怪用户使用了错误的游戏名称。
现在是更难的部分,验证游戏类型是否相互兼容。我建议您创建一个结构,该结构包含代表每种辅助游戏类型的布尔数据结构。可以使用枚举访问的 std::map 或单个布尔数组。
现在您需要一个数据对象来表示每个主要游戏类型以及作为每个次要游戏类型。
然后只需为每种游戏类型(主要和次要)制作一个数组。参考代码示例:
map<const char*, bool> mapSecondaryCompatibility;
struct tGameType
char szName[3];
mapSecondaryCompatibility m_compat;
如您所见,您的主要游戏类型和次要游戏类型在技术上没有区别,它们都有相同的限制......可能与其他次要游戏类型不兼容。
有了这个,我相信你可以弄清楚其余的。希望对你有帮助,我得回去工作了:)
哦,我是 DotA 的忠实粉丝...继续努力!
【讨论】:
这实际上与我最终所做的非常接近,所以我会接受这个作为答案并用我的代码编辑我的帖子。【参考方案3】:我可能会尝试将每种模式放入 std::set 中,并带有与给定节点兼容的模式的白名单。当您想要解析模式字符串时,您制作了主要白名单的副本。然后你从字符串中走下来。如果下一个节点不在白名单中,则您的模式字符串无效。如果下一个模式在白名单中,那么您将下一个节点的白名单与您的工作白名单相交。继续直到到达字符串末尾或白名单为空。如果白名单为空且你不在字符串末尾,则无效,否则为好。
其他模式可能不属于白名单(因为它们在每个白名单中)。
您也可以尝试使用黑名单,并在每一步创建联合,然后在模式在列表中时退出。 p>
【讨论】:
【参考方案4】:我倾向于将游戏模式类型转换为枚举。我什至可以将模式包装在一个可以存储当前游戏模式状态的类中,并为游戏的其他部分提供友好的访问器以快速查询当前模式。
在内部我会创建一个 std::map> 来存储兼容模式的列表。输入命令行后,我会将两个字符串转换为枚举值。然后我会在兼容模式映射中查找它是否是允许的模式。
如何填写地图取决于您 - 我认为您可以有一个类来执行此操作 - 兼容性加载器,或者如果您希望最终用户能够修改可用的配置文件,您可以从配置文件中驱动它模式。
使用枚举会更好,这样您就可以在编译时进行大量检查,而不是运行时检查。当您从输入字符串转换为枚举时,您会执行一次运行时检查,如果无效则向用户返回失败消息。然后在编译时检查所有其他代码。
【讨论】:
以上是关于C++ - 如何在字符串中提取有效字符串?的主要内容,如果未能解决你的问题,请参考以下文章