Help! My calculator broke! (Turn integer expression into calculator keystrokes)
Python 3. 337 327 - 10% = 295 bytes
Supports floating-point and unlimited levels of parentheses, so qualifies for bonus -10%.
import re
def v(s):
if s[:1]=='(':l,s=e(s[1:]);return l,s[1:]
m=re.match(r"\d+\.?\d*|\.\d+",s)
if m:return[m.group()],s[m.end():]
def b(s,f,*O):
l,s=f(s)
while s[:1]in O:
o=s[0]
r,s=f(s[1:])
if len(r)>1:l,r=r,l
l+=[o]+r
return l,s
m=lambda s:b(s,v,'*','/')
e=lambda s:b(s,m,'+','-')
print(' '.join(e(input())[0]))
TI-BASIC, 605.2 bytes
Eligible for lirtosiast's TI-BASIC bounty under Fitting but Unsuitable Languages.
Qualifies for all 3 bonuses, 712 - 15% = 605.2
. There are some golfing opportunities here and there, but I wanted to get this out first, as some of the potential golfs are non-trivial. Please note that TI-BASIC is a tokenized language, and the below is a textual representation of that program. Thus, the program is not 1182 bytes, since this program is not encoded under UTF-8. Note that ~
is equivalent to unary negation and ->
to the STO>
operator. Output is a string, retrievable from Ans
or Str1
.
The below program is the result of a few programmer hours thinking and programming, spread over the course of a few weeks.
Input "",Str1
0->dim(L1
0->dim(L2
1->I
DelVar S
While I<length(Str1
DelVar T
".->Str3
1->C
While C and I<length(Str1
sub(Str1,I,1->Str2
inString("0123456789.",Str2->C
If C:Then
I+1->I
1->T
Str3+Str2->Str3
End
End
If T=0:Str3+Str2->Str3
sub(Str3,2,length(Str3)-1->Str3
If T=1:Then
expr(Str3->L2(1+dim(L2
End
inString("*+/-",Str3->M
If M:Then
I+1->I
2->T
1->C
While C
dim(L1->C
If C:Then
2fPart(L1(dim(L1))/2)>2fPart(M/2->C
If C:Then
~L1(dim(L1->L2(1+dim(L2
dim(L1)-1->dim(L1
End
End
End
M->L1(1+dim(L1
End
If Str3="(":Then
If S=1:Then
sub(Str1,1,I-1)+"*"+sub(Str1,I,length(Str1)-I+1)->Str1
Else
I+1->I
0->L1(dim(L1)+1
End
End
If Str3=")":Then
I+1->I
While L1(dim(L1
~L1(dim(L1->L2(1+dim(L2
dim(L1)-1->dim(L1
End
dim(L1)-1->dim(L1
End
T->S
End
augment(L2,-seq(L1(X),X,dim(L1),1,-1)->L2
0->dim(L1
1->C
{0,1->L3
For(I,1,dim(L2
L2(I->M
If M>=0:Then
M->L1(dim(L1)+1
Else
If C:Then
L1(dim(L1)-1
Goto S
Lbl A
Ans->Str1
L1(dim(L1->L1(dim(L1)-1
dim(L1)-1->dim(L1
0->C
End
Str1+" "+sub("*+/-",-M,1)+" ->Str1
L1(dim(L1
Goto S
Lbl B
Str1+Ans->Str1
dim(L1)-1->dim(L1
End
End
Goto F
Lbl S
L3Ans->L4
LinReg(ax+b) L3,L4,Y1
Equ►String(Y1,Str2
sub(Str2,1,length(Str2)-3
If C:Goto A
Goto B
Lbl F
Str1
General explanation
Here's the key I worked with while developing the program:
Str1 The input string.
Str2 Counter variable (e.g. current character)
Str3 The current token being built.
L1 The operator stack
L2 The output queue
L3 Temporary list
L4 Temporary list
I Iterator index
T Type of token (enum)
S Type of previous token (enum)
M Temporary variable
C Conditional variable
Token Types (T)
0 Undefined
1 Number
2 Operator
3 Open Parenthesis
Operator Elements
0 left parenthesis ("(")
1 multiplication ("*")
2 addition ("+")
3 division ("/")
4 subtraction ("-")
5 right parenthesis (")")
Precedence Rule: Remainder(prec, levelno)
0 - add, sub
1 - mul, div
(levelno = 2)
And here is the equivalent, hand-written JavaScript code I used to test and develop this program.
let Str1, Str2, Str3, L1, L2, I, S, T, M, C;
let error = (type, ...args) => {
let message = "ERR:" + type + " (" + args.join("; ") + ")";
throw new Error(message);
};
let isInteger = (n) => n == Math.floor(n);
let inString = (haystack, needle, start=1) => {
if(start < 1 || !isInteger(start)) {
error("DOMAIN", haystacak, needle, start);
}
let index = haystack.indexOf(needle, start - 1);
return index + 1;
};
let sub = (string, start, length) => {
if(start < 1 || length < 1 || !isInteger(start) || !isInteger(length)) {
error("DOMAIN", string, start, length);
}
if(start + length > string.length + 1) {
error("INVALID DIM", string, start, length);
}
return string.substr(start - 1, length);
}
let fPart = (value) => value - Math.floor(value);
// Input "", Str1
Str1 = process.argv[2];
// 0->dim(L1
L1 = [];
// 0->dim(L2
L2 = [];
// 1->I
I = 1;
// DelVar S
S = 0;
// While I<=length(Str1
while(I <= Str1.length) {
// DelVar T
T = 0;
// Disp "Starting",I
console.log("Starting, I =", I);
// " read token
// ".->Str3
Str3 = ".";
// 1->C
C = 1;
// While C and I<=length(Str1
while(C && I <= Str1.length) {
// sub(Str1,I,1->Str2
Str2 = sub(Str1, I, 1);
// inString("0123456789",Str2->C
C = inString("0123456789", Str2);
// If C:Then
if(C) {
// I+1->I
I++;
// 1->T
T = 1;
// Str3+Str2->Str3
Str3 += Str2;
}
}
// If T=0:
if(T == 0) {
// console.log("Huh?T=0?", Str3, Str2);
// Str3+Str2->Str3
Str3 += Str2;
}
// " remove placeholder character
// sub(Str3,2,length(Str3)-1->Str3
Str3 = sub(Str3, 2, Str3.length - 1);
// " number
// If T=1:Then
if(T == 1) {
// expr(Str3->L2(1+dim(L2
L2[L2.length] = eval(Str3);
}
// Disp "Str3",Str3
console.log("post processing, Str3 = \"" + Str3 + "\"");
// inString("*+/-",Str3->M
M = inString("*+/-", Str3);
// " operator
// If M:Then
if(M) {
// I+1->I
I++;
// 2->T
T = 2;
// Disp "op",M,dim(L1
console.log("op", M, L1.length);
// " parse previous operators
// 1->C
C = 1;
// While C
while(C) {
// dim(L1->C
C = L1.length;
// If C:Then
if(C) {
// 2fPart(L1(dim(L1))/2)>2fPart(M/2->C
C = 2 * fPart(L1[L1.length - 1] / 2) > 2 * fPart(M / 2);
// If C:Then
if(C) {
// ~L1(dim(L1->L2(1+dim(L2
L2[L2.length] = -L1[L1.length - 1];
// dim(L1)-1->dim(L1
L1.length--;
}
}
}
// " push current operator
// M->L1(1+dim(L1
L1[L1.length] = M;
}
// If Str3="(":Then
if(Str3 == "(") {
// 3->T
T = 3;
// If S=1:Then
if(S == 1) {
// sub(Str1,1,I-1)+"*"+sub(Str1,I,length(Str1)-I+1)->Str1
Str1 = sub(Str1, 1, I - 1) + "*" + sub(Str1, I, Str1.length - I + 1);
}
// Else
else {
// I+1->I
I++;
// 0->L1(dim(L1)+1
L1[L1.length] = 0;
}
// End
}
// If Str3=")":Then
if(Str3 == ")") {
// I+1->I
I++;
// While L1(dim(L1
while(L1[L1.length - 1]) {
// ~L1(dim(L1->L2(1+dim(L2
L2[L2.length] = -L1[L1.length - 1];
// dim(L1)-1->dim(L1
L1.length--;
}
// End
// dim(L1)-1->dim(L1
L1.length--;
}
// Disp "Ending",I
console.log("Ending", I);
// T->S
S = T;
// Pause
console.log("-".repeat(40));
}
// augment(L2,-seq(L1(X),X,dim(L1),1,-1)->L2
L2 = L2.concat(L1.map(e => -e).reverse());
// Disp L1, L2
console.log("L1", L1);
console.log("..", "[ " + L1.map(e=>"*+/-"[e-1]).join`, ` + " ]");
console.log("L2", L2);
console.log("..", "[ " + L2.map(e=>e<0?"*+/-"[~e]:e).join`, ` + " ]");
// post-processing
let res = "";
// 0->dim(L1
L1.length = 0;
// 1->C
C = 1;
// For(I,1,dim(L2
for(I = 1; I <= L2.length; I++) {
// L2(I->M
M = L2[I - 1];
// If M>=0:Then
if(M >= 0) {
// M->L1(dim(L1)+1
L1[L1.length] = M;
}
// Else
else {
// If C:Then
if(C) {
// L1(dim(L1)-1
// Goto ST
// Lbl A0
// Ans->Str1
res += L1[L1.length - 2];
// L1(dim(L1->L1(dim(L1)-1
L1[L1.length - 2] = L1[L1.length - 1];
// dim(L1)-1->dim(L1
L1.length--;
// 0->C
C = 0;
}
// End
// Str1+" "+sub("*+/-",-M,1)+" ->Str1
res += " " + "*+/-"[-M - 1] + " ";
// L1(dim(L1
// Goto ST
// Lbl A1
// Str1+Ans->Str1
res += L1[L1.length - 1];
// dim(L1)-1->dim(L1
L1.length--;
}
}
// Goto EF
// Lbl ST
// L3Ans->L4
// LinReg(ax+b) L3,L4,Y1
// Equ►String(Y1,Str2
// sub(Str2,1,length(Str2)-3
// If C:Goto A0
// Goto A1
// Lbl EF
// Str1
console.log(res);
I will provide a more in-depth explanation once I'm sure I'm done golfing, but in the meantime, this might help provide a cursory understanding of the code.
Javascript (ES6), 535 - 80 (15% bonus) = 455 bytes
f=z=>{a=[],i=0;c=n=>{while(n[M='match'](/\(/)){n=n[R='replace'](/\(([^()]+)\)/g,(q,p)=>{m=++i;a[m]=c(p);return'['+m+']'})}n=n[R](/(\](?=\[|[\d])|[\d](?=\[))/g,'$1*');n=n[R](/\[?[\d\.]+\]?[*/]\[?[\d\.]+\]?/g,q=>{a[++i]=q;return'['+i+']'});n=n[R](/([\d.]+)\+(\[[\d]+\])/g,'$2+$1');while(n[M](/\[/)){n=n[R](/(\[?[\d\.]+\]?)\*(\[?[\d\.]+\]?)/g,(q,p,r)=>{t=a[r[R](/[\[\]]/g,'')];return r[M](/\[/)?(t&&t[M](/\+|\-/)?(r+'*'+p):q):q});n=n[R](/\[(\d+)\]/g,(q,p)=>{return a[p]+(a[p][M](/\+|-/)?'=':'')})}return n};return c(z)[R](/./g,'$& ')+'='}
Not a minimal solution I'm sure, but pretty complete, allowing for all three bonuses. Some instances require multiple pressings of equals key, but do not require clearing of calculator contents. (ex. 3,5,6 & 7 in fiddle)
Link to JSFiddle with some tests: https://jsfiddle.net/2v8rkysp/3/
Here's some unfolded, semi-unobfuscated code with a few comments for good measure.
function f(z) {
var a=[],i=0;
function c(n) {
//// Tokenize parentheses groups recursively
while (n.match(/\(/)) {
n = n.replace(/\(([^()]+)\)/g, function(q,p) {
m = ++i;
a[m]=c(p);
return '['+m+']';
});
}
//// Allow implied multiplication with parentheses
n = n.replace(/(\](?=\[|[\d])|[\d](?=\[))/g, '$1*');
//// Tokenize mult/division
n = n.replace(/\[?[\d\.]+\]?[*\/]\[?[\d\.]+\]?/g, function(q) {
a[++i]=q;
return '['+i+']';
});
//// Move addition tokens to the front
n = n.replace(/([\d.]+)\+(\[[\d]+\])/g,'$2+$1');
//// Detokenize
while (n.match(/\[/)) {
//// If a token includes addition or subtraction,
//// move it to the front of other tokens
n = n.replace(/(\[?[\d\.]+\]?)\*(\[?[\d\.]+\]?)/g,function(q,p,r) {
t=a[r.replace(/[\[\]]/g,'')];
return r.match(/\[/)?(t&&t.match(/\+|\-/)?(r+'*'+p):q):q;
});
//// If a token includes addition or subtraction,
//// add the equals keypress
n = n.replace(/\[(\d+)\]/g, function(q,p) {
return a[p]+(a[p].match(/\+|-/)?'=':'');
});
}
return n;
}
//// Add spaces and final equals keypress
return c(z).replace(/./g, '$& ')+'=';
}