11
\$\begingroup\$

Background

This challenge is based on a personal project I did, UnitDC. This challenge is a super-miniaturized version of UnitDC.

Challenge

Please write a simple unit-aware calculator that evaluates a sequence of positive integers, units and operators in RPN (postfix) notation, while checking whether the units are compatible with each other.

Tokens

Numbers

Numbers are all positive integers in the form "[0-9]+".

Units

Pops a value off the stack and set the unit of that quantity. For simplicity, units are represented by a single lower-cased letter. All letters are considered base units. Additionally it is guaranteed that the top of stack will have a quantity with no unit when the unit token is encountered.

Operators

For simplicity there are only three operators, +, * and /. All pop 2 values off the stack and push one back. It is guaranteed that the stack will not underflow.

  • For the + operator, you should check whether the two operands are of the same unit, if not you should report error.
  • For the * and / operator, you should update the units accordingly (sum up or subtract the exponents of rhs to lhs). All divisions are guaranteed to be whole.

I/O

You can choose take Input in any of the following forms, from STDIN, function arguments or a memory location:

  • A string of tokens, separated by space.
  • An array of token strings.

At the end of the evaluation, print to STDOUT or return as value the contents of the stack (top to bottom or bottom to top) in one of the following forms:

  • An array of quantities.
  • A string concatenation of quantities, separated by space.

where quantity is a string represented by the regex "^\d+([a-z](-?\d+))*$". The notation is simply the concatenation of the number, the letter for each unit and then its exponents. For example "1m2s-3" means \$1m^2s^{-3}\$. Valid representations include "1" "1g1" "1m1s-2". "1a0" is INVALID (no zero exponents). Ordering of the units are not important.

If an error is encountered during the evaluation, either output '*' and nothing else, or crash, or exit with a non zero code.

Examples

I: 1
O: 1

I: 1 s
O: 1s1

I: 1 s 2 m +
O: *

I: 1 m 1 s / 1 m 1 s / +
O: 2s-1m1

I: 1 s 2 s * 1 s
O: 2s2 1s1

I: 1 s 1 s /
O: 1 
Note: *1s0 is not acceptable here*

I: 1 m 1 s / 2 m 2 s / +
O: 2m1s-1

I: 1 s 1 s / 1 +
O: 2

I: 1 m 1 m / s
S: 1s1

I: 5 s 2 *
S: 10s1

I: 5 s 2 +
O: *

Reference Implementation (Rust)

This is a small reference implementation I made, this program accepts multiple lines of inputs but you only need to accept one input.

Try it online!

Related

Similar questions that may be helpful:

Scoring

This is code-golf, shortest code wins!

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Is 1 m 1 m / s valid? \$\endgroup\$
    – tsh
    Commented Mar 1 at 11:08
  • \$\begingroup\$ Ohhh, yes it should be valid since both values on the stack has no unit, I will update that on the question! thanks for pointing that out \$\endgroup\$ Commented Mar 1 at 14:26
  • \$\begingroup\$ Reference Implementation in Scala, fails on one case. Any help would be appreciated. Attempt This Online! \$\endgroup\$
    – 138 Aspen
    Commented Mar 3 at 11:08
  • \$\begingroup\$ Your example output 1g does not meet the requirement of regex. Do you mean 1g1 or do you mean \d* in regex? \$\endgroup\$
    – tsh
    Commented Mar 5 at 2:59

6 Answers 6

6
\$\begingroup\$

K (ngn/k), 134 124 117 bytes

