您的位置:首页 > 其它

函数调用过程中所有参数的提取

2016-04-29 20:33 267 查看

1. 引言

曾经遇到一个面试题:如何将函数调用看作一个字符串,提取出所有的参数并构建成一个树。

比如输入字符串 func1(123, 456), 输出是
func1(123, 456)
|
---------123
|
---------456

输入字符串 func1(123, 456, func2(78, 99)) 输出则是
func1(123, 456, func2(78, 99))
|
-----------------------------123
|
----------------------------456
|
----------------------------func2(78, 99)
|
--------------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 网站还在不断开发中…
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: