# Create a Boolean Calculator

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.

• Note I found this (related?) challenge: codegolf.stackexchange.com/questions/78351/…; but I think this is sufficiently different – simonalexander2005 Jul 14 at 8:22
• Can we take the operands as lowercase, or is uppercase mandatory? – Kevin Cruijssen Jul 14 at 9:06
• Is the input guaranteed to have a space between each token (a number or an operator)? Can I take tokenized input (e.g. ["1", "NOR", "NOT", "0"])? – Bubbler Jul 14 at 9:07
• Can we output True/False for 1/0? I think this is generally allowed by default in a language like Python where True==1 and False==0, but am never sure. – xnor Jul 14 at 11:00
• @xnor yes, I would say so - it fits with the spirit of the question being boolean logic and all! – simonalexander2005 Jul 14 at 11:06

# 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.

• I'm surprised your solution doesn't work for XNOR, xnor – Daniel H. Jul 14 at 11:38
• I didn't understand, for examples I have ['1','OR',0], it should return 1 but it obviously returns 0. – Victor VosMottor Jul 15 at 9:26
• @VictorVosMottorthanksMonica The 0 is a number, instead of a string, like the '1'. Maybe that's why? – Ismael Miguel Jul 15 at 9:32
• @VictorVosMottorthanksMonica This only supports AND -- it actually ignores the operator strings. – xnor Jul 15 at 9:33

# Pyth, 1 byte ÷ 2 = 0.5

v


Try it online!

This works for and and not. Works by evaluating the input as python code

# 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)

• Unfortunately I don't think you can quite get or together with and since Python's operator precedence evaluates and's first, so 1 or 0 and 0 gives 1 rather than 0. – xnor Jul 14 at 11:54
• @xnor good point, I overlooked that. I'll try to fix it without adding too many bytes – Daniel H. Jul 14 at 11:55
• Your first solution can actually just be eval on its own, since f=eval would work. – xnor Jul 14 at 11:55
• It turns out any fix for this 1 or 0 and 0 case would make my score go up a lot more than simply removing or from the working functions. I've made changes accordingly – Daniel H. Jul 14 at 23:57

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!

# 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".

# 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 |        |        |
+--------+----------------------+-----+--------+--------+

• I think you can have your second solution take input as a list like [1,"OR",0,"OR",1] and so be lambda l:1in s. – xnor Jul 14 at 10:56
• @xnor I removed that part of my answer, as your solution does a way better job at showing that answers with a single operator perform better. – ovs Jul 14 at 11:42

# 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.

• It seems combining ≠ and = like RONX←~ROX←≠ works – user41805 Jul 14 at 11:52
• @user41805 Ah yes, I miscalculated that that would be the same byte count. – Adám Jul 14 at 11:52
• @Bubbler Thanks. Fixed. – Adám Jul 14 at 13:05
• So hilarious to see the 7-operator version exactly ties with that of J, and it even uses the exact same dop/conjunction for NOT. – Bubbler Jul 15 at 0:20
• @Bubbler Ah, well, but J is "cheating" by using predefined words; APL is obviously getting a lot more done for the same character count. NOT has to be to be the same for syntactic reasons. – Adám Jul 15 at 0:25

# 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.

# 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

• Sorry, I didn't mean to say Turing Complete, it just computes "NOT" and "OR" and I get similar terms mixed up a lot. – PkmnQ Jul 19 at 14:38

# 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.

# 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?

# 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!

• Fails on 1 or 0 and 0: tio.run/##K0gtyjH9/18l3tagJrUsMef/f0OF/CIFA4XEvBQFg3/5BSWZ@XnF/… – ovs Jul 14 at 21:02
• It's performing proper order of operations. So the and part gets evaluated first. – Xcali Jul 14 at 21:15
• yes, but the challenge specifies that it should be calculated left-to-right. – ovs Jul 14 at 21:52
• 66 bytes,for 7 operators,otherwise 2nd only works for 2 operators (not , and) because of perl operator precedence – Nahuel Fouilleul Jul 15 at 13:36
• 56 bytes also seems to work – Nahuel Fouilleul Jul 15 at 14:39

# 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}
{@/}


# 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


# 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

• It's unfortunate that you couldn't get the hash down to 32 or fewer bits, otherwise you could have used >>>. – Neil Jul 21 at 9:04

# 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.

# 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.

# 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)

# 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

# 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.

# Scala, Score: ~26.14 (183 bytes, 7 ops)

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


Try it online!

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