Create a Boolean Calculator

Code Golf Asked on October 27, 2021

Our boolean operators are AND, OR, XOR, NAND, NOR, XNOR and, in conjunction with one of those operators, NOT.

Our numbers are $$1$$ and $$0$$.

The challenge is to write a program or function that calculates the results of the input.

Input

A string, array or other input format of your choice; containing alternating numbers and operators, e.g. 1 NOR 1 or ["1","OR","0","AND","1"] or 0XOR0XNOR1.

As an exception, NOT must always come directly after another operator (e.g. 0 AND NOT 1).. You can’t implement NOT by itself, and you won’t ever get a chain of multiple NOTs (so 1 AND NOT NOT 0 is an invalid input).

The input must contain the strings for the operators (upper or lower-case is fine); no other representation can be used e.g. .+^¬||&& etc.

Output

Return or print a single number ($$1$$ or $$0$$), derived using the calculation below. Invalid input can lead to any output you choose, or none.

Calculation

We’re ignoring any precedence rules here – just calculate them in the order they come in (i.e. left-to-right) – as if someone was typing it into a calculator and pressing Enter after each number. NOT is the only one that might cause some difficulties with that logic, as you need to figure out what it’s NOT-ing before you can apply the other operator.

Truth Tables

INPUT   OUTPUT
A   B   AND NAND OR NOR XOR XNOR
0   0    0   1   0   1   0   1
0   1    0   1   1   0   1   0
1   0    0   1   1   0   1   0
1   1    1   0   1   0   0   1

IN OUT
A  NOT A
0    1
1    0


Examples

• 1 NOR 1 = 0
• 1 NOR NOT 0 = 0 (equivalent to $$1$$ NOR $$1$$)
• 1 NOR NOT 0 AND 1 = 0 (equivalent to $$0$$ (from above) AND $$1$$)
• 1 NOR NOT 0 AND 1 OR 1 = 1 (equivalent to $$0$$ (from above) OR $$1$$)
• 1 NOR NOT 0 AND 1 OR 1 XNOR 1 = 1 (equivalent to $$1$$ (from above) XNOR $$1$$)
• 1 NOR NOT 0 AND 1 OR 1 XNOR 1 NAND 0 = 1 (equivalent to $$1$$ (from above) NAND $$1$$)
• 1 NOR NOT 0 AND 1 OR 1 XNOR 1 NAND 0 XOR NOT 0 = 0 (equivalent to $$1$$ (from above) XNOR NOT $$0$$ = $$1$$ XNOR $$1$$ = $$0$$)

Scoring

This is code-golf, but with a twist.

Your score is the number of bytes in your code, divided by the number of operators your code implements. Smallest score wins.

For example, if you only implement AND, your score is the number of bytes of your code.

If you implement AND, OR, XOR, NAND, NOR, XNOR, NOT (the full list of operators); then you get to divide the number of bytes by 7.

You must implement at least one operator, and you cannot implement NOT by itself; as it must be preceded by another, different operator and so doesn’t count as implemented otherwise.

Just because someone has a low score already, don’t let that put you off trying to get the best score for your language! It would be interesting to compare across different numbers of implemented operators too – e.g. you might have a bad score for 2 operators, but you might be able to implement 7 really efficiently.

Scala, 183 bytes 164 bytes / 7 ops = ~23.43

"OR".+(_).split("(?<=\d)")./:(0>1){(b,o)=>val s=o.replace("NOT","")
val c=s!=o^(o.last>48)
s.count(_==78)==1^(if(s toSet 65)!(b&c)else if(s toSet 88)b^c
else b|c)}


Try it online!

