2

iOS课程的作业,按老师的说法,“为了更好地理解Foundation”,所以没有用NSExpression,而是自己实现一个栈来完成表达式求值。

思路

说起表达式求值,首先想到严奶奶版的数据结构教材就有现成的算法,可以直接拿过来用嘛。

OperandType EvaluateExpression(){
//算术表达式求值的算符优先算法,设OPTR和OPND分别为运算符合运算数栈
//OP为运算符集合
InitStack(OPTR); Push(OPTR,'#')
InitStack(OPND); c = getchar();
while(c != '#' || GetTop(OPTR) != '#'){
    if(!In(c, OP)){
        Push(OPND, c); 
        c = getchar();
    }//不是运算符则进栈
    else
        switch(Precede(GetTop(OPTR), c)){
            case '<': //栈顶元素优先级低,栈外运算符入栈
                Push(OPTR, c);
                c = getchar();
                break;
            case '=': //脱括号并接收下一字符
                Pop(OPTR,x);
                c = getchar();
                break;
            case '>': //栈顶元素优先级高,出栈并将运算结果入栈
                Pop(OPTR, theta);
                Pop(OPND, b); Pop(OPND, a);//出栈顺序:先b后a
                Push(OPND, Operate(a, theta, b);//计算式:a theta b
                break;
        }//switch
 }//while
   return GetTop(OPND);
}//EvaluateExpression

分析这个算法,可以知道整理出首先要用OC实现一个栈,然后实现以下几个函数即可。

//判断输入的字符是运算符还是操作数
-(BOOL)isOperator:(NSString *)ch;

//判断输入的字符串中是否含有非法字符(除了数字和运算符之外)
-(BOOL)isNumberic:(NSString *) ch;

//比较栈外和栈内元素优先级
-(NSString *)comparePriority:(NSString *)inOptr outOptr:(NSString *)outOptr;

//计算opnd1 optr opnd2
-(double)calculate:(double)opnd1 opnd2:(double)opnd2 optr:(NSString *)optr;
    
//实现上述算法
-(NSString *)ExpressionCalculate:(NSString *)inputString;

栈的实现

  1. 数据存储
    考虑到数据的动态变化,使用NSMutableArray来存放

  2. 初始化
    直接用initWithCapacity:数组是空的,因为它只分配内存,得往里面塞点东西。

  3. pop函数
    最开始使用的下面的函数定义:

        
        -(BOOL)pop:(NSString *)element stack:(Stack *)stack

    然后发现element没办法传回去,比如,我调用:

    NSString *a;
    pop:a stack:self.opnd
    //NSLog: a = null

所以修改了下,增加了一个property放pop出来的元素,并且赋上pop的返回值来调用

    -(NSString *)pop:(Stack *)stack
    //调用
    NSString *a;
    a = [self.opnd pop:self.opnd];

这就好啦。

函数comparePriority

  1. 使用字典NSDictionary,按栈内和栈外分两个字典存放优先级;

  2. 优先级采用百度文库上定义的一个表;

  3. 两个优先级字典采用类方法初始化;

  4. 优先级比较的时候,用到NSString与NSInteger的转化

        //NSInteger转化NSString类型:
        [NSString stringWithFormat: @"%d", NSInteger];
    
        //NSString转化 NSInteger类型:
        NSInteger myInteger = [myString integerValue];
    

函数calculate

  1. switch不能直接用NSString类型作为expression,在StackOverflow上搜索了一下解决办法,出于已经写的代码的考虑,采用第三个回答的方法试试。

关于TextField控件

  1. 用来输入字符;

  2. 使用view作为代理,完成在点击键盘外部时隐藏键盘

函数ExpressionCalculate

  1. 处理输入的字符串:
    (1)对其进行分割,按数字和运算符分别存储在不同的数组中。对于需要用到函数是componentsSeparatedByCharactersInSet和characterSetWithCharactersInString这两个神奇的组合。比如:

    self.arrayToCalculate = [inputString componentsSeparatedByCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:@"+-*/%"]];

只是有个问题,这样分割出来的数组中,会在原表达式出现括号的地方出现空白符,不是很理解。[?]

对于运算符,是对字符串进行逐个扫描,提取出+-*/等运算符号;如果采用提取数字的同样的方法,会出现(-这样,括号和运算符连在一起的情况,因为它们都在我给的运算符集合中,连在一起的时候自然不会分割。

关于[?]处的解答,在网上查了一下,是componentsSeparatedByCharactersInSet的问题。苹果的文档是这么描述的:

Adjacent occurrences of the separator characters produce empty strings in the result. Similarly, if the string begins or ends with separator characters, the first or last substring, respectively, is empty.

意思大概是,处理分割string的时候,如果连续出现了用于分割的字符集合set中的两个字符,或者刚好set中的字符处于string的开始或结尾的地方,就会出现空白。

StackOverflow上有人给了一个处理第二种情况的办法,即采用stringByTrimmingCharactersInSet函数,将开始和结尾处的set中的字符trim掉。
(2)为什么要事先分割输入的字符串呢?
我是将输入的字符串按字符串index一个一个取出来的,运算符还好,但如果是两位数三位数就惨了。所以事先将要进行运算的操作数取出来,每次在扫描到下一个字符是运算符时,将arrayToCalculate中的第一个元素取出入栈,然后在arrayToCalculate中删除第一个元素。

    //删除下标为0的(即第一个元素)
    [self.arrayToCalculate removeObjectAtIndex:0];     
  1. double与NSString的相互转换

       double num = [NSString doubleValue];
       NSString * str = [NSString stringWithFormat:@"%f", num];
  2. NSArray与NSMutableArray的相互转换

       //componentsSeparatedByCharactersInSet返回的是NSArray
       NSArray * tempArray = [inputString componentsSeparatedByCharactersInSet: 
       [NSCharacterSet characterSetWithCharactersInString:@"(+-*/%=)"]];
       self.arrayToCalculate = [NSMutableArray arrayWithArray:tempArray];
  3. char转换为NSString

       //方式一:使用stringWithFormat
       char c = 'a';
       NSString *s = [NSString stringWithFormat:@"%c", c];
    
       //方式二:使用-stringWithUTF8String:
       char *ch = ……;
       NSString *str = [NSString stringWithUTF8String:&ch];

    使用stringWithUTF8String:的话,str末尾会包含"\n",看我的问题就知道这是一个多么让人伤心的领悟。另外,也可以发现stringWithFormat:是个挺万金油的方法,好像可以把大家都变成NSString的样子。

  4. 使用for循环也可以让当前元素停下来。
    注意当栈顶元素的优先级高于栈外元素时,要将栈顶元素弹出,此时要注意保存当前的栈外元素。算法中用的while循环,很容易保存,不接受下一个元素就可以了;如果是for循环的话,每次循环一定会递增,所以如果要保存当前栈外元素的话,将i--就可以啦。

函数isNumberic

再次使用了NSCharacterSet这个类,用到了一个decimalDigitCharacterSet类方法,苹果文档里是这么说的:

Returns a character set containing the characters in the category of Decimal Numbers.

Informally, this set is the set of all characters used to represent the decimal values 0 through 9. These characters include, for example, the decimal digits of the Indic scripts and Arabic.

我的代码 将Digits打印出来是这样的:__NSCFCharacterSet: 0x7ffb4ad23b60,不太明白是什么玩意儿,心塞,回头再研究。

效果图

效果图


严肃的马甲
110 声望10 粉丝