Let's do some location arithmetic!

From the Wikipedia article:

Location arithmetic (Latin arithmeticæ localis) is the additive (non-positional) binary numeral systems, which John Napier explored as a computation technique in his treatise Rabdology (1617), both symbolically and on a chessboard-like grid.

What?

Location numerals is a way of writing numbers using letters of the alphabet.

Binary notation had not yet been standardized, so Napier used what he called location numerals to represent binary numbers. Napier's system uses sign-value notation to represent numbers; it uses successive letters from the English alphabet to represent successive powers of two: a = 2^0 = 1, b = 2^1 = 2, c = 2^2 = 4, d = 2^3 = 8, e = 2^4 = 16 and so on.

An example

ab = 1+2 = 3 in base 10

aabb = 1+1+2+2 = 6 in base 10

Note that aabb can be shortened to bc by replacing any 2 instances of a letter by a higher one.

You just concatenate the two numbers and simplify.

acd + bde = acdbde = abcdde = acebe = abcf = 39 in base 10

Subtraction

Just remove all digits appearing equally in both parts of the subtraction. Expanding (converting b to aa) may be necessary

abde- ad = be = 18 in base 10

Multiplication

This is a bit harder.

Lets say we want to multiply acd (13) by def (56). First you arrange acd vertically:

a
c
d


Then you add def after the first a:

a def
c
d


Now, c is 2 positions later in the alphabet than a, so we add 2 positions in the alphabet to def to make fgh. That is added to the second row.

a def
c fgh
d


Lastly, d is 1 position later in the alphabet than c, so we add 1 position in the alphabet to fgh to make ghi. That is added to the third row.

a def
c fgh
d ghi


Then you take the sum of the right: def + fgh + ghi = deffgghhi = deggghhi = deghhhi = deghii = deghj (728)

Another example of multiplication

Input:

bc * de


First:

b
c


Then

b ef
c


Then

b ef
c fg


Note that we wrote down ef on the first line. That's because bc starts with b, and b is the second letter in the alphabet, so we need to shift de by 1 letter, so it becomes ef.

Then

ef+fg


Output:

eh


Division

This is not part of this challenge, because it can get very complex.

Your program or function must take input as a string that looks like this:

a + b


And you must output:

ab


Of course, your program or function must support numbers of arbitrary length (up to the string or input limit of your language) with any of the operators +, -, or *. Some more examples:

Input:

ab + bd


Output:

acd


Input:

d - ab


Output:

ac


Input:

ab * cd


Output:

cf


Notes:

• The order of letters in the output doesn't matter, but you can always assume that the order of letters in numbers in the input will be ascending (a before z).
• You may take input with a trailing newline and output with a trailing newline.
• You may not take input as a list of ab, * and bd for ab * bd.
• The english alphabet is used (abcdefghijklmnopqrstuvwxyz)
• Your output must be simplified (aa is not allowed, b is required)
• The input will be simplified (b + c, not aa + bb or aa + aaaa)
• You may require a space before and the operator (+, -, or *), or you may require there to be none.
• There will only be one operator per input.
• You may assume that the output and the input will never go over 2^27-1 (abcdefghijklmnopqrstuvwxyz)
• This is , so the shortest answer in bytes wins!
• d is 2 positions later in the alphabet than c is this wright? shouldn't it be 1? That is added to the second row. on the same sentence, shouldn't it be third? – Felipe Nardi Batista Apr 10 '17 at 14:28
• @FelipeNardiBatista the english alphabet is used here, edited that. – programmer5000 Apr 10 '17 at 14:29
• @programmer5000 still, bc*de==efgh but efgh is 240 not 144 – Felipe Nardi Batista Apr 10 '17 at 14:52
• bc*de should be eh – Felipe Nardi Batista Apr 10 '17 at 14:55
• @Dada there will only be one operator per input. – programmer5000 Apr 10 '17 at 14:57

Jelly, 26 25 bytes

i@€Øað’2*S;ḟ.Ḣ
ḲÇ€VBṚTịØa


Uses Jelly's operators (× rather than * and _ rather than -) in the input string as allowed by the OP.

(Requires spaces around the operators)

Try it online! or see the test suite

How?

i@€Øað’2*S;ḟ.Ḣ - Link 1, transform from input sub-string to value or operator: sub-string
i@€            - 1st index of, for €ach (or 0 if not found) [reversed @rguments] in:
Øa          -      lowercase alphabet (i.e. a->1, b->2, ..., non-alpha->0)
ð         - dyadic chain separation i.e. f(result above, substring):
’        - decrement (i.e a->0, b->1, ..., non-alpha->-1)
2*      - 2 raised to that power
S     - sum
;    - concatenate with the substring
ḟ   - filter out:
.  -     0.5 (for an operator substring the evaluated 0.5 is removed)
Ḣ - head (i.e. the evaluation for a location, and the operator otherwise)

ḲÇ€VBṚTịØa - Main link: string                        e.g. 'ab × cd'
Ḳ          - split on spaces                               [['a','b'],['×'],['c','d']]
Ç€        - last link (1) as a monadic function for €ach  [3,'×',12]
V       - evaluate as Jelly code                        36
B      - convert to binary                             [1,0,0,1,0,0]
Ṛ     - reverse                                       [0,0,1,0,0,1]
T    - truthy indexes                                [3,6]
ị   - index into:
Øa -     lowercase alphabet                        ['c','f'] (i.e. "cf", which is implicitly printed when run as a full program)


Mathematica, 168 bytes

FixedPoint[StringReplace[x_~~x_:>FromCharacterCode[c@x+1]],Table["a",ToExpression@StringReplace[#,x:LetterCharacter..:>ToString@Tr[2^((c=ToCharacterCode)@x-97)]]]<>""]&


My initial solution (before the post was edited to clarify that the output must be simplified) was 64 bytes shorter:

Table["a",ToExpression@StringReplace[#,x:LetterCharacter..:>ToString@Tr[2^(ToCharacterCode@x-97)]]]<>""


This just modified that solution to work. It's probably shorter to actually use the methods described in the challenge, but I wanted to put this up anyway.

Explanation:

Replaces each sequence of letters with its corresponding integer by character code arithmetic, then converts the resulting string to an expression (which will automatically simplify to an integer), then produces a string of a characters of length equal to that integer, and finally replaces adjacent identical characters with the next character code up until a fixed point is reached.

• Oh, there's no 1-char builtin? Suprising! – programmer5000 Apr 10 '17 at 17:56

JavaScript (ES6), 136134 133 bytes

Saved 1 byte thanks to Luke

s=>[...a='abcdefghijklmnopqrstuvwxyz'].filter((c,i)=>eval(s.replace(/\w+/g,s=>[...s].reduce((p,c)=>p|1<<a.search(c),0)))&1<<i).join


Test cases

let f =

s=>[...a='abcdefghijklmnopqrstuvwxyz'].filter((c,i)=>eval(s.replace(/\w+/g,s=>[...s].reduce((p,c)=>p|1<<a.search(c),0)))&1<<i).join

console.log(f('ab + bd'));  // acd
console.log(f('d - ab'));   // ac
console.log(f('ab * cd'));  // cf

• Nicely done! You beat me to it... – programmer5000 Apr 10 '17 at 14:51
• Does this convert to decimal and back? It appears so. – programmer5000 Apr 10 '17 at 15:09
• @programmer5000 Yes indeed. I suspect that many answers will. (Except of course Mathematica, which probably has a built-in for it. ^^) – Arnauld Apr 10 '17 at 15:15
• It looks like your comment was missing a link. What has a built-in fot it? – programmer5000 Apr 10 '17 at 15:16
• @programmer5000 (In fact, it was missing a word.) – Arnauld Apr 10 '17 at 15:20

Perl 5, 95 bytes

94 bytes of code + -p flag.

s/\w/a x 2**(-97+ord$&)/ge;s/(.*)-\1|\+//;/\*/&&($_=$x length$');1while s/(.)\1/chr 1+ord$1/e  Try it online! Three steps here: - s/\w/a x 2**(-97+ord$&)/ge; converts the input into a string of a only.
- s/(.*)-\1|+//;/*/&&($_=$x length$') will execute the operator (that are very simple on strings of a): + is the concatenation, - means removing from the first part as many a as there are in the second part, and * means duplicating the first part as many times as there are a in the second part. - 1while s/(.)\1/chr 1+ord$1/e folds the consecutive same letters into the next letter in the alphabet.

• The only answer that doesn't convert to decimal! Nice job! – programmer5000 Apr 10 '17 at 15:11
• @programmer5000 Out of 2 answers, I wouldn't call that impressive! – Dada Apr 10 '17 at 15:12

05AB1E, 29 bytes

ð¡À¬U¦v0yvAyko+}}X.VbRvyiANèJ


Try it online! or as a Test suite

Explanation

ð¡                             # split input on string
À                            # rotate left
¬U¦                         # get the operator, store it in X and remove it from list
v                        # for each side of the equation
0                       # push 0 as an accumulator
yv                     # for each letter in each side of the equation
Ayk                  # get its index in the alphabet
o                 # raise 2 to this power
+                # add to the accumulator
}}              # end loops
X.V           # apply the operator to the 2 numbers now on the stack
bR         # convert to binary and reverse
v        # for each binary digit
yi      # if it is true
ANè   # get the letter at that index in the alphabet
J  # join stack to a single string


C & x86 asm, 340 Bytes

Compile with -O0

#define G getchar()
g(){int c,a=0;for(;islower(c=G);)a+=1<<(c-97);return a;}
main(){short o[]={[43]=0x4403,[45]=0x442b,[42]=0x6cf7};
mprotect((long)&&l&~4095,4096,7);
for(;;){int c,b=0,a=g();*(short*)&&l=o[G];G;g();asm("xchg %%eax,%0":"+m"(a));
for(;a=c>>b;b++)if(a&=1)putchar(97+b);putchar(10);}}


Explanation

Since C doesn't have eval(), I used a table of x86 instructions in its place. I had to choose instructions which were all the same length (or padded with nops), and which expected src and destination of the same types. Of particular annoyance was that MUL can only write to registers, and the 1-byte MUL opcodes can only write to EAX. Additionally, there seemed to be no register-writing SUB instruction which subtracted from memory, instead of the other way around, hence the XCHG.

edit

#define G getchar()
#define return r
#define int i
g(){i c,a=0;for(;islower(c=G);)a+=1<<(c-97);r a;}
a(i x,i y){r x+y;}s(i x,i y){r x-y;}m(i x,i y){r x*y;}
main(){i(*o[])(i,i)={[43]=a,[45]=s,[42]=m};
for(;;){i c,b,a=g();b=G;G;g();c=o[b](a,g());
for(b=0;a=c>>b;b++)if(a&=1)putchar(97+b);putchar(10);}}


It's actually a bit shorter, at 301 chars, for a few reasons: 1. Because there need to be a lot of functions, the overhead of each can be chopped with some preprocessor rules. 2. Modern linux protects from execution on the stack, so the mprotect() call to disable this sacrificed 34 bytes. 3. The XCHG call is very sub-optimal, costing another 30 bytes. If not for those things, the x86 combo would win by about 10-20 bytes.

Also chopped 2 bytes from both by improving the islower() call in g.

• I can't really tell how it would compare to a more classical approach in terms of code-size, but I really like your solution. +1 – Arnauld Apr 12 '17 at 16:21

GNU sed + coreutils, 329 bytes

Yeah, I have no idea what got into me, but at least I know sed scripting a bit better now. Note that this solution requires GNU sed's e extension, which runs a shell command.

/\+/{s/\+//
b S}
/-/{:E
/a+-a+/{s/(a*)(a*)-\2/\1/
b S}
s/.*/echo &|tr b-z- A-Y-/
e
s/([A-Z])/\L\1\1/g
b E}
/\*/{h
:M
/^\*/{x
s/[^\n]*//
s/\n//g
b S}
s/(.).*\*(.*)/echo \2|tr a-z \1-za-z/
e
H
g
s/.(.*)/\1/
h
s/\n.*//
b M}
:S
s/^.*$/echo &|grep -o .|sort|tr -d '\n'/ e :L s/(.)\1/\u\1/g /^[a-z]*$/ q
s/.*/echo &|tr A-Z b-za/;e
b L


I assume that there will not be spaces around the operators. From my terminal:

$sed -rf golf.sed <<< a+b ab$ sed -rf golf.sed <<< ab+bd
acd
$sed -rf golf.sed <<< abc+b ad$ sed -rf golf.sed <<< d-ab
ca
$sed -rf golf.sed <<< ab*cd cf$ sed -rf golf.sed <<< bc*de
eh
$sed -rf golf.sed <<< acd*def deghj  And, for those saner than I: the commented version! #!/bin/sed -rf /\+/ { s/\+// b simplify } /-/ { # expand pattern space; everything will now be 'a's :E /a+-a+/{ # Remove doubled 'a's on either side of the dash. For example, # for input d-ab, space is now 'aaaa-aaa'; substitute this to 'a' s/(a*)(a*)-\2/\1/ b simplify } # shift letters that aren't 'a' down and double them s/.*/echo &|tr b-z- A-Y-/;e s/([A-Z])/\L\1\1/g b E } /\*/ { # Hold space: line 1 is pattern, other lines are output h :M # if space starts with *, we've eaten entire arg0; sum and simplify /^\*/ { x s/[^\n]*// # remove first line, which is our pattern s/\n//g # remove newlines to add results together b simplify } # convert pattern into shifting command s/(.).*\*(.*)/echo \2|tr a-z \1-za-z/ # execute it, append result to hold space e H # restore pattern, with leading char and all output lines removed g s/.(.*)/\1/ h s/\n.*// b M } :simplify # reorder all letters so all 'a's are before all 'b's are before all 'c's # are before ... etc # See https://stackoverflow.com/questions/2373874 s/^.*$/echo &|grep -o .|sort|tr -d '\n'/
e

:L
# Replace repeated characters with themselves upper-cased, then translate
# upper-cased characters to what they should be.
s/(.)\1/\u\1/g
/^[a-z]*$/ q s/.*/echo &|tr A-Z b-za/;e b L  • +1 for the sed code and welcome to PPCG! The convention here when not solving in pure GNU sed (or in any other pure language), is to add to the title the system commands used, like "GNU sed + coreutils" for example, even if you mention calling a shell command in description. This is done to differentiate, especially in challenges with leader-boards, from pure GNU sed answers. – seshoumara Apr 12 '17 at 7:18 • Also, except for the flag 'f' needed every time, any other flag must be counted as 1 byte. So your score is 329. You might want to mention that in description. And for completion, you might think of adding a link to an online sed interpreter, like TIO. – seshoumara Apr 12 '17 at 7:30 • To not be all talk and no action, here is a 43 bytes shorter! version of your code (286 bytes including -r), that I found by golfing the commands. I'm sure it can be even shorter. – seshoumara Apr 12 '17 at 8:56 • Ah, alright, good to know! Also, nice golfing! What version of sed are you using, though? Yours works in TIO, but in GNU sed 4.4 I just get sed: file golf.sed line 24: ":" lacks a label – charliegreen Apr 13 '17 at 6:14 • The nameless label is a well known bug in GNU sed, that was fixed in version 4.3. But on PPCG, you can write programs for any sed variant and version, using bugs as features if it helps in golfing. Differences between versions are too small to mention (4.2 vs 4.4), but the variant (standard POSIX sed vs extended GNU sed) needs to be specified in the title, with the mention of system programs called, if any. – seshoumara Apr 13 '17 at 10:42 PHP, 168 Output Ascending with use of eval [$a,$o,$b]=explode(" ",$argn);function d($s){for(;$i<strlen($s);)$n+=2**(ord($s[$i++])-97);return$n;}for(eval("$k=d(a)o d(b);");i<26;)echok&2**i++?chr(96+i):"";  PHP, 185 Bytes Output Ascending [a,o,b]=explode(" ",argn);function d(s){for(;i<strlen(s);)n+=2**(ord(s[i++])-97);returnn;}for(;i<26;)echo(bc.[mul,add,0,sub][ord(o)-42])(d(a),d(b))&2**i++?chr(96+i):"";  Online Version Expanded [a,o,b]=explode(" ",argn); # part the input into variables function d(s){ # make decimal value for(;i<strlen(s);)n+=2**(ord(s[i++])-97); returnn; } for(;i<26;) echo(bc.[mul,add,0,sub][ord(o)-42])(d(a),d(b))&2**i++?chr(96+i):""; # Bitwise Compare and Output  PHP, 201 Bytes Output Decending [a,o,b]=explode(" ",argn);function d(s){for(;i<strlen(s);)n+=2**(ord(s[i++])-97);returnn;}for(r=(bc.[mul,add,0,sub][ord(o)-42])(d(a),d(b));r;r-=2**l)t.=chr(97+l=log(r,2)^0);echot;  Online Version Expanded [a,o,b]=explode(" ",argn); # part the input into variables function d(s){ # make decimal value for(;i<strlen(s);)n+=2**(ord(s[i++])-97); returnn; } for( r=(bc.[mul,add,0,sub][ord(o)-42])(d(a),d(b)) # result of the operation ;r; r-=2**l) # subtract the letter value t.=chr(97+l=log(r,2)^0); # find greatest letter echot; # Output  Python 3, 176 167 bytes i=lambda a:str(sum(1<<ord(i)-97for i in a)) def f(a): a,b,c=a.split();m=eval(i(a)+b+i(c));r='' while m: t=0 while m>=2**t*2:t+=1 r+=chr(97+t);m-=2**t return r  Try it online! • Unless I'm mistaken, you can shave off two bytes by replacing m>=2**(t+1) with m>=2**t*2, and five bytes by replacing a=a.split();m=eval(i(a[0])+a[1]+i(a[2])) with something like b,c,d=a.split();m=eval(i(b)+c+i(d)). – Tutleman Apr 10 '17 at 21:40 • Oh, and two more bytes by replacing 2**(ord(i)-97) with 1<<ord(i)-97. – Tutleman Apr 10 '17 at 21:52 • I am amazed how readable this solution is compared to other solutions. – Ole Tange Apr 11 '17 at 12:30 • Thank you :). But i think its also because of python being the language used. The indentation makes the byte count increase, however readable. ;) – officialaimm Apr 11 '17 at 12:42 PHP, 130 for(d=a;e=argn[i++];)e!=' '?d!=b?d+=1<<ord(e)-97:b=e:++d;eval("for(;$j++<27;)echo($a$b$c>>$j-1)&1?chr(96+$j):'';");  expanded version: for($d=a;$e=$argn[$i++];) // for each char in the input$e!=' '?                      //   if space
$d!=b? // if not the operation$$d+=1<<ord($e)-97:       //       add 2^(char - 'a')
$b=$e:                    //     else save operation
++$d; // else increase "pointer" eval("for(;$j++<27;) // for each bit in the output echo(abc>>$j-1)&1? // calulate the result and check the bit chr(96+\$j):          //     output corrosponding char
'';                   //     output nothing
");


run with php -R <code>.

AWK, 201 bytes

BEGIN{RS="(.)"}n=index(V="abcdefghijklmnopqrstuvwxyz",RT){s+=2^--n}index("+-*",RT){a=s RT
s=0}END{RS="\n"
"(awk '$0="a s"'<<<1)"|getline v for(j=26;j--;)if((s=v-2^j)>=0){v=s;c=substr(V,j+1,1)c}print c}  "(awk '$0="a s"'<<<1)"|getline v is the best way I could come up with to do an evaluate in AWK. I may be "cheating" a little to call this just AWK, since I am executing a command, but at least the command is also AWK :)

I'm sure I'm missing some way to reduce the byte-count, but I sure can't see it.

Usage is fairly standard, e.g. put the code in FILE and do:

awk -f FILE <<< "bc + ab"


Note that spaces are not required and any non-op/non[a-z] character will be silently ignored. Could be extended to work with numbers greater than "abcdefghijklmnopqrstuvwxyz" by changing the loop. To do division, simply add the / character to the operation string :). Also, will print a blank line if the result <= 0.