namespace Dpz.Core.Simple.Test;
/// <summary>
/// 通配符匹配调试测试
/// 用于调试和验证特定的匹配情况
/// </summary>
public class WildcardDebugTest
{
[Test]
public void DebugSpecificCases()
{
// 测试具体的问题案例
TestCase("test", "t??t", false, "4个字符的字符串匹配4个字符的模式,但最后一个字符不匹配");
TestCase("tast", "t??t", true, "4个字符匹配,第2、3位任意,第4位匹配");
TestCase("", "*", true, "空字符串应该匹配星号");
TestCase("", "", true, "空字符串应该匹配空字符串");
TestCase("", "a", false, "空字符串不应该匹配非空模式");
TestCase("a", "", false, "非空字符串不应该匹配空模式");
// 更多边界测试
TestCase("test", "????", true, "4个字符匹配4个问号");
TestCase("test", "???", false, "4个字符不匹配3个问号");
TestCase("test", "?????", false, "4个字符不匹配5个问号");
// 混合测试
TestCase("test.txt", "*.txt", true, "文件扩展名匹配");
TestCase("test.doc", "*.txt", false, "文件扩展名不匹配");
TestCase("a", "?", true, "单字符匹配单问号");
TestCase("ab", "?", false, "两字符不匹配单问号");
}
private static void TestCase(string input, string pattern, bool expected, string description)
{
var result = WildcardMatch(input.AsSpan(), pattern.AsSpan());
Console.WriteLine(
$"测试: '{input}' vs '{pattern}' => {result} (预期: {expected}) - {description}"
);
if (result != expected)
{
Console.WriteLine(
$"❌ 失败: 输入='{input}', 模式='{pattern}', 结果={result}, 预期={expected}"
);
// 让我们详细跟踪这个匹配过程
DetailedTrace(input, pattern);
}
else
{
Console.WriteLine($"✅ 成功");
}
Console.WriteLine();
}
private static void DetailedTrace(string input, string pattern)
{
Console.WriteLine("=== 详细匹配过程 ===");
var inputSpan = input.AsSpan();
var patternSpan = pattern.AsSpan();
Console.WriteLine($"输入长度: {inputSpan.Length}, 模式长度: {patternSpan.Length}");
// 处理空字符串的特殊情况
if (patternSpan.Length == 0)
{
Console.WriteLine("模式为空,返回输入是否也为空");
return;
}
if (inputSpan.Length == 0)
{
Console.WriteLine("输入为空,检查模式是否全为 '*'");
for (var i = 0; i < patternSpan.Length; i++)
{
Console.WriteLine($"模式[{i}] = '{patternSpan[i]}'");
if (patternSpan[i] != '*')
{
Console.WriteLine("发现非 '*' 字符,返回 false");
return;
}
}
Console.WriteLine("模式全为 '*',返回 true");
return;
}
var inputIndex = 0;
var patternIndex = 0;
var starIndex = -1;
var match = 0;
var step = 0;
Console.WriteLine("开始主匹配循环:");
while (inputIndex < inputSpan.Length)
{
step++;
Console.WriteLine(
$"步骤 {step}: inputIndex={inputIndex}, patternIndex={patternIndex}, starIndex={starIndex}, match={match}"
);
if (inputIndex < inputSpan.Length)
{
Console.WriteLine($" 当前输入字符: '{inputSpan[inputIndex]}'");
}
if (patternIndex < patternSpan.Length)
{
Console.WriteLine($" 当前模式字符: '{patternSpan[patternIndex]}'");
}
// 如果模式字符是 '?' 或者字符匹配
if (
patternIndex < patternSpan.Length
&& (
patternSpan[patternIndex] == '?'
|| char.ToLowerInvariant(inputSpan[inputIndex])
== char.ToLowerInvariant(patternSpan[patternIndex])
)
)
{
Console.WriteLine(" 匹配成功,两个索引都前进");
inputIndex++;
patternIndex++;
}
// 如果模式字符是 '*'
else if (patternIndex < patternSpan.Length && patternSpan[patternIndex] == '*')
{
Console.WriteLine(" 遇到 '*',记录位置并跳过");
starIndex = patternIndex;
match = inputIndex;
patternIndex++;
}
// 如果之前遇到过 '*',回溯
else if (starIndex != -1)
{
Console.WriteLine(" 不匹配但有 '*',回溯");
patternIndex = starIndex + 1;
match++;
inputIndex = match;
}
// 不匹配
else
{
Console.WriteLine(" 不匹配且无法回溯,返回 false");
return;
}
}
Console.WriteLine("主循环结束,处理模式末尾的 '*'");
while (patternIndex < patternSpan.Length && patternSpan[patternIndex] == '*')
{
Console.WriteLine($"跳过模式末尾的 '*' at {patternIndex}");
patternIndex++;
}
Console.WriteLine(
$"最终结果: patternIndex={patternIndex}, pattern.Length={patternSpan.Length}"
);
Console.WriteLine($"返回: {patternIndex == patternSpan.Length}");
}
private static bool WildcardMatch(ReadOnlySpan<char> input, ReadOnlySpan<char> pattern)
{
// 处理空字符串的特殊情况
if (pattern.Length == 0)
{
return input.Length == 0;
}
if (input.Length == 0)
{
// 输入为空,只有模式全部是 '*' 才匹配
for (var i = 0; i < pattern.Length; i++)
{
if (pattern[i] != '*')
{
return false;
}
}
return true;
}
var inputIndex = 0;
var patternIndex = 0;
var starIndex = -1;
var match = 0;
while (inputIndex < input.Length)
{
// 如果模式字符是 '?' 或者字符匹配
if (
patternIndex < pattern.Length
&& (
pattern[patternIndex] == '?'
|| char.ToLowerInvariant(input[inputIndex])
== char.ToLowerInvariant(pattern[patternIndex])
)
)
{
inputIndex++;
patternIndex++;
}
// 如果模式字符是 '*'
else if (patternIndex < pattern.Length && pattern[patternIndex] == '*')
{
starIndex = patternIndex;
match = inputIndex;
patternIndex++;
}
// 如果之前遇到过 '*',回溯
else if (starIndex != -1)
{
patternIndex = starIndex + 1;
match++;
inputIndex = match;
}
// 不匹配
else
{
return false;
}
}
// 跳过模式末尾的 '*'
while (patternIndex < pattern.Length && pattern[patternIndex] == '*')
{
patternIndex++;
}
return patternIndex == pattern.Length;
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一个用于测试和调试通配符匹配算法的单元测试类。让我详细解释一下这个代码的结构和功能:
主要功能
这个类实现了一个通配符匹配算法,支持两种通配符:
*:匹配任意数量的字符(包括0个)?:匹配单个字符
代码结构分析
1. 主测试方法 DebugSpecificCases()
[Test]
public void DebugSpecificCases()
这是一个NUnit测试方法,包含了多个测试用例来验证通配符匹配的各种场景:
边界情况测试:
- 空字符串与各种模式的匹配
- 长度不匹配的情况
具体匹配测试:
"test"vs"t??t"→ false(最后一个字符不匹配)"tast"vs"t??t"→ true(中间两位任意,首尾匹配)- 文件扩展名匹配等实际应用场景
2. 测试辅助方法 TestCase()
private static void TestCase(string input, string pattern, bool expected, string description)
这个方法负责:
- 执行单个测试用例
- 比较实际结果与期望结果
- 输出测试结果和描述
- 如果测试失败,调用详细跟踪方法
3. 详细跟踪方法 DetailedTrace()
private static void DetailedTrace(string input, string pattern)
当测试失败时,这个方法会:
- 逐步显示匹配算法的执行过程
- 显示每一步的索引位置和当前字符
- 帮助开发者理解算法的执行逻辑和失败原因
4. 核心匹配算法 WildcardMatch()
private static bool WildcardMatch(ReadOnlySpan<char> input, ReadOnlySpan<char> pattern)
这是核心的通配符匹配算法,使用了回溯策略:
算法逻辑:
- 边界处理:处理空字符串的特殊情况
- 主循环匹配:
- 如果字符直接匹配或遇到
?:两个索引都前进 - 如果遇到
*:记录星号位置,准备回溯 - 如果不匹配但之前有
*:回溯到星号后继续尝试 - 如果不匹配且无法回溯:返回false
- 如果字符直接匹配或遇到
- 后处理:跳过模式末尾的
* - 最终判断:检查是否完全匹配
关键变量:
inputIndex:当前输入字符串位置patternIndex:当前模式字符串位置starIndex:最近遇到的*的位置match:*匹配开始的输入位置
算法特点
- 高效性:使用
ReadOnlySpan<char>避免字符串复制 - 大小写不敏感:使用
char.ToLowerInvariant()进行比较 - 回溯机制:遇到
*时能够智能回溯尝试不同的匹配长度 - 调试友好:提供详细的执行跟踪信息
这个代码非常适合用于文件名匹配、路径过滤等需要通配符支持的场景。
AI 正在分析代码…
评论加载中...