{,//$x,(!y),'.y}.'|(){(,$[43=*y-*|&/=/h;E;1=C;**x;^[_%;.y].|*'h],(&~b)_b:(y!C;*|(1-3!*y)/h:C#x)2=C),(C:3!"0a"'*y)_x}/

Try it online! Input and output as list of strings, t converts between that and space separated format.

\$\endgroup\$
3
\$\begingroup\$

JavaScript (Node.js), 797 795 bytes

Z=require('util');O=Object;N="length";A=u=>O.fromEntries([...u.matchAll(/([a-zA-Z])(-?\d+)/g)].map(_=> _.slice(1)));B=u=>{s="";e=O.entries(u);for(i=0;i<e[N];i++){s+=e[i][0]+e[i][1]}return s};D=T=>{S=[];U=[];p=/[+*\/]/;for(t of T){if(/\d+/.test(t[0])||/\d+/.test(t[1])&&(t[0]=="-"||t[0]=="+")){S.push(t)}else if(p.test(t)){let[r,l]=[S.pop(),S.pop()];[h,H]=[U.pop(),U.pop()];if(H){f=A(h);g=A(H);m=1;if(t=="+"){if(Z.isDeepStrictEqual(g,f)){n=B(g)}else{return'*'}}if(t=="/"){m=-1}L=O.keys(g);P=O.keys(f);z={};for(i=0;i<L[N];i++){k=L[i];if(k in f){y=+g[k]+m*+f[k];y!=0?(z[k]=y):0}else{z[k]=+g[k]}}for(i=0;i<P[N];i++){k=P[i];if(!(k in z||k in g)){z[k]=m*+f[k]}}n=B(z);n!=""?U.push(n):0}S.push(""+eval(l+t+r))}else{if(t[N] > 1){U.push(t)}else{U.push(t+1)}}}return S.map((E,I)=> E+(U[I]||"")).join(" ")}

Try it online! Input is as an array of token strings passed to D. Output is to STDOUT. There's probably a shorter way, but this is what I've got (and JS is not a golfing language).

\$\endgroup\$
1
  • \$\begingroup\$ Actually, JS is one of the better languages for code-golf if you're just talking about those which are also used outside of it. \$\endgroup\$ Commented Mar 5 at 11:54
3
\$\begingroup\$

JavaScript (Node.js), 209 bytes

p=>p.map(n=>a.push(y=+n?[+n,...Buffer(36)]:n<0+a.pop()?a.pop().map((o,i)=>(m=y[i],i)?n<'+'?o+m:n<'/'?o-m?E:o:o-m:eval(o+n+m)):(y[parseInt(n,36)]=1,y)),a=[])&&a.map(x=>x.reduce((x,v,i)=>v?x+i.toString(36)+v:x))

Attempt This Online!

Input array of tokens, output array of values or throw error.

\$\endgroup\$
2
\$\begingroup\$

JavaScript (ES6), 263 bytes

s=>s.replace(/\S+/g,v=>v>{}?s[0][1]=[[v,1]]:s=[[(u=[])|v||([[y,q],[x,u],...s]=s,v+1|0?e|=u+''!=q:q.map(([U,e])=>(u.find(o=>o[0]==U)||u.push(o=[U,0])&&o)[1]+=v<"/"?e:-e),eval(x+v+y)),u.filter(o=>o[1]).sort()],...s],e=s=[])|e?"*":s.reverse().join` `.split`,`.join``

Try it online!

\$\endgroup\$
2
\$\begingroup\$

Uiua, 204 192 178 153 characters

-12: remove error handling and output reversal and redundant spaces that the formatter always puts back in.
-14: use "uiuisms" page to golf character range, use content ◇ instead of unbox, and golf creation of the stack.
-25: Get less stingy with memory usage, etc.

P←⊙:⊙⊙°⊂∩°⊂°⊂◌
r←⇡27
a←⍜°⊂(@ ◌)+@`r
i←⊙(⟨⊂⊂⊓×+P|⊂⊂⊓+⟜(⍤"*"≍)P|◌|◌|◌|⊂⊂⊓÷-P|⟨⍜⊙°⊂+=a|⊂×=0r⋕⟩⍣(1◌⋕)0.⟩↧6/+-@*.):°□°⊂ 
(↘1/◇⊂♭≡(⨬(□""◌◌|□⊂⊙°⋕)≠0,a)◌⍥i⧻.:↯0r)

commented version with tests golfed version

P ← ⊙:⊙⊙°⊂∩°⊂°⊂ # pop 2 from small stack and separate number and units
r ← ⇡27
a ← ⍜°⊂(@ ◌)+@`r
i ← ⊙(
  ⨬(
    ⊂⊂⊓×+P◌        # times
  | ⊂⊂⊓+⟜(⍤"*"≍)P◌ # plus
  | ◌
  | ◌
  | ◌       # other characters
  | ⊂⊂⊓÷-P◌ # divided by
  | ⨬(
      ⍜⊙°⊂+=a # letter: add one to corresponding cell in top of small stack
    | ⊂×=0r⋕  # number: push unitless number to small stack
    )⍣(1◌⋕)0.
  )↧6/+-@*. # ---the difference from star, or 6 if it's a number or letter
):°□°⊂      # process one token from the input 
f ← (
  ◌⍥i⧻.:↯0r             # setup the label and small stack
  ≡(⨬(□""◌◌|□⊂⊙°⋕)≠0,a) # append unit to each number in the small stack
  ↘1/◇⊂♭                # final concatenation
)

Input is a boxed array ({}) of token strings, or there is an extra string parsing function in the formatted version link.

\$\endgroup\$
1
\$\begingroup\$

Charcoal, 123 116 bytes

F⪪S ¿Σι⊞υ⁺⟦Iι⟧Eβ⁰¿№βι§≔§υ±¹⊕⌕βι¹«≔⊟υζ≔⊟υη⊞υ∧∨⬤∧ζη∨¬λ⁼κ§ζλ⁻+ιE∧ζη⎇λ⁻κקζλ⊖﹪℅鳧⟦×κ§ζλ⁺κ§ζλ÷κ§ζλ⟧℅ι»Eυ⭆∨ι*⎇μ⎇λ⁺§β⊖μλωλ

Try it online! Link is to verbose version of code. Since the whole stack is output, I've effectively included several test cases in the link. Explanation:

F⪪S 

Split the input on spaces and loop over the resulting tokens.

¿Σι⊞υ⁺⟦Iι⟧Eβ⁰

If this token is a positive integer, then push its value with zero exponents for all letters.

¿№βι§≔§υ±¹⊕⌕βι¹

Otherwise, if this token is a letter, then set the exponent for that letter to 1 for the latest value.

«

Otherwise:

≔⊟υζ≔⊟υη

Retrieve the two arguments.

⊞υ∧∨⬤∧ζη∨¬λ⁼κ§ζλ⁻+ιE∧ζη⎇λ⁻κקζλ⊖﹪℅鳧⟦×κ§ζλ⁺κ§ζλ÷κ§ζλ⟧℅ι

If the arguments are compatible or the operations is not sum, calculate the product, sum and quotient of the two arguments, selecting according to the ordinal of the token. (In the case of the exponents, the ordinal modulo 3 is decremented and multiplied by the second exponent, and this is subtracted from the first exponent.)

»Eυ⭆∨ι*⎇μ⎇λ⁺§β⊖μλωλ

For each value on the stack, concatenate its value with any non-zero exponents, unless the value was invalid, in which case output a *.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.