1. 引言
曾经遇到一个面试题:如何将函数调用看作一个字符串,提取出所有的参数并构建成一个树。
比如输入字符串 func1(123, 456), 输出是
func1(123, 456) | ---------123 | ---------456
输入字符串 func1(123, 456, func2(78, 99)) 输出则是
func1(123, 456, func2(, )) | -----------------------------123 | ----------------------------456 | ----------------------------func2(78, ) | --------------78 | --------------99
面试过程中,假设了函数调用肯定是合法的,即函数左右括号肯定匹配,函数参数都是double 或者 int 等数值类型。
由于存在这样的假设前提,题目已经变得相对简单多,但是实际项目中函数的参数是多种多样的,参数可以是String 类型,并且实参包含逗号或者左右括号都是可能的。
非常巧的是最近在项目中就遇到了类似的算法,需要将函数调用过程中所有满足一定格式的参数提取出来,当然参数有字符串并以单引号为开头结尾。但是单引号不再包含单引号。
今天就给出一个针对函数所有参数提取的C#实现。
2. 代码实现
首先给出了一个简单的Tree类的定义,熟悉C++的同学肯定不陌生,这里用C#实现:
public class TreeNode { public string Text { get; set; } public List<TreeNode> ChildNodes { get; set; } }
定义函数:判断参数是不是嵌套的函数调用,例如函数参数只可能是以下类型,
数值类型: 123 或者 56.87
字符类型: ‘fake parameter’ 或者 ‘fake paramter with left bracket ( or right bracket ).’
嵌套函数: funcx(34,78, ‘dd’) 或者 funcy()
public static bool IsFunctionCall(string str) { if (string.IsNullOrEmpty(str)) { return false; } if (str.Trim().First( ) == '\'' && str.Trim().Last() == '\'') { return false; } int leftBracketIndex = str.IndexOf("(", StringComparison.OrdinalIgnoreCase); int rightBracketIndex = str.LastIndexOf(")", StringComparison.OrdinalIgnoreCase); if (leftBracketIndex < 0 && rightBracketIndex < 0) { return false; } return true; }
定义函数:获取函数所有未分割的参数,例如
“func1(123, 456)”=> “123, 456”
“func1(123, 456, func2(78, 99))”=> “123, 456, func2(78, 99)”
public static string GetParameterString(string function) { int leftBracketIndex = function.IndexOf("(", StringComparison.OrdinalIgnoreCase); int rightBracketIndex = function.LastIndexOf(")", StringComparison.OrdinalIgnoreCase); string value = function.Substring(leftBracketIndex + 1, rightBracketIndex - leftBracketIndex - 1).Trim(); return (string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value)) ? null : value; }
调用函数:
public static TreeNode Parse(string input) { if (string.IsNullOrEmpty(input)) return null; if (IsFunctionCall(input)) { return ParseFunction(input); } else { return new TreeNode() {Text = input}; } }
最后是提取Function 所有参数的函数实现:
/// <summary> /// 通过递归提取所有参数 /// </summary> /// <param name="input">输入参数肯定是一个函数调用比如: func1(123, 456, func2(78, 'abc'))</param> /// <returns>返回一个树</returns> public static TreeNode ParseFunction(string input) { if (string.IsNullOrEmpty(input)) return null; //构建顶层树节点并且得到所有的未分割的参数 TreeNode rootNode = new TreeNode() {Text = input}; var paramStr = GetParameterString(input); if (paramStr == null) { return rootNode; } //存放所有参数的List List<string> result = new List<string>(); string tmp = ""; //可以用Stack 来实现括号的匹配,这里简单的记录左括号的个数 int bracketCount = 0; //参数是否是字符串 bool hasSingleQuote = false; foreach (char c in paramStr) { if (c == '\'') { tmp += c; //字符串结尾 if (hasSingleQuote) { hasSingleQuote = false; continue; } //字符串开始 hasSingleQuote = true; continue; } //只有参数不是字符串的时候 左括号才加一 if (c == '(' && !hasSingleQuote) { bracketCount++; tmp += c; } //只有参数不是字符串的时候 右括号才减一 else if (c == ')' && !hasSingleQuote) { bracketCount--; tmp += c; } //参数参数满足的条件 else if (c == ',' && bracketCount == 0 && !hasSingleQuote) { result.Add(tmp.Trim()); tmp = ""; } else { tmp += c; } } //最后一个参数 if (tmp != "") { result.Add(tmp.Trim()); } //递归的提取 rootNode.ChildNodes = result.Select(parm => Parse(parm)).ToList(); return rootNode; }
测试用例:
static void Main(string[] args) { TreeNode testTree = Parse("func1(123, 456, func2(78, 'abc'))"); testTree = Parse(" func1(func4(), 123, '(abc', func3(')dj'), 456, func2(78, 'dev', func5('xx)(x', 99)))"); }
3. 结论
这道题目其实考察的是对递归和Stack的理解。首先如果直接用逗号分隔只会把问题弄得复杂,只要想到左右括号匹配等条件再利用Stack的思想,问题也就变得简单多了。
如果给出的算法有错误或者有更好的算法 请各位支出。
欢迎访问我的个人网站 51zhang.net 网站还在不断开发中…