The operators are uppercase (whitespace doesn't matter), and the output is a Boolean.

Answered by user on October 27, 2021

brainfuck, 130 bytes / 1 operator = 130

+>>+<,[------------------------------------------------[<->[-]]<[>>[-]<<-]><+>,]>++++++++++++++++++++++++++++++++++++++++++++++++. 

Try it online!

Link is for a readable, commented version. Only implements AND. Although technically all it does is print 1 unless there is a 0 in your input, in which case it prints 0. It might be possible to shorten this by replacing the spots where I add/subtract 48 times, but I'm lazy, so I'm going to leave it as is.

Answered by pslessard on October 27, 2021

Haskell, $$225 div 4 = 56.25$$

b(_:'A':'0':s)=b('0':s)
b(c:'A':_:s)=b(c:s)
b(_:'O':'1':s)=b('1':s)
b(c:'O':_:s)=b(c:s)
b('N':'0':s)=b('1':s)
b('N':_:s)=b('0':s)
b('0':'X':'0':s)=b('0':s)
b('1':'X':'1':s)=b('0':s)
b(_:'X':_:s)=b('1':s)
b x=x
f=(b.map(!!0))


Defines a function f, which given a list of the format ["1","AND","0"] returns either "1" or "0". Implements AND, OR, NOT, and XOR.

Answered by sugarfi on October 27, 2021

Japt v2.0a0, 36 bytes ÷ 7 ≈ 5.14

e/..*?d/@1&#e4#÷0953÷2pXn36 %873%34


A direct port of @Arnauld's answer to Japt

Explanation

e/..*?d/@1&#e4#÷0953÷2pXn36 %873%34
e                                     // Repeatedly replace
/..*?d/                             // the regex /..*?d/g with output of
@                            // a function which takes the match as arg named X
1&#e4#÷0953÷2pXn36 %873%34  // and returns 1 & 10142470953 / 2 ** (parseInt(X, 36) % 873 % 34)


The expression 1 & 10142470953 / 2 ** (parseInt(X, 36) % 873 % 34) was brute-forced by @Arnauld

Answered by Mukundan314 on October 27, 2021

JavaScript (ES7), 77 bytes / 7 operators = 11

Expects a string with no separator, such as "1NORNOT0".

f=s=>1/s?s:f(s.replace(/..*?d/,s=>10142470953/2**(parseInt(s,36)%873%34)&1))


Try it online!

Or try all possible sub-expressions.

How?

We use a recursive function to simplify the input string s until we get a single digit. When this happens, 1/s is either 1 or Infinity, which are both truthy.

As long as s still contains at least one operator, we use the following regular expression to isolate the next sub-expression:

/..*?d/
.         a single character (must be 0 or 1)
.*?      followed by several characters, non-greedily
d    followed by a digit


We use this hash function (which was brute-forced) to get the result of the sub-expression and replace it in s:

10142470953 / 2 ** (parseInt(s, 36) % 873 % 34) & 1


Answered by Arnauld on October 27, 2021

sed, 8 bytes ÷ 1 = 8

/0/c0
c1


Try it online!

Implements only and.

Explanation

/0/c0   # Set contents of line to 0 if line contains 0
c1      # Otherwise set contents to 1


Answered by Mukundan314 on October 27, 2021

SimpleTemplate, 361 bytes ÷ 4 operators = 90.25

It is a very big piece of code, but was very challenging!

{@fnP S}{@fnT.AND a,b}{@ifa}{@ifa is equalb}{@return1}{@/}{@/}{@return"0"}{@/}{@fnT.OR a,b}{@incbyb a}{@ifa}{@return1}{@/}{@return"0"}{@/}{@fnT.XOR a,b}{@ifa is equalb}{@return1}{@/}{@return"0"}{@/}{@whileS matches"@([01])(AND|X?OR)(NOT)?([01])@"P}{@callT.[P.2]intoR P.1,P.4}{@ifP.3}{@setT 1,0}{@setR T.[R]}{@/}{@callstr_replace intoS P.0,R,S}{@/}{@returnS}{@/}


This implements the AND, OR, XOR and NOT operators.

This was entirely implemented without using AND, OR and XOR, as those don't exist in my language, at all!

It was even more challenging due to a bug in the compiler, where {@return 0} returns null ... :/ 6 bytes right there ...

Usage:

Simply call the function P and pass a single string without spaces.

Returns either 0 or 1, or the whole string for invalid inputs.

Example:

{@call P into result "1ORNOT0"}
{@echo result}


Ungolfed:

Since this is a massive mess, I've also prepared an human readable version:

{@fn parse string}
{@fn this.AND a, b}
{@if a}
{@if a is equal to b}
{@return 1}
{@/}
{@/}
{@return "0"}
{@/}

{@fn this.OR a, b}
{@inc by b a}
{@if a}
{@return 1}
{@/}
{@return "0"}
{@/}

{@fn this.XOR a, b}
{@if a is equal to b}
{@return 1}
{@/}
{@return "0"}
{@/}

{@while string matches "@([01])(AND|X?OR)(NOT)?([01])@" pieces}
{@call this.[pieces.2] into result pieces.1, pieces.4}
{@if pieces.3}
{@set tmp 1, 0}
{@set result tmp.[result]}
{@/}
{@call str_replace into string pieces.0, result, string}
{@/}

{@return string}
{@/}


This works exactly the same way, except the function is called "parse".

Alternative:

Below is a SUPER boring one that has EVERYTHING pre-calculated, but has a score of 276/7 = 39.428571428571... (428571 is recurring).

{@fnP S}{@setL.AND"001"}{@setL.NAND"110"}{@setL.OR"011"}{@setL.NOR"100"}{@setL.XOR"010"}{@setL.XNOR"101"}{@whileS matches"@([01])(N?AND|X?N?OR)(NOT)?([01])@"P}{@ifP.3}{@setT"10"}{@setP.4 T.[P.4]}{@/}{@incbyP.4 P.1}{@callstr_replace intoS P.0,L.[P.2].[P.1],S}{@/}{@returnS}{@/}


It implements all operators, but ... It is kinda cheating...

Below it the ungolfed version:

{@fn parse string}
{@set table.AND 0, 0, 1}
{@set table.NAND 1, 1, 0}
{@set table.OR 0, 1, 1}
{@set table.NOR 1, 0, 0}
{@set table.XOR 0, 1, 0}
{@set table.XNOR 1, 0, 1}

{@while string matches "@([01])(N?AND|X?N?OR)(NOT)?([01])@" pieces}
{@if pieces.3}
{@set tmp 1, 0}
{@set pieces.4 tmp.[pieces.4]}
{@/}
{@inc by pieces.4 pieces.1}
{@set values table.[pieces.2]}
{@call str_replace into string pieces.0, values.[pieces.1], string}
{@/}

{@return string}
{@/}


Answered by Ismael Miguel on October 27, 2021

JavaScript (V8), 141 bytes ÷ 7 operators, score 20.14

f=i=>'01'[i]||f(i.replace(/NOT./,n=>'10'[n[3]]).replace(/(.)(..)D*(.)/,(_,a,o,b)=>({AN:a&b,OR:a|b,XO:a^b,NA:a&b^1,NO:(a|b)^1,XN:a^b^1})[o]))


Try it online!

Takes input as a string with capitalised operators and no padding, like 0AND1OR0.

Recursively calculates the next value based on the first two characters of the operator. (not before replacing NOTs with their counterparts)

Answered by Matthew Jensen on October 27, 2021

Perl 5, 86 bytes / 7 operators = 12.29

s/not/!/g;s/d//;$=1*$1;$=eval"$ $_"=~s/(.*?[^a])n(.*)/!($1$2)/r for/.*?d/g}{$|=0


Try it online!

Perl 5, 9 bytes / 3 operators (or, xor, not) = 3

$_=0|eval  Try it online! Answered by Xcali on October 27, 2021 Wolfram Language (Mathematica), 62 bytes ÷ 6 = 10.33 Boole@ToExpression[Capitalize@#~StringRiffle~"~"]/.a:0|1:>a>0&  Try it online! Pure function. Takes a list of lowercase strings as input and returns 0 or 1 as output. Supports every operation except NOT. Wolfram Language (Mathematica), 87 bytes ÷ 7 = 12.43 Boole@ToExpression@StringReplace[Capitalize@#~StringRiffle~"~","t~"->"t@"]/.a:0|1:>a>0&  Try it online! Similar to the previous solution, but it also supports NOT. Wolfram Language (Mathematica), 15 bytes ÷ 1 = 15 Boole@*FreeQ[0]  Try it online! Function. Takes a list of strings and integers as input and returns 0 or 1 as output. Only supports AND. Answered by LegionMammal978 on October 27, 2021 FEU, 33 bytes, 2 operations, score 16.5 m/NOT 0/1/NOT 1/0/.*1.*/1/[^1]+/0  Try it online! Implements NOT and OR Answered by PkmnQ on October 27, 2021 Japt, 1 byte ÷ 1 = 1 e  Try it online! Implements only and. Works by checking if every element in the input has a truthy value. Answered by Mukundan314 on October 27, 2021 Pyth, 1 byte ÷ 2 = 0.5 v  Try it online! This works for and and not. Works by evaluating the input as python code Answered by Mukundan314 on October 27, 2021 APL (Dyalog Extended), 2 bytes ÷ 1 = 2 ~⍲  Try it online! ⍲ (nand) returns 1 if and only if argument has a 0 anywhere (it ignores all other data) ~ negates that APL (dzaima/APL), 2 bytes ÷ 1 = 2 1∊  Try it online! Simply asks is there any 1 in the argument? Answered by Adám on October 27, 2021 Python 3, 16 4 bytes / 2 = 2 score eval  Try it online! This works for and and not in any combinations. It partially works for or but only when not used in conjunction with and in certain cases due to operator precedence in Python. As such cases exist, my official score will only be divided by two (if half-points are allowed, this could maybe be divided by 2.5 instead to get a final score of 1.6) Answered by Daniel H. on October 27, 2021 APL (Dyalog Unicode), 43 36 bytes ÷ 6 = 6 Thank you user41805 for the idea of combining definitions that are negations of each other and to tsh for noticing stray spaces. DNAN←~DNA←∧ RON←~RO←∨ ROX←~RONX←= ⍎⌽  Try it online! Since APL is right-to-left, we define the functions with reversed names, then reverse the expression (⌽) and execute it (⍎). APL (Dyalog Unicode), 57 50 bytes ÷ 7 = 7.14 Thank you user41805 for the idea of combining definitions that are negations of each other and to tsh for noticing stray spaces. DNAN←~DNA←∧ RON←~RO←∨ ROX←~RONX←= TON←{⍵ ⍵⍵~⍺⍺} ⍎⌽  Try it online! Since APL is right-to-left, we define the functions with reversed names, then reverse the expression (⌽) and execute it (⍎). NOT (TON) requires special treatment. We define it as a dyadic operator ({}) because this makes it bind stronger to its operands. We then negate the left (original right) operand (~⍺⍺) and apply the right operand (⍵⍵ — originally on its left) with the right argument (⍵ originally from its left) as left argument. The arguments' sides doesn't matter since all functions are commutative. Answered by Adám on October 27, 2021 Python, 3 bytes ÷ 1 op = score 3 min  Try it online! Implements AND, with inputs like ["1", "AND", "0", "AND", "0"]. Simply takes the smallest string value, which is "0" if present and "1" otherwise. Since "AND" is later alphabetically, it can be ignored. Another solution is all, using inputs like [1, "AND", 0, "AND", 0] since only 0 is Falsey. Python 2 could also do min with such inputs, since it has numbers as smaller than strings, whereas Python 3 refuses to compare them. Answered by xnor on October 27, 2021 Retina 0.8.2, 84 bytes, score 12 NOT0 1 NOT1 0 ^(0A|1NO|(0NA|1O))[A-Z]+.$#2
}^(1A|0O|0XO|1XN)[A-Z]+

}^.[A-Z]+
NOT


Try it online! Link includes test suite that deletes spaces from the input for the convenience of the user. Explanation:

NOT0
1
NOT1
0


Handle the NOT operator.

^(0A|1NO|(0NA|1O))[A-Z]+.
\$#2


0 AND and 1 NOR are always 0, while 0 NAND and 1 OR are always 1, regardless of the RHS.

}^(1A|0O|0XO|1XN)[A-Z]+


1 AND, 0 OR, 0 XOR and 1 XNOR leave the RHS unchanged. Repeat the above operations until an operation that inverts the RHS is reached.

}^.[A-Z]+
NOT


Replace this operation with a NOT and loop around to start processing operations again.

Answered by Neil on October 27, 2021

Python 2, score 15.714 (132 ... 114 110 bytes, 7 operators)

Input is a single string with lowercase operators.

b=c=d=49
for a in input().replace('t ','a').split():a=hash(a);c,d=[a|d,a&d,a^d^1][b%65%3]^b%45,c;b=a
print~c&1


Try it online!

The code uses the following numbers produced by Python 2's hash function:

+--------+----------------------+-----+--------+--------+
| string |    h=hash(string)    | h&1 | h%65%3 | h%45&1 |
+--------+----------------------+-----+--------+--------+
| and    |  1453079729200098176 |     |      0 |      0 |
| nand   | -4166578487142698835 |     |      0 |      1 |
| or     |    14208085359128317 |     |      1 |      0 |
| nor    |  5261102140395498078 |     |      1 |      1 |
| xor    | -5999452984713080668 |     |      2 |      0 |
| xnor   |   485507670233933377 |     |      2 |      1 |
|        |                      |     |        |        |
| 0      |           6144018481 |   1 |        |        |
| 1      |           6272018864 |   0 |        |        |
| noa0   | -4166584487129698722 |   0 |        |        |
| noa1   | -4166584487129698721 |   1 |        |        |
+--------+----------------------+-----+--------+--------+


