Permalänk
Medlem

Btw, min första tanke var att skriva en lexer eller vad det heter och göra om uttrycket till ett AST. Men usch vad jobbigt det blev med prioriterings reglerna.

Här är min kod som jag gav upp med, den klarar i alla fall nivå 1.

<?php /** * The actual functions */ function F_ADD($a, $b) { return $a + $b; } function F_MINUS($a, $b) { return $a - $b; } function F_MULTIPLY($a, $b) { return $a * $b; } function F_DIVIDE($a, $b) { return $a / $b; } /** * Tokens and their functions */ $op1 = array('F_ADD' => '+', 'F_MINUS' => '-'); $op2 = array('F_MULTIPLY' => '*', 'F_DIVIDE' => '/'); $num = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'); //$tokens = ['NUM' => ['NUM', 'NEG_NUM'], 'OP' => ['OP1', 'OP2']] /* * TODO: Decimals */ function tokenize($str) { global $op1, $op2, $num; $input = str_split($str); $out = array(); for($i = 0, $len = count($input); $i < $len; $i++) { if($input[$i] == '-') { if(in_array($input[$i - 1], $num)) { $out[] = ['TOKEN' => 'OP1', 'VALUE' => array_search($input[$i], $op1)]; } else { $out[] = ['TOKEN' => 'NEG_NUM', 'VALUE' => '-'. $input[$i+1]]; $i++; } } else if(in_array($input[$i], $num)) { $value = $input[$i]; // Multi digit number while(++$i < $len && in_array($input[$i], $num)) { $value .= $input[$i]; } $i--; $out[] = ['TOKEN' => 'NUM', 'VALUE' => $value]; } else if(in_array($input[$i], $op1)) $out[] = ['TOKEN' => 'OP1', 'VALUE' => array_search($input[$i], $op1)]; else if(in_array($input[$i], $op2)) $out[] = ['TOKEN' => 'OP2', 'VALUE' => array_search($input[$i], $op2)]; else if($input[$i] == '(') $out[] = ['TOKEN' => 'GROUP_OPEN', 'VALUE' => $input[$i]]; else if($input[$i] == ')') $out[] = ['TOKEN' => 'GROUP_CLOSE', 'VALUE' => $input[$i]]; } return $out; } function operator_precedence($tokens) { $out = $tokens; $padding = 0; foreach ($tokens as $i => $token) { if($tokens[$i]['TOKEN'] == 'OP2') { if($tokens[$i-1]['TOKEN'] == 'GROUP_CLOSE') { //Find first GROUP_OPEN foreach ($tokens as $key => $token2) { if($token2['TOKEN'] == 'GROUP_OPEN') { array_splice($out, $key, 0, [['TOKEN' => 'GROUP_OPEN', 'VALUE' => '(']]); array_splice($out, $i+1+$padding +2, 0, [['TOKEN' => 'GROUP_CLOSE', 'VALUE' => ')']]); break; } } } else if($tokens[$i+1]['TOKEN'] == 'GROUP_OPEN') { foreach ($tokens as $key => $token2) { if($token2['TOKEN'] == 'GROUP_CLOSE') { array_splice($out, $i+1+$padding -2, 0, [['TOKEN' => 'GROUP_OPEN', 'VALUE' => '(']]); array_splice($out, $key+1, 0, [['TOKEN' => 'GROUP_CLOSE', 'VALUE' => ')']]); break; } } } else { array_splice($out, $i+1+$padding -2, 0, [['TOKEN' => 'GROUP_OPEN', 'VALUE' => '(']]); array_splice($out, $i+1+$padding +2, 0, [['TOKEN' => 'GROUP_CLOSE', 'VALUE' => ')']]); } $padding += 2; //Added two chrs to the array, so need to pad 2 steps } } return $out; } function treelize($tokens, $index = 0) { $out = []; for($i = $index, $len = count($tokens); $i < $len; $i++) { // Group, go one level deeper if($tokens[$i]['TOKEN'] == 'GROUP_OPEN') { $result = treelize($tokens, $i+1); $i = $result['index']; $out[] = $result['out']; } // Close group else if($tokens[$i]['TOKEN'] == 'GROUP_CLOSE') { return ['index' => $i, 'out' => $out]; } // Normal case else { $out[] = ['VALUE' => $tokens[$i]['VALUE'], 'TOKEN' => $tokens[$i]['TOKEN']]; } } return ['index' => $i, 'out' => $out]; } //Debug function function flattern($tokens) { $out = ""; foreach ($tokens as $token) { $out .= $token['VALUE'] . " "; } return $out; } /** * The function translate infix notation into polish notation. * * Turns 3*3+8 into * 3 + 3 8 * See it as: multiply(3, add(3, 8)) * * Turns (3+3)*10 into * + 3 3 10 * * Basically this function moves all operators one step to the left. */ function infix2polish($tree) { global $op1, $op2, $num; $out = []; foreach ($tree as $i => $item) { if(is_array(reset($item))) { $out[$i] = infix2polish($item); } else if(in_array($item['TOKEN'], ['OP1', 'OP2'])) { //Swap $tmp = $out[$i - 1]; $out[$i - 1] = $item; $out[$i] = $tmp; } else { $out[$i] = $item; } } return $out; } function run($tree) { if(in_array($tree[0]['TOKEN'], ['OP1', 'OP2'])) { //Array? if(is_array(reset($tree[1]))) $x = run($tree[1]); //OP? else if(in_array($tree[1]['TOKEN'], ['OP1', 'OP2'])) $x = run(array_values(array_slice($tree, 1))); //Num? else if(in_array($tree[1]['TOKEN'], ['NUM', 'NEG_NUM'])) $x = intval($tree[1]['VALUE']); //Array? if(is_array(reset($tree[2]))) $y = run($tree[2]); //OP? else if(in_array($tree[2]['TOKEN'], ['OP1', 'OP2'])) $y = run(array_values(array_slice($tree, 2))); //Num? else if(in_array($tree[2]['TOKEN'], ['NUM', 'NEG_NUM'])) $y = intval($tree[2]['VALUE']); return call_user_func($tree[0]['VALUE'], $x, $y); } // Handle special case like "3+(3)" else { return $tree[0]['VALUE']; } } ########### # Main ########### $input = $argv[1]; $tokens = tokenize($input); print_r($tokens); //$tokens = operator_precedence($tokens); //print_r($tokens); //echo flattern($tokens) . chr(10); //echo $input . chr(10); $tree = treelize($tokens); print_r($tree['out']); $functionized = infix2polish($tree['out']); print_r($functionized); $result = run($functionized); print_r($result); /* (5+(3+3)*10) (5+((3+3)*10)) 3+3(*10) 10*(3+3) (10*(3+3)) 3*4*5 (3*4)*5 ((3*4)*5) //*/ ?>

Dold text
Visa signatur

Programmerare -> PHP | HTML | CSS | JS | Java.

Permalänk
Datavetare
Skrivet av Sony?:

Äntligen!
...

Järnvägsalgoritmen var ny för mig, så riktigt kul att få lära sig något nytt som säkert kan komma till användning längre fram. Gillar din lösning bättre än min i.o.m din separation mellan infix->postfix samt beräkning när allt är i postfix (RPN).

Var fortfarande allt för inne på hur jag tänkte alla år med HP48 där miniräknaren hade en stack med tal och man fick köra operatorstacken i huvudet för att översätta infix till HP-räknarnas RPN, ser man out som det miniräknaren visade i sitt fönster så inser man lätt att det fungerar att evaluera alla operatorer i stället för att köa dem på out.

Hade varit ett väldigt mycket enklare progblem om det handlat om att göra en miniräknare med RPN-syntax

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem
Skrivet av Yoshman:

Järnvägsalgoritmen var ny för mig, så riktigt kul att få lära sig något nytt som säkert kan komma till användning längre fram. Gillar din lösning bättre än min i.o.m din separation mellan infix->postfix samt beräkning när allt är i postfix (RPN).

Var fortfarande allt för inne på hur jag tänkte alla år med HP48 där miniräknaren hade en stack med tal och man fick köra operatorstacken i huvudet för att översätta infix till HP-räknarnas RPN, ser man out som det miniräknaren visade i sitt fönster så inser man lätt att det fungerar att evaluera alla operatorer i stället för att köa dem på out.

Hade varit ett väldigt mycket enklare progblem om det handlat om att göra en miniräknare med RPN-syntax

Kul att du gillar min lösning

Att skapa en miniräknare med RPN-syntax går att göra på 117 tecken i PHP om man får använda eval() (var en tävling/utmaning PHPportalen höll för något år sen).

Visa signatur

Programmerare -> PHP | HTML | CSS | JS | Java.

Permalänk
Medlem

Efter en stunds hackande

Tänkte först att man kunde skippa steget att göra om infix till revers polish och använda bangårds algoritmen för att ge ett direkt resultat. Där man alltså endast har en operator stack och en värde stack. Det går men endast om allt är inneslutet i parenteser vilket inte är särskilt praktiskt.

Till slut fick jag ändra mig och göra om talet till revers polish för att sedan kunna räkna ut resultatet.

#include <iostream> #include <stack> #include <queue> #include <string> #include <sstream> #include <algorithm> #include <ctype.h> #define cts(x) string(1,x) //char till sträng using namespace std; std::stack<char> oper; //operator staken std::queue<string> output; //resultatet i reverse polish stack<double> vals; //numer stacken för uträkningen bool String2Double(const std::string& str, double& result) // bra string to int from the interwebs { std::istringstream ss(str); ss.imbue(std::locale::classic()); ss >> result; return !ss.fail() && ss.eof(); } bool IsRight(char o){ // höger eller vänster asosiatív return o == '^'; } bool IsLeft(char o){ return !IsRight(o); //hackat } int Precedence(char o){ //operator rank switch (o) { case '+': return 1; case '-': return 1; case '/': return 2; case '*': return 2; case '^': return 3; } return 0; } //räknar ut resultatet av en operation //och lägger tillbaka det på värdes stacken void domath() { // popa operatorn och de senaste värdena char c = output.front()[0]; output.pop(); double a, b; a = vals.top(); vals.pop(); b = vals.top(); vals.pop(); switch(c) { case '*': vals.push(a*b); break; case '+': vals.push(a+b); break; case '-': vals.push(a-b); break; case '/': vals.push(a/b); break; case '^': vals.push(pow(a,b)); break; } } //Till omvänd polsk notation int ToReversePolish(string exp) { exp.erase(remove_if(exp.begin(), exp.end(), isspace), exp.end()); for(int i = 0; i < exp.length(); i++) { if(isalpha(exp[i])) break; //todo implementera variabler ok funktioner if (isdigit(exp[i])) { string temp=""; for(int j = i; j < exp.length(); j++) { if(isdigit(exp[j]) || exp[j] == '.') { temp += exp[j]; } else { i = j-1; break; } } output.push(temp); }else { if(exp[i] == '(') oper.push('('); else if( exp[i] == ')') { while(oper.top() != '(') { output.push(cts(oper.top())); oper.pop(); } oper.pop(); }else { char o1 = exp[i]; while (oper.size() > 0) { char o2 = oper.top(); if((IsLeft(o1) && Precedence(o1) <= Precedence(o2)) || (IsRight(o1) && Precedence(o1) < Precedence(o2))) { output.push(cts(o2)); oper.pop(); }else { break; } } oper.push(o1); } } } while(oper.size() > 0) { output.push(cts(oper.top())); oper.pop(); } return 1; } //Räknar ut den omvända polska notationen int calculate(string str) { ToReversePolish(str); while(output.size() > 0) { string s = output.front(); if(s.length() > 1 || isalnum(s[0])) { double d = 0; String2Double(s,d); vals.push(d); output.pop(); }else { domath(); } } return vals.top(); } int main(int argc, _TCHAR* argv[]) { string s; cin>>s; cout << calculate(s); return 0; }

Dold text

Koden är skriven i C++ med standard libarys. Brukar för kul skriva om mina C++ program till mer C stils kod men stringhanteringen i C är klumpig så vi får se om jag har tid.

Todo:
Fler operatorer
Negativa tal !
Variabler

Visa signatur

KTH student, programmerare, arch användare

Permalänk
Medlem

Objective-C

Gav mig på progblemet en sen kväll för någon vecka sedan, och tänkte då lite naivt att "hur svårt kan det vara"… De många timmarna av grubblande, svordomar och skrotade idéer skvallrar om svaret.

Hursomhelst började jag och min envishet till slut läsa på lite om postfix, och fick ihop en rörig lösning i Objective-C som verkar fungera (nåja, inte helt). I sin nuvarande form ska den klara av de vanliga räknesätten (+-*/), parenteser, negativa tal och flyttal, men klarar t.ex. inte av att negera parenteser.

Calculator.m