Answered by ovs on October 27, 2021

Sorry to report that the task is trivial in some language, but anyway here it is.

J, 2 bytes, score 2 ÷ 1 = 2

".


Try it online!

Implements any one of AND, OR, or XOR. The three are defined in the J standard library as infix functions, so calling ". (eval) on the string automagically evaluates the given expression as-is. The only problem is that J evaluates from right to left, so the three cannot be used at once.

Since it is boring, here are some attempts at adding features one at a time:

J, 10 bytes, score 10 ÷ 3 = 3.33

[:".|.&.;:


Try it online!

Implements all of AND, OR, and XOR. Since all six operators (except NOT) are symmetric, in order to fix the evaluation order, it suffices to reverse the order of words.

[:".|.&.;:  NB. Input: the expression with space-separated tokens
NB. Example input '1 AND 1 XOR 0'
&.;:  NB. Split into words ['1', 'AND', '1', 'XOR', '0']
|.      NB. Reverse the order of words ['0', 'XOR', '1', 'AND', '1']
&.;:  NB. Join the words back, with spaces in between '0 XOR 1 AND 1'
[:".        NB. Eval it


At this point, adding a feature is a matter of defining a named infix function.

J, 18 bytes, score 18 ÷ 4 = 4.5

NOR=:+:
[:".|.&.;:


Try it online!

Adds NOR (+:) to the list.

J, 26 bytes, score 26 ÷ 5 = 5.2

XNOR=:=
NOR=:+:
[:".|.&.;:


Try it online!

Adds XNOR (=) to the list.

J, 35 bytes, score 35 ÷ 6 = 5.83

NAND=:*:
XNOR=:=
NOR=:+:
[:".|.&.;:


Try it online!

Adds NAND (*:) to the list.

Using the same strategy to add NOT is a bit more tricky, since the word order would look like 1 NOT AND 1 instead of 1 AND NOT 1, and it should negate the number on its left. I solved it by making it a "conjunction", which has higher precedence over regular functions or "verbs" and consumes two tokens on the both sides of it. It is defined as

NOT=:2 :'y v-.u'


and it evaluates like this: given 0 NOT AND 1, u, v, y become 0, AND, 1 respectively, and y v-.u becomes 1 AND -. 0 (where -. is a prefix function for logical negation), successfully negating the number on its left before applying the infix function.

J, 52 bytes, score 52 ÷ 7 = 7.43

NOT=:2 :'y v-.u'
NAND=:*:
XNOR=:=
NOR=:+:
[:".|.&.;:


Try it online!

Answered by Bubbler on October 27, 2021

05AB1E, score: 1 (1 byte, 1 operator)

ß


Input as a list of strings for each digit/operator.
Implements AND.

Explanation:

ß    # Pop the (implicit) input-list and leave its minimum,
# which is "0" if the input contains a "0", or "1" otherwise
# (after which this is output implicitly as result)


05AB1E, score: ~7.857 (55 bytes, 7 operators)

1ÝÂ„€– ìs:„€ƒ€—#„nxvDyìì}„&~SD'_«ì'^õšD'_«ìì:#ðš2ôí˜J.V


Input is a single lowercase string.
Implements all 7 operators.

Explanation:

Step 1: Replace the not 1/not 0 with 0/1 respectively:
I.e. 1 nor not 0 and 1 or 1 xnor 1 nand 0 xor not 0 is converted to 1 nor 1 and 1 or 1 xnor 1 nand 0 xor 1.

1Ý                   # Push list [0,1]
Â                  # Bifurcate it (short for Duplicate & Reverse copy): [1,0]
„€–               # Push dictionary string "not "
ì             # Prepend it in front of both: ["not 1","not 0"]
s            # Swap so the [0,1] is at the top of the list again
:           # Replace all ["not 1","not 0"] with [0,1] in the (implicit) input


Step 2: Replace all other operations xnor/xor/nand/nor/and/or with ^_/^/&_/~_/&/~ respectively:
I.e. 1 nor 1 and 1 or 1 xnor 1 nand 0 xor 1 is converted to 1 ~_ 1 & 1 ~ 1 ^_ 1 &_ 0 ^ 1.

„€ƒ€—                # Push dictionary string "and or"
#               # Split it on spaces: ["and","or"]
„nx            # Push string "nx"
v           # Loop y of its characters:
D          #  Duplicate the list at the top of the stack
yì        #  Prepend the current letter to each string in the list
ì       #  Prepend-merge the lists together
}           # Stop the loop. We now have the list:
#  ["xnand","xnor","xand","xor","nand","nor","and","or"]
„&~                  # Push string "&~"
S                 # Convert it to a list of characters: ["&","~"]
D                # Duplicate it
'_«            '# Append "_" to each: ["&_","~_"]
ì            # Prepend-merge it: ["&_","~_","&","~"]
'^         '# Push "^"
õš        # Convert it to a list, and prepend an empty string: ["","^"]
D       # Duplicate it
'_«   '# Append "_" to each: ["_","^_"]
ì   # Prepend-merge it: ["_","^_","","^"]
ì  # Prepend-merge it: ["_","^_","","^","&_","~_","&","~"]
:                    # Replace all ["xnand","xnor","xand","xor","nand","nor","and","or"]
# with ["_","^_","","^","&_","~_","&","~"]


&~^ are builtins for bitwise AND, OR, and XOR respectively. And _ is the ==0 builtin (which converts 0 to 1 and vice-versa).

Step 3: Convert it to Reverse Polish notation:
I.e. 1 ~_ 1 & 1 ~ 1 ^_ 1 &_ 0 ^ 1 is converted to 1 1~_1&1~1^_0&_1^.

 #                   # Split the string by spaces
ðš                 # Prepend a leading " " to the list
2ô               # Split the list into parts of size 2
í              # Reverse each pair
˜J            # Flattened join everything together


Step 4: Execute/evaluate it as 05AB1E code, and output the result:
I.e. 1 1~_1&1~1^_0&_1^ results in 0.

         .V          # Evaluate/execute it as 05AB1E code
# (after which the result is output implicitly)


See this 05AB1E tip of mine (section How to use the dictionary?) to understand why „€–  is "not " and „€ƒ€— is "and or".

Answered by Kevin Cruijssen on October 27, 2021

Get help from others!