// // Calculator.m // InfixCalculator // // Created by Karl Persson on 2014-07-21. // Copyright (c) 2014 Karl Persson. All rights reserved. // #import "Calculator.h" // Operation types // {right parenthesis, subtraction, addition, divition multiplication, left parenthesis} typedef enum {RIP, SUB, ADD, DIV, MUL, LEP} operatorType; /* * Helper class for handling operators */ @interface Operator : NSObject @property (nonatomic) operatorType type; + (Operator *)operatorFromChar:(char)c; - (NSNumber *)calculate:(NSNumber *)first second:(NSNumber *)second; @end /* * Calculator */ @implementation Calculator // Execute calculation based on input string + (NSNumber *)calculate:(NSString *)calculation { // Infix to postfix NSArray *postfix = [self infixToPostfix:calculation]; // Calculate return [self executeCalculation:postfix]; } // Convert infix expression to postfix + (NSArray *)infixToPostfix:(NSString *)infix { NSCharacterSet *operatorCharacters = [NSCharacterSet characterSetWithCharactersInString:@-+/*()]; NSCharacterSet *operandCharacters = [NSCharacterSet characterSetWithCharactersInString:@1234567890.]; NSMutableArray *postfix = [[NSMutableArray alloc] init]; NSMutableArray *stack = [[NSMutableArray alloc] init]; // Start conversion NSMutableString *operand = [[NSMutableString alloc] init]; BOOL operatorLast = YES; for (int i = 0; i < infix.length; ++i) { char c = [infix characterAtIndex:i]; // Save operand if (operand.length > 0 && ([operatorCharacters characterIsMember:c] || i == infix.length-1)) { [postfix addObject:[NSNumber numberWithDouble:[operand doubleValue]]]; [operand setString:@""]; } // Test character if ([operandCharacters characterIsMember:c]) { // Operand part [operand appendString:[NSString stringWithFormat:@%c, c]]; operatorLast = NO; } else if ([operatorCharacters characterIsMember:c]) { // Operator Operator *operator = [Operator operatorFromChar:c]; // Negative/positive number or an actual operation if (operatorLast && (operator.type == SUB || operator.type == ADD)) { // Positive/nexative if (operator.type == SUB) { [operand insertString:@- atIndex:0]; } operatorLast = NO; } else { // Operation // Pop stack until lower type is found Operator *topOperator = [stack lastObject]; while (stack.count > 0 && topOperator.type > operator.type) { // Stop popping where a complete parenthesis is found if (topOperator.type != LEP || operator.type == RIP) { [stack removeObject:topOperator]; // Don't add LEP to postfix if (topOperator.type != LEP) { [postfix addObject:topOperator]; } } else break; topOperator = [stack lastObject]; } // Save operator if (operator.type != RIP) { [stack addObject:operator]; operatorLast = YES; } } } } // Pop remaining stack for (Operator *operator in [stack reverseObjectEnumerator]) { if (operator.type != LEP) { [postfix addObject:operator]; } } return postfix; } // Execute postfix calculation + (NSNumber *)executeCalculation:(NSArray *)postfix { NSMutableArray *stack = [[NSMutableArray alloc] init]; for (NSString *component in postfix) { if ([component isKindOfClass:[NSValue class]]) { // Operand [stack addObject:component]; } else { // Operator if (stack.count >= 2) { // Pop operands from stack NSNumber *second = [stack lastObject]; [stack removeLastObject]; NSNumber *first = [stack lastObject]; [stack removeLastObject]; // Calculate result and push to stack [stack addObject:[(Operator *)component calculate:first second:second]]; } else { NSLog(@ERROR: Not sufficient values!); } } } // Check result if (stack.count == 1) { return [stack objectAtIndex:0]; } else { NSLog(@ERROR: Too many values!); } return [NSNumber numberWithDouble:0.0f]; } @end @implementation Operator // Create an operator + (Operator *)operatorFromChar:(char)c { operatorType type; switch (c) { case '-': type = SUB; break; case '+': type = ADD; break; case '/': type = DIV; break; case '*': type = MUL; break; case '(': type = LEP; break; case ')': type = RIP; break; default: return nil; } // Create operator Operator *op = [[Operator alloc] init]; op.type = type; return op; } // Calculate with operator - (NSNumber *)calculate:(NSNumber *)first second:(NSNumber *)second { switch (self.type) { case SUB: return [NSNumber numberWithDouble:[first doubleValue] - [second doubleValue]]; case ADD: return [NSNumber numberWithDouble:[first doubleValue] + [second doubleValue]]; case DIV: return [NSNumber numberWithDouble:[first doubleValue] / [second doubleValue]]; case MUL: return [NSNumber numberWithDouble:[first doubleValue] * [second doubleValue]]; default: return [NSNumber numberWithDouble:0.0f]; } } @end

Dold text

(Och i sin helhet på GitHub)

Kommer förmodligen inte kunna släppa det på ytterligare någon vecka som minst, så InfixToPostfix-funktionen ligger fortfarande väldigt pyrt till. Men då har man förstås något att fundera över i solstolen också.

Fruktansvärt roligt och klurigt progblem får jag säga (åtminstone för en som egentligen aldrig riktigt har grubblat över hur en räknare fungerar).

Visa signatur

Stationärt: i7 3770k, Gigabyte GTX 780 WF, 4x8GB XMS3, MSI Z77A-GD65, Samsung 830 + 840, FD Newton 650W, FD Define R2
Bärbart: MacBook Pro 13" Tidigt 2011, Core i5, 8GB DDR3, OCZ Vertex 3 LT 240GB
Hemmaserver: HP Microserver N54L, 4GB ECC, Debian

Permalänk
Medlem

Jag satt en kväll till och gjorde om det till en infix till RPN-konverterare och RPN-evaluerare. Fortfarande i C#.
Inkluderar rubbet, extension methods inklusive egen enkel implementation av en Stack samt en dubbellänkad stack.

public abstract class MathNode { } public class Parenthesis : Operator { public override int getPrecedence() { throw new NotImplementedException(); } protected override Argument eval(Argument arg1, Argument arg2) { throw new NotImplementedException(); } } public class LeftParenthesis : Parenthesis { } public class RightParenthesis : Parenthesis { } public interface IRightAssociative { } public interface IRightRPNOrder { } public abstract class Operator : MathNode { public static Dictionary<char, Operator> Hash = new Dictionary<char, Operator>() .Append('−', new SubOperator()) .Append('(', new LeftParenthesis()) .Append(')', new RightParenthesis()) .Append('-', new SubOperator()) .Append('+', new AddOperator()) .Append('/', new DivOperator()) .Append('*', new MulOperator()) .Append('%', new RemOperator()) .Append('s', new SqrOperator()) .Append('^', new PowOperator()); public Argument evaluate(Argument arg1, Argument arg2) { return eval(arg1, arg2); } public abstract int getPrecedence(); public bool isRightRPNOrder() { return (this is IRightRPNOrder); } public bool isLeftAssociative() { return !(this is IRightAssociative); } protected abstract Argument eval(Argument arg1, Argument arg2); public Argument evaluateRPN(SingleStack<Argument> numberStack) { var a1 = numberStack.Pop(); var a2 = numberStack.Pop(); if (this.isRightRPNOrder()) return evaluate(a2, a1); return evaluate(a1, a2); } } public class AddOperator : Operator { protected override Argument eval(Argument arg1, Argument arg2) { return new Argument(arg1.Value + arg2.Value); } public override int getPrecedence() { return 2; } public override string ToString() { return "+"; } } public class SubOperator : Operator, IRightRPNOrder { protected override Argument eval(Argument arg1, Argument arg2) { return new Argument(arg1.Value - arg2.Value); } public override int getPrecedence() { return 2; } public override string ToString() { return "-"; } } public class MulOperator : Operator { protected override Argument eval(Argument arg1, Argument arg2) { return new Argument(arg1.Value * arg2.Value); } public override int getPrecedence() { return 3; } public override string ToString() { return "*"; } } public class DivOperator : Operator, IRightRPNOrder { protected override Argument eval(Argument arg1, Argument arg2) { return new Argument(arg1.Value / arg2.Value); } public override int getPrecedence() { return 3; } public override string ToString() { return "/"; } } public class RemOperator : Operator, IRightRPNOrder { protected override Argument eval(Argument arg1, Argument arg2) { return new Argument(arg1.Value % arg2.Value); } public override int getPrecedence() { return 3; } public override string ToString() { return "%"; } } public class PowOperator : Operator, IRightAssociative, IRightRPNOrder { protected override Argument eval(Argument arg1, Argument arg2) { return new Argument(Math.Pow(arg1.Value, arg2.Value)); } public override int getPrecedence() { return 4; } public override string ToString() { return "^"; } } public class SqrOperator : Operator { protected override Argument eval(Argument arg1, Argument arg2) { return new Argument(Math.Sqrt(arg1.Value)); } public override int getPrecedence() { throw new NotImplementedException(); } public override string ToString() { return "s"; } } public class MathEvaluator { public static Argument eval(string text) { text = text.Replace(" ", ""); var sequence = parse(text); return sequence; } private static Argument parse(string infix) { var rpn = convertInfixToRpn(infix); return evaluateRPN(rpn); } private static Argument evaluateRPN(DoubleStack<MathNode> rpn) { var numberStack = new SingleStack<Argument>(); while (!rpn.IsEmpty()) { var popped = rpn.PopBottom(); if (popped is Argument) numberStack.Push(popped as Argument); else if (popped is Operator) { var value = (popped as Operator).evaluateRPN(numberStack); numberStack.Push(value); } } return numberStack.Pop(); } private static DoubleStack<MathNode> convertInfixToRpn(string infix) { var outPutStack = new DoubleStack<MathNode>(); var tempStack = new SingleStack<MathNode>(); var tokens = infix.SplitAndKeep(Operator.Hash.Keys.ToArray(), StringSplitOptions.RemoveEmptyEntries); //While there are tokens to be read foreach (var token in tokens) { //Read a token //If the token is a number (build number by appending characters until token is NOT a number) var number = Double.NaN; if (Double.TryParse(token, out number)) { //then add it to the output queue outPutStack.Push(new Argument(number)); continue; } //If the token is an operator, o1 else if (Operator.Hash.ContainsKey(token[0])) { Operator o1 = Operator.Hash[token[0]]; //If the token is a left parenthesis if (o1 is LeftParenthesis) { //push it onto the stack tempStack.Push(o1); continue; } //If the token is a right parenthesis if (o1 is RightParenthesis) { LeftParenthesis lp = null; //Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue while ((lp = tempStack.Peek() as LeftParenthesis) == null) { outPutStack.Push(tempStack.Pop()); } //Pop the left parenthesis from the stack, but not onto the output queue tempStack.Pop(); continue; } Operator o2 = null; //while there is an operator token, o2, at the top of the stack while (!tempStack.IsEmpty() && (o2 = tempStack.Peek() as Operator) != null && !(o2 is Parenthesis)) { //either o1 is left-associative and its precedence is less than or equal to that of o2, //or o1 has precedence less than that of o2 if ((o1.isLeftAssociative() && o1.getPrecedence() <= o2.getPrecedence()) || o1.getPrecedence() < o2.getPrecedence()) { //pop o2 off the stack, onto the output queue outPutStack.Push(tempStack.Pop()); continue; } break; } //push o1 onto the stack. tempStack.Push(o1); continue; } } //When there are no more tokens to read //While there are still operator tokens in the stack while (!tempStack.IsEmpty()) { //Pop the operator onto the output queue outPutStack.Push(tempStack.Pop()); } return outPutStack; } } public class Argument : MathNode { public double Value { get; set; } public Argument(double value) { Value = value; } public Argument(string value) { Value = Double.Parse(value); } public override string ToString() { return Value.ToString(); } } public static class Extensions { public static Dictionary<TKey, TValue> Append<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey key, TValue value) { dict[key] = value; return dict; } public static bool isValidNumericValue(this char c) { return (char.IsNumber(c) || c == '.' || c == ','); } public static IEnumerable<string> SplitAndKeep(this string s, char[] delims, StringSplitOptions options) { int start = 0; int index = 0; while ((index = s.IndexOfAny(delims, start)) != -1) { index++; index = Interlocked.Exchange(ref start, index); var res1 = s.Substring(index, start - index - 1); if (res1.Length > 0 && options == StringSplitOptions.RemoveEmptyEntries) yield return res1; var res2 = s.Substring(start - 1, 1); if (res2.Length > 0 && options == StringSplitOptions.RemoveEmptyEntries) yield return res2; } if (start < s.Length) { var res3 = s.Substring(start); if (res3.Length > 0 && options == StringSplitOptions.RemoveEmptyEntries) yield return res3; } } } public class SingleStack<T> { public SingleStack() { List = new LinkedList<T>(); } protected LinkedList<T> List { get; set; } public T Peek() { return List.Last.Value; } public T Pop() { var value = Peek(); List.RemoveLast(); return value; } public void Push(T value) { List.AddLast(value); } public int Count { get { return List.Count; } } public bool IsEmpty() { return (Count == 0); } } public class DoubleStack<T> : SingleStack<T> { public DoubleStack() : base() { } public T PeekBottom() { return List.First.Value; } public T PopBottom() { var value = PeekBottom(); List.RemoveFirst(); return value; } public void PushBottom(T value) { List.AddFirst(value); } }

Dold text
Visa signatur

ηλί, ηλί, λαμά σαβαχθανί!?

Permalänk
Datavetare

Emacs lisp

Skrivet av TheFork:

Fruktansvärt roligt och klurigt progblem får jag säga (åtminstone för en som egentligen aldrig riktigt har grubblat över hur en räknare fungerar).

Detta var inget jag funderat på tidigare heller, vilket visade sig i att det initiala försöket var via BNF när järnvägsalgoritmen visade sig vara betydligt enklare, om man undantar fallet att använda något likt lex & yacc. Även det slog mig att har faktiskt implementerat ett par parsers i C just m.h.a. av BNF.

Var inte helt nöjd med senaste Go-versionen, men i stället för att bara fixa till den (som skulle vara mer eller mindre trivialt) så kändes det lika bra att börja från scratch med ett annat språk. Denna gång blev det en variant i Emacs Lisp. Emacs är min favoriteditor, så alltid bra att göra lite hjärngympa i elisp

Denna version är verkligen uppdelad i små steg, med input "1 + 2 *(3 + 4) - 5" utförs följande steg

"1 + 2 *(3 + 4) - 5" str-to-strtok -> ("1" "+" "2" "*" "(" "3" "+" "4" ")" "-" "5") strtok-to-tok -> (1 :add 2 :mul :lpar 3 :add 4 :rpar :sub 5) shunting-yard -> (1 2 3 4 :add :mul 5 :sub :add) rpn-to-sexp -> (+ 1 (- (* 2 (+ 3 4)) 5)) eval -> 10

calc.el

(require 'cl) ; property list of operators, each operator must specify priority, ; left/right associative, Lisp operator and string representation (setq ops '(:add (:prio 1 :assoc :left :repr "+" :op +) :sub (:prio 1 :assoc :left :repr "-" :op -) :mul (:prio 2 :assoc :left :repr "*" :op *) :div (:prio 2 :assoc :left :repr "/" :op /) :pow (:prio 3 :assoc :right :repr "^" :op expt))) (defun op-attr (op attr) (plist-get (plist-get ops op) attr)) (defun op-assoc (op) (op-attr op :assoc)) (defun op-prio (op) (op-attr op :prio)) (defun op-repr (op) (op-attr op :repr)) (defun op-op (op) (op-attr op :op)) (defun ch-type (c) (cond ((string-match "[[:space:]]" (string c)) :space) ((and (>= c ?0) (<= c ?9)) :number) ((or (eq c ?\() (eq c ?\))) :par) (t :operator))) (defun str-to-strtok (str) "Turns a string of numbers and operators into a list of tokens \"1+2\" -> '(1 + 2)" (let ((tokens) (prev-tok :number)) (dolist (ch (string-to-list str)) (let ((ct (ch-type ch))) (if (not (eq ct :space)) (progn (setq tokens (if (eq prev-tok ct) (cons (concat (car tokens) (string ch)) (cdr tokens)) (cons (string ch) tokens))) (setq prev-tok ct))))) (nreverse tokens))) (defun create-tok (tok) "Converts a string into a number or operator keyword, \"+\" -> :add" (cond ((string-match "\\`[-+]?[0-9]+\\'" tok) (string-to-number tok)) ((string-equal tok ")") :rpar) ((string-equal tok "(") :lpar) (t (let ((result-op)) (dolist (o ops result-op) (if (and (keywordp o) (equal tok (op-repr o))) (setq result-op o))))))) (defun strtok-to-tok (strtok) "Converts a list of string tokens into a list of integers and operator keywords, (\"1\" \"+\" \"2\") -> (1 :add 2)" (mapcar 'create-tok strtok)) (defun shunting-yard (tokens) "Converts from infix to postfix notion, (1 :add 2) -> (1 2 :add)" (let ((out) (stack)) (dolist (tok tokens) (cond ((numberp tok) (setq out (cons tok out))) ((eq tok :lpar) (setq stack (cons tok stack))) ((eq tok :rpar) (while (let ((ts (pop stack))) (if (eq ts :lpar) nil (setq out (cons ts out)))))) (t (progn (while (let* ((o2 (car stack)) (o2-prio (op-prio o2)) (o1-prio (op-prio tok)) (o1-assoc (op-assoc tok))) (if (and o2 (not (eq o2 :lpar)) (or (and (eq o1-assoc :right) (< o1-prio o2-prio)) (and (not (eq o1-assoc :right)) (<= o1-prio o2-prio)))) (progn (pop stack) (push o2 out)) nil))) (setq stack (cons tok stack)))))) (append (nreverse out) stack))) (defun rpn-to-sexp (tokens) "Converts a list of tokens in RPN order to a S-expression, (1 2 3 :add :mul) -> (+ 1 (* 2 3))" (let ((sexp)) (car (dolist (tok tokens sexp) (if (numberp tok) (push tok sexp) (let ((a (pop sexp)) (b (pop sexp))) (push (list (op-op tok) b a) sexp))))))) (defun ecalc (str) (interactive "M") (print (eval (rpn-to-sexp (shunting-yard (strtok-to-tok (str-to-strtok str)))))))

Dold text

Laddar man in detta i Emacs, kör M-x eval-buffer så kan man sedan köra räknaren som det interaktiva kommandot ecalc (finns redan ett inbyggt kommando i Emacs som heter calc).

Edit: lade till "upphöjt-till" så den har samma feature-set som C-versionen nedan.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem

Kunde inte låta bli att sätta tänderna i min InfixToPostfix-funktion ytterligare för att få unära plus- och minus-operationer att fungera lite bättre.

Valde att dela upp funktionen i två delar, så att uttrycket delas upp i operander och operatorer innan översättningen till postfix påbörjas. På så vis blev det lite lättare att urskilja unära operationer från binära (för att t.ex. kunna negera en parentes ordentligt, vilket den tidigare lösningen inte fixade). Som en bonus fungerar även +-, -+, ++ och -- som dom ska.

Calculator.m

(I sin helhet på GitHub)

// // Calculator.m // InfixCalculator // // Created by Karl Persson on 2014-07-21. // Copyright (c) 2014 Karl Persson. All rights reserved. // #import "Calculator.h" // Operation types // {right parenthesis, subtraction, addition, divition multiplication, unary subtraction, left parenthesis} typedef enum {RIP, SUB, ADD, DIV, MUL, LEP, USU} operatorType; /* * Helper class for handling operators */ @interface Operator : NSObject @property (nonatomic) operatorType type; + (Operator *)operatorFromChar:(char)c; - (NSNumber *)calculate:(NSNumber *)first second:(NSNumber *)second; @end /* * Calculator */ @implementation Calculator // Execute calculation based on input string + (NSNumber *)calculate:(NSString *)calculation { // Split infix NSArray *infix = [self splitInfixCalculation:calculation]; // Infix to postfix NSArray *postfix = [self infixToPostfix:infix]; // Calculate return [self executeCalculation:postfix]; } // Split infix calculation into operators and operands + (NSArray *)splitInfixCalculation:(NSString *)calculation { NSCharacterSet *operatorCharacters = [NSCharacterSet characterSetWithCharactersInString:@-+/*()]; NSCharacterSet *operandCharacters = [NSCharacterSet characterSetWithCharactersInString:@1234567890.]; NSMutableArray *result = [[NSMutableArray alloc] init]; NSMutableString *operand = [[NSMutableString alloc] init]; for (int i = 0; i < calculation.length; ++i) { char c = [calculation characterAtIndex:i]; if ([operatorCharacters characterIsMember:c]) { // Operator // Save buffered operand if (operand.length > 0) { [result addObject:[NSNumber numberWithDouble:[operand doubleValue]]]; [operand setString:@""]; } Operator *op = [Operator operatorFromChar:c]; // Unary check // Operator is unary only if... // - the previous component was an operator or nothing at all // - the previous component wasn't a parenthesis // - and the operator is either addition or subtraction. if ((result.count == 0 || [[result lastObject] isKindOfClass:[Operator class]]) && (op.type == ADD || op.type == SUB)) { Operator *top = [result lastObject]; if (top == nil || (top.type != RIP && top.type != LEP)) { // Ignore unary addition if (op.type == ADD) continue; else op.type = USU; } } [result addObject:op]; } else if ([operandCharacters characterIsMember:c]) { // Operand [operand appendString:[NSString stringWithFormat:@%c, c]]; } } // Save remaining operand if (operand.length > 0) { [result addObject:[NSNumber numberWithDouble:[operand doubleValue]]]; } return result; } // Convert infix expression to postfix + (NSArray *)infixToPostfix:(NSArray *)infix { NSMutableArray *postfix = [[NSMutableArray alloc] init]; NSMutableArray *stack = [[NSMutableArray alloc] init]; for (id component in infix) { if ([component isKindOfClass:[Operator class]]) { // Operator found Operator *op = component; if (op.type == LEP) { // Left parenthesis [stack addObject:op]; } else { // Operator or right parenthesis Operator *top = [stack lastObject]; while (stack.count > 0 && (op.type <= top.type || op.type == RIP)) { // Pop top of the stack, stop if parenthesis is found [stack removeLastObject]; if (top.type == LEP) break; else [postfix addObject:top]; top = [stack lastObject]; } // Add operator to stack if (op.type != RIP) { [stack addObject:op]; } } } else if ([component isKindOfClass:[NSNumber class]]) { // Operand found [postfix addObject:component]; } } // Pop remaining stack for (Operator *operator in [stack reverseObjectEnumerator]) { if (operator.type != LEP) { [postfix addObject:operator]; } } return postfix; } // Execute postfix calculation + (NSNumber *)executeCalculation:(NSArray *)postfix { NSMutableArray *stack = [[NSMutableArray alloc] init]; for (id component in postfix) { if ([component isKindOfClass:[NSValue class]]) { // Operand [stack addObject:component]; } else { // Operator Operator *operator = component; if (operator.type == USU && stack.count > 0) { // Unary minus NSNumber *first = [stack lastObject]; [stack removeLastObject]; [stack addObject:[operator calculate:first second:nil]]; } else if (stack.count >= 2) { // Pop operands from stack NSNumber *second = [stack lastObject]; [stack removeLastObject]; NSNumber *first = [stack lastObject]; [stack removeLastObject]; // Calculate result and push to stack [stack addObject:[operator calculate:first second:second]]; } else { NSLog(@ERROR: Not sufficient values!); } } } // Check result if (stack.count == 1) { return [stack objectAtIndex:0]; } NSLog(@ERROR: Too many values!); return [NSNumber numberWithDouble:0.0f]; } @end @implementation Operator // Create an operator + (Operator *)operatorFromChar:(char)c { operatorType type; switch (c) { case '-': type = SUB; break; case '+': type = ADD; break; case '/': type = DIV; break; case '*': type = MUL; break; case '(': type = LEP; break; case ')': type = RIP; break; default: return nil; } // Create operator Operator *op = [[Operator alloc] init]; op.type = type; return op; } // Calculate with operator - (NSNumber *)calculate:(NSNumber *)first second:(NSNumber *)second { switch (self.type) { case SUB: return [NSNumber numberWithDouble:[first doubleValue] - [second doubleValue]]; case ADD: return [NSNumber numberWithDouble:[first doubleValue] + [second doubleValue]]; case DIV: return [NSNumber numberWithDouble:[first doubleValue] / [second doubleValue]]; case MUL: return [NSNumber numberWithDouble:[first doubleValue] * [second doubleValue]]; case USU: return [NSNumber numberWithDouble:[first doubleValue] * -1.0f]; default: return [NSNumber numberWithDouble:0.0f]; } } @end

Dold text
Visa signatur

Stationärt: i7 3770k, Gigabyte GTX 780 WF, 4x8GB XMS3, MSI Z77A-GD65, Samsung 830 + 840, FD Newton 650W, FD Define R2
Bärbart: MacBook Pro 13" Tidigt 2011, Core i5, 8GB DDR3, OCZ Vertex 3 LT 240GB
Hemmaserver: HP Microserver N54L, 4GB ECC, Debian

Permalänk
Medlem
Skrivet av Yoshman:

Nej, man måste inte har mellanrum mellan alla forms och jag är helt konsekvent att inte ha det mellan funktionsnamn och argument. Däremot kommer jag nu ihåg att det normalt att ha det i Clojure (tack vare ditt subtila påpekade ). Har bevisligen glömt rätt mycket i det språket eftersom jag inte hittat något vettigt man kan använda det till. Sedan är det väl Clojure som är inkonsekvent då argument inte är en lista som (nästan) allt annat.

Höll på rätt mycket med Clojure för några år sedan då det i teorin är riktigt bra för "concurrent progamming" men tyvärr är det närapå värdelöst till detta i praktiken p.g.a. usel skalning över CPU-kärnor och allt för komplicerad modell för I/O när man har många samtida kontext. Är också därför jag stark gillar Go, det hanterar "concurrent programming" både i teori och i praktik + att jag personligen föredrar statiskt typade språk över dynamiskt typade.

Edit: Inte så att jag säger att Clojure är generellt dåligt, tvärt om är det ett riktigt trevligt språk. Det var däremot inte bra på det jag ville använda det till.

Nej, listor är nästan alltid anrop. Argumentvektorn är inget anrop. Och btw så kan man ju typehinta argumentvektorn, och då måste man ha i alla fall ett mellanrum, skulle jag tro.

Permalänk
Datavetare
Skrivet av tufflax:

Nej, listor är nästan alltid anrop. Argumentvektorn är inget anrop. Och btw så kan man ju typehinta argumentvektorn, och då måste man ha i alla fall ett mellanrum, skulle jag tro.

En kritik som Clojure fått ta emot från Lisp-puritaner är att den inte använder listor i alla lägen som en "riktig" Lisp. Common lisp, Emacs Lisp, Scheme m.fl. använder alla listor även för argumentvektorn.

Är själv ingen puritan, fördrar den gyllne medelvägen i de flesta fall. Dock finns en uppenbar nackdel i Clojures val att både ha listor (som växer i fronten) och vektorer (som växer på slutet) i att anrop som conj inte är triviala att förstå då dess beteende beror av om det är en lista eller vektor man skickar in (jag använder denna skillnad i mitt program). Ett annat problem är att Clojure-program lätt kan skapa massor med "skräp" och massor med potentiellt dyra kopieringar av listor/vektorer då man går mellan dessa representationer (händer flera gånger i mitt program och är inte helt uppenbart var).

Type-hint kräver inte mellanrum, detta fungerar alldeles utmärkt

(defn foo[^int n](println n))

Däremot håller jag med om att det bör vara mellanrum, var bara väldigt ringrostig i Clojure då jag i praktiken helt slutat använt det (håller lite Lisp-takter igång tack vare elisp).

Type-hint visar för övrigt ett av (minst) två stora problem jag ser med dynamiskt typade språk: dessa finns för de ger en rejält prestandaboost, en boost som i princip alltid statiskt typade språk har över dynamiskt typade.

Det andra stora problemet är att problem är billigare/lättare att laga ju tidigare de upptäcks, statiskt typade språk upptäcker en rad buggar redan vi kompileringstillfället som i bästa fall hittas av automatiska tester eller vid kodgranskning, vilket då redan ökat kostnaden (i arbetstid) mångfalt.

Lite OT, men gillar att diskutera programmeringsrelaterade frågor

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Datavetare

Har rest lite och som lite tidsfördriv kunde jag inte låta bli att skriva en ren C-version, d.v.s. utan lex & yacc men järnvägsalgoritmen.

En intressant sak med denna implementation är att den faktiskt aldrig använder sig av dynamisk allokerat minne (getline gör det internt, men det är enda fallet). Till skillnad från de flesta andra progblemet så var inte detta helt trivial (det är mer än några tiotal rader) och även detta fall visar det min erfarenhet av C kontra "moderna" språk är: det blir ingen större skillnad i antal rader kod men C-versionen är nästan alltid betydligt kompaktare och kör snabbare tack vare mycket högre s.k. spatial/temporal locality. I detta fall är det helt irrelevant då problemet är så enkelt att jobba med för en modern CPU, men på en miniräknare med 8-bitars CPU på ett par MHz är det nog rätt uppenbart att man kanske inte har Lisp som förstahands val...

#define _GNU_SOURCE /* stdio.h -> getline() */ #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_CHR 128 typedef long num_t; typedef enum { ASSOC_NONE, ASSOC_LEFT, ASSOC_RIGHT } assoc_t; typedef struct { char tok; int prio; assoc_t assoc; num_t(*fn)(num_t, num_t); } operator_t; typedef enum { TOK_SPACE, TOK_NUM, TOK_OP, TOK_LPAR, TOK_RPAR } token_type_t; typedef struct { token_type_t what; num_t val; } token_t; operator_t *chr_to_op[MAX_CHR]; token_type_t chr_to_type[MAX_CHR]; static num_t __add(num_t a, num_t b) { return a + b; } static num_t __sub(num_t a, num_t b) { return a - b; } static num_t __mul(num_t a, num_t b) { return a * b; } static num_t __div(num_t a, num_t b) { return a / b; } static num_t __pow(num_t a, num_t b) { return b == 0 ? 1 : a * __pow(a, b - 1); } operator_t ops[] = { { .tok = '+', .prio = 1, .assoc = ASSOC_LEFT, .fn = __add }, { .tok = '-', .prio = 1, .assoc = ASSOC_LEFT, .fn = __sub }, { .tok = '*', .prio = 2, .assoc = ASSOC_LEFT, .fn = __mul }, { .tok = '/', .prio = 2, .assoc = ASSOC_LEFT, .fn = __div }, { .tok = '^', .prio = 3, .assoc = ASSOC_RIGHT, .fn = __pow }, }; operator_t *find_op(char tok) { for (int i = 0; i < sizeof ops / sizeof *ops; i++) if (ops[i].tok == tok) return ops + i; return NULL; } void init_lookup_tables() { for (int i = 0; i < sizeof chr_to_type / sizeof *chr_to_type; i++) { token_type_t tt = TOK_OP; if (NULL == (chr_to_op[i] = find_op(i))) { if (isdigit(i)) tt = TOK_NUM; else if (i == '(') tt = TOK_LPAR; else if (i == ')') tt = TOK_RPAR; else tt = TOK_SPACE; } chr_to_type[i] = tt; } } int to_tokens(token_t *tok, char *line, int cnt) { char *str; token_type_t what; num_t val; if (*line == '\0') return cnt; what = chr_to_type[*line]; switch (what) { case TOK_SPACE: return to_tokens(tok, line + 1, cnt); case TOK_NUM: val = strtol(line, &str, 10); break; default: val = *line; str = line + 1; break; } if (tok != NULL) { tok->what = what; tok->val = val; tok++; } return to_tokens(tok, str, cnt + 1); } int shunting_yard(int num_tok, token_t *toks) { token_t stack_storage[num_tok]; token_t *empty_stack = stack_storage + num_tok; token_t *stack = empty_stack; token_t *out = toks; token_t *t; operator_t *o1; operator_t *o2; for (int i = 0; i < num_tok; i++) { switch (toks[i].what) { case TOK_NUM: *out++ = toks[i]; break; case TOK_LPAR: *--stack = toks[i]; break; case TOK_RPAR: while ((t = stack++)->what != TOK_LPAR) *out++ = *t; break; case TOK_OP: o1 = chr_to_op[toks[i].val]; while (stack != empty_stack) { o2 = chr_to_op[stack->val]; if (stack->what == TOK_LPAR || !((o1->assoc == ASSOC_RIGHT && o1->prio < o2->prio) || (o1->assoc != ASSOC_RIGHT && o1->prio <= o2->prio))) { break; } *out++ = *stack++; } *--stack = toks[i]; } } while (stack != empty_stack) { *out++ = *stack++; } return out - toks; } num_t eval_rpn(int num_tok, token_t *toks) { num_t stack_storage[num_tok]; num_t *stack = stack_storage; for (int i = 0; i < num_tok; i++) { if (toks[i].what == TOK_NUM) { *stack++ = toks[i].val; } else { int b = *--stack; int a = *--stack; *stack++ = chr_to_op[toks[i].val]->fn(a, b); } } return *--stack; } num_t calc(int num_tok, char *line) { token_t tokens[num_tok]; to_tokens(tokens, line, 0); return eval_rpn(shunting_yard(num_tok, tokens), tokens); } int main(int argc, char *argv[]) { char *line = NULL; size_t n; init_lookup_tables(); while (getline(&line, &n, stdin) >= 0) { int num_tok = to_tokens(NULL, line, 0); printf("=%ld\n", calc(num_tok, line)); } }

Dold text
Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem
Skrivet av Yoshman:

En kritik som Clojure fått ta emot från Lisp-puritaner är att den inte använder listor i alla lägen som en "riktig" Lisp. Common lisp, Emacs Lisp, Scheme m.fl. använder alla listor även för argumentvektorn.

Är själv ingen puritan, fördrar den gyllne medelvägen i de flesta fall. Dock finns en uppenbar nackdel i Clojures val att både ha listor (som växer i fronten) och vektorer (som växer på slutet) i att anrop som conj inte är triviala att förstå då dess beteende beror av om det är en lista eller vektor man skickar in (jag använder denna skillnad i mitt program). Ett annat problem är att Clojure-program lätt kan skapa massor med "skräp" och massor med potentiellt dyra kopieringar av listor/vektorer då man går mellan dessa representationer (händer flera gånger i mitt program och är inte helt uppenbart var).

Type-hint kräver inte mellanrum, detta fungerar alldeles utmärkt

(defn foo[^int n](println n))

Däremot håller jag med om att det bör vara mellanrum, var bara väldigt ringrostig i Clojure då jag i praktiken helt slutat använt det (håller lite Lisp-takter igång tack vare elisp).

Type-hint visar för övrigt ett av (minst) två stora problem jag ser med dynamiskt typade språk: dessa finns för de ger en rejält prestandaboost, en boost som i princip alltid statiskt typade språk har över dynamiskt typade.

Det andra stora problemet är att problem är billigare/lättare att laga ju tidigare de upptäcks, statiskt typade språk upptäcker en rad buggar redan vi kompileringstillfället som i bästa fall hittas av automatiska tester eller vid kodgranskning, vilket då redan ökat kostnaden (i arbetstid) mångfalt.

Lite OT, men gillar att diskutera programmeringsrelaterade frågor

Det är bra att ha vektorer i syntaxen tycker jag, då ser man lätt skillnad på anrop och ickeanrop. Dessutom så kan man ju välja den datastruktur man vill ha i övrigt. Och det där med typehints ja sa förut: asså om man typhintar framför argumenten, utanför vektorn, så typehintar man returvärdet.

Permalänk
Datavetare
Skrivet av tufflax:

Det är bra att ha vektorer i syntaxen tycker jag, då ser man lätt skillnad på anrop och ickeanrop. Dessutom så kan man ju välja den datastruktur man vill ha i övrigt. Och det där med typehints ja sa förut: asså om man typhintar framför argumenten, utanför vektorn, så typehintar man returvärdet.

Då är jag med på vad du menade att man måste ha en mellanrum, men det stämmer inte då '^' inte är giltigt som del av ett namn, detta är därför helt OK

(defn foo^Integer[n] n)

vilket deklarerar en funktion med namnet 'foo' som ett returvärde av typen Integer

Håller med om att det finns fördelar med vektor, men det är trots allt en av raden av "prestanda-hack" som man stoppat in i Clojure. T.ex. så är detta konstant tid

([:a :b :c] 2) -> :c

vilket är syntax för att nå element 2 i en vektor. Problemet som man ändå måste ge rätt mycket cred till "Lisp-puritanerna" med detta är att den höga kostnaden att konvertera mellan lista och vektor, något som är väldigt lätt rent syntaktiskt -> folk kommer använda det. I Emacs-lisp versionen finns bara listor och i de fall jag använde vektorer i Clojure för det var naturligt att bygga mängden på slutet får man bygga listan åt "fel" håll och sedan vända på den.

Vilket är dyrast: konvertera mellan vektor <-> lista eller vända på en lista? Lisp är enkellänkade listor, "car" är data och "cdr" är resten av listan, d.v.s "next" pekaren. Båda operationerna har samma tidskomplexitet (O(N)), men det är betydligt billigare att vända på en lista då det inte innefattar minnesallokering + efterföljande skräpsamling av indata. En enkellänkad lista kan man enkelt och effektivt vända utan att kopiera något mer än "next" pekare.

Det är ett av flera problem med att använda Clojure i "riktiga" program, det är långsamt och använder typiskt betydligt mer RAM än motsvarande Java-program och Java är inte precis känt för att vara snål med RAM om man vill att det ska vara snabbt...

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem

Meckade ihop nått i Java
Blev ganska många klasser på de här viset (så det passar inte att klistra in i forumet) men de är är inte så mycket i varje.
Man får alltid svaren som decimal tal dvs 1.0 om man ville ha 1 men jag tror nog att det är okej!

https://bitbucket.org/litensherlock/calculator

Permalänk
Datavetare
Skrivet av relling:

Ambitiöst! Nu känner jag att minst en av mina varianter måste lyftas till att hantera +/- prefix på siffror samt funktion

Skrivet av liten1:

Meckade ihop nått i Java
Blev ganska många klasser på de här viset (så det passar inte att klistra in i forumet) men de är är inte så mycket i varje.
Man får alltid svaren som decimal tal dvs 1.0 om man ville ha 1 men jag tror nog att det är okej!

https://bitbucket.org/litensherlock/calculator

Helt rätt. De andra progblemet har typiskt resulterat i några få rader, detta var lite för stor för att bli överskådligt i forumet. Väldigt kul att se så många ändå ge sig på rätt kompletta lösningar. Själv tycker jag det är både kul och lärorikt att göra massa småprogram som jag redan från start vet att bara kommer bli throw-away. Men bättre att lära sig misstagen där än i riktiga produkter

Ska fixa ett bitbucket konto jag med.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Datavetare

Gick ganska enkelt att ändra min C-variant till att hantera unära operatorer samt variabelt antal argument till funktioner. "Tricket" är att sortera alla möjliga operatorer/funktioner och sedan konvertera inströmmen av text till ström av "tokens" där en operator-token är en pekare till operator strukturelementet.

1+2 blir översatt till (lite pseudokod kod)

{ {what = TOK_NUM, val = 1} {what = TOK_OP, op = { pointer to desc of operator+ } {what = TOK_NUM, val = 2} }

Järnvägsalgoritmen körs sedan på denna ström.

Uttryck som -1*-2 hanteras genom att '-' och '+' tillåts i positioner där tal tillåts och föregående token inte var ett nummer eller ')' då dessa saker måste följas av en binär operator.

Den är fortfarande utan explicit användning av dynamiskt minne och använder standard C funktionerna qsort samt bsearch för att mappa text mot matchande operator.

#define _GNU_SOURCE /* stdio.h -> getline() */ #include <ctype.h> #include <math.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> typedef double num_t; typedef enum { ASSOC_NONE, ASSOC_LEFT, ASSOC_RIGHT } assoc_t; typedef struct { char *repr; // String representation of the operator unsigned prio; // Evaluation priority of the operator assoc_t assoc; // Associativity of the operator unsigned num_args; // Number of arguments num_t(*apply)(num_t *args); // Apply function for the operator } operator_t; typedef enum { TOK_NUM, TOK_OP, TOK_LPAR, TOK_RPAR } token_type_t; typedef struct { token_type_t what; union { operator_t *op; num_t val; } d; } token_t; static num_t _add(num_t *args) { return args[0] + args[1]; } static num_t _sub(num_t *args) { return args[0] - args[1]; } static num_t _mul(num_t *args) { return args[0] * args[1]; } static num_t _div(num_t *args) { return args[0] / args[1]; } static num_t _pow(num_t *args) { return args[1] == 0 ? 1 : args[1]--, args[0] * _pow(args); } static num_t _abs(num_t *args) { return fabs(args[0]); } static num_t _max(num_t *args) { return args[0] > args[1] ? args[0] : args[1]; } static num_t _min(num_t *args) { return args[0] < args[1] ? args[0] : args[1]; } static num_t _sqrt(num_t *args) { return sqrt(args[0]); } static num_t _sin(num_t *args) { return sin(args[0]); } static num_t _cos(num_t *args) { return cos(args[0]); } static num_t _tan(num_t *args) { return tan(args[0]); } operator_t ops[] = { // { repr, prio, assoc, num_args, apply_fn } { "+", 0, ASSOC_LEFT, 2, _add }, { "-", 0, ASSOC_LEFT, 2, _sub }, { "*", 1, ASSOC_LEFT, 2, _mul }, { "/", 1, ASSOC_LEFT, 2, _div }, { "^", 2, ASSOC_LEFT, 2, _pow }, { "abs", 3, ASSOC_RIGHT, 1, _abs }, { "max", 3, ASSOC_RIGHT, 2, _max }, { "min", 3, ASSOC_RIGHT, 2, _min }, { "sqrt", 3, ASSOC_RIGHT, 1, _sqrt }, { "sin", 3, ASSOC_RIGHT, 1, _sin }, { "cos", 3, ASSOC_RIGHT, 1, _cos }, { "tan", 3, ASSOC_RIGHT, 1, _tan }, }; int cmp_key_op(const void *key, const void *obj) { const operator_t *op = obj; return strcmp(key, op->repr); } int cmp_ops(const void *a, const void *b) { const operator_t *op = a; return cmp_key_op(op->repr, b); } int to_tokens(token_t *tok, char *line, int cnt, bool unary_op_allowed) { char *str; token_type_t what; num_t val; if (*line == '\0') return cnt; if (isspace(*line)) return to_tokens(tok, line + 1, cnt, unary_op_allowed); str = line; if (*str == '(') { if (tok != NULL) { tok->what = TOK_LPAR; tok->d.op = NULL; } str++; unary_op_allowed = true; } else if (*str == ')') { if (tok != NULL) { tok->what = TOK_RPAR; tok->d.op = NULL; } str++; unary_op_allowed = false; } else if(isdigit(*str) || (unary_op_allowed && (*str == '+' || *str == '-'))) { num_t val = strtod(line, &str); if (tok != NULL) { tok->what = TOK_NUM; tok->d.val = val; } unary_op_allowed = false; } else { while (isalpha(*str) || (ispunct(*str) && *str != '(')) { str++; if (*str == '+' || *str == '-') break; } if (tok != NULL) { char ch = *str; *str = '\0'; tok->what = TOK_OP; tok->d.op = bsearch(line, ops, sizeof ops / sizeof ops[0], sizeof ops[0], cmp_key_op); *str = ch; } unary_op_allowed = true; } if (tok != NULL) tok++; return to_tokens(tok, str, cnt + 1, unary_op_allowed); } int shunting_yard(int num_tok, token_t *toks) { token_t stack_storage[num_tok]; token_t *empty_stack = stack_storage + num_tok; token_t *stack = empty_stack; token_t *out = toks; token_t *t; operator_t *o1; operator_t *o2; for (int i = 0; i < num_tok; i++) { switch (toks[i].what) { case TOK_NUM: *out++ = toks[i]; break; case TOK_LPAR: *--stack = toks[i]; break; case TOK_RPAR: while ((t = stack++)->what != TOK_LPAR) *out++ = *t; break; case TOK_OP: o1 = toks[i].d.op; while (stack != empty_stack) { o2 = stack->d.op; if (stack->what == TOK_LPAR || !((o1->assoc == ASSOC_RIGHT && o1->prio < o2->prio) || (o1->assoc != ASSOC_RIGHT && o1->prio <= o2->prio))) { break; } *out++ = *stack++; } *--stack = toks[i]; } } while (stack != empty_stack) { *out++ = *stack++; } return out - toks; } num_t eval_rpn(int num_tok, token_t *toks) { num_t stack_storage[num_tok]; num_t *stack = stack_storage; for (int i = 0; i < num_tok; i++) { if (toks[i].what == TOK_NUM) { *stack++ = toks[i].d.val; } else { operator_t *op = toks[i].d.op; num_t args[op->num_args]; memcpy(args, stack -= op->num_args, sizeof args); *stack++ = op->apply(args); } } return *--stack; } num_t calc(int num_tok, char *line) { token_t tokens[num_tok]; to_tokens(tokens, line, 0, true); return eval_rpn(shunting_yard(num_tok, tokens), tokens); } int main(int argc, char *argv[]) { char *line = NULL; size_t n; qsort(ops, sizeof ops / sizeof ops[0], sizeof ops[0], cmp_ops); while (getline(&line, &n, stdin) >= 0) { int num_tok = to_tokens(NULL, line, 0, true); printf("=%f\n", calc(num_tok, line)); } }

Dold text

Edit: Fixade pinsam bug...

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Datavetare
Skrivet av relling:

Snyggt med min och max, funderade på att lägga till det och summa operator, jag får se om jag gör det.

Jag tror du måste vända på argumenten, annars blir det i fel ordning, som det är nu så blir 1-2 = 1 och inte -1 som det bör vara.

Så i eval_rpn borde det nog vara:

for (int a = op->num_args - 1; a >= 0; a--) args[a] = *--stack;

Eller ändra ordningen i operatorerna kan man ju också göra förstås men blir kanske mindre logiskt.

Du har så rätt, gick lite fort där då jag vill få till detta innan jobbet drar igång idag... Helt uppenbart att jag glömde testa med subtraktion och division denna gång då detta fel upptäcks direkt då. Skrev om det så här i stället då jag ändå bemödat mig att skapa exakt rätt storlek av args på stacken

operator_t *op = toks[i].d.op; num_t args[op->num_args]; memcpy(args, stack -= op->num_args, sizeof args); *stack++ = op->apply(args);

Men det finns lite vårtor kvar, parsningen av max/min är lite lurig just nu måste man använda parenteser kring uttryck som inte bara består av tal.

# Detta borde fungera men blir fel max 1+2 3+4 # Detta fungerar max (1+2) (3+4)

Säkert rätt enkelt att fixa bara man sätter sig ner och letar.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer