AlgoVerse

Truth table

For every assignment of the variables, work out each subformula from the inside out, and finally say whether the formula is always true (a tautology), always false (a contradiction) or sometimes true.

A propositional formula's value depends only on its variables' values. With n variables there are 2^n assignments; the truth table has a row for each.

Read the full explanation ↓
Input:
formula="(p>q)=(~q>~p…"PDF export

(p → q) ↔ (¬q → ¬p): a conditional is equivalent to its contrapositive; all four rows are true: a tautology.

  1. class Node:
  2. def __init__(self, op, left, right, text):
  3. self.op = op
  4. self.left = left
  5. self.right = right
  6. self.text = text # how the subformula is written
  7. def truth_table(formula):
  8. root = parse(formula)
  9. if root is None:
  10. return None
  11. names = []
  12. columns = []
  13. collect(root, names, columns)
  14. names.sort()
  15. header = names + [c.text for c in columns]
  16. rows = [header]
  17. trues = 0
  18. for m in range(2 ** len(names)):
  19. env = {}
  20. for i in range(len(names)):
  21. env[names[i]] = (m >> (len(names) - 1 - i)) % 2 == 1
  22. row = []
  23. for name in names:
  24. row.append("T" if env[name] else "F")
  25. for c in columns:
  26. env[c.text] = value(c, env)
  27. row.append("T" if env[c.text] else "F")
  28. rows.append(row)
  29. last = env[root.text]
  30. if last:
  31. trues = trues + 1
  32. if trues == 2 ** len(names):
  33. kind = "tautology"
  34. elif trues == 0:
  35. kind = "contradiction"
  36. else:
  37. kind = "contingent"
  38. return kind
  39. def value(c, env):
  40. # Each column only reads columns to its left: its operands were filled in first.
  41. if c.op == "¬":
  42. return not env[c.right.text]
  43. a = env[c.left.text]
  44. b = env[c.right.text]
  45. if c.op == "∧":
  46. return a & b
  47. if c.op == "∨":
  48. return a | b
  49. if c.op == "→":
  50. return (not a) | b
  51. return a == b
  52. def collect(node, names, columns):
  53. # Postorder, so every column comes after the columns it depends on; a repeated subformula gets one column.
  54. if node.op == "var":
  55. if node.text not in names:
  56. names.append(node.text)
  57. return
  58. if node.left is not None:
  59. collect(node.left, names, columns)
  60. collect(node.right, names, columns)
  61. for c in columns:
  62. if c.text == node.text:
  63. return
  64. columns.append(node)
  65. def parse(text):
  66. # ~ not, & and, | or, > implies, = if and only if; from tightest to loosest. > groups to the right.
  67. toks = []
  68. for c in text:
  69. if c != " ":
  70. toks.append(c)
  71. pos = [0]
  72. root = parse_binary(toks, pos, 0)
  73. if root is None or pos[0] != len(toks):
  74. return None
  75. return root
  76. LEVELS = [["=", "↔"], [">", "→"], ["|", "∨"], ["&", "∧"]]
  77. def parse_binary(toks, pos, level):
  78. if level == len(LEVELS):
  79. return parse_not(toks, pos)
  80. sym, op = LEVELS[level]
  81. left = parse_binary(toks, pos, level + 1)
  82. while left is not None and pos[0] < len(toks) and toks[pos[0]] == sym:
  83. pos[0] += 1
  84. right = parse_binary(toks, pos, level if sym == ">" else level + 1)
  85. if right is None:
  86. return None
  87. left = Node(op, left, right, "(" + left.text + " " + op + " " + right.text + ")")
  88. return left
  89. def parse_not(toks, pos):
  90. if pos[0] >= len(toks):
  91. return None
  92. c = toks[pos[0]]
  93. pos[0] += 1
  94. if c == "~":
  95. inner = parse_not(toks, pos)
  96. if inner is None:
  97. return None
  98. return Node("¬", None, inner, "¬" + inner.text)
  99. if c == "(":
  100. inner = parse_binary(toks, pos, 0)
  101. if inner is None or pos[0] >= len(toks) or toks[pos[0]] != ")":
  102. return None
  103. pos[0] += 1
  104. return inner
  105. if c.isalpha():
  106. return Node("var", None, None, c)
  107. return None

Invariant: Each column uses only columns to its left, so by the time we reach it its operands are filled in.

Start

trues—
kind—

The table is still empty.

Recursion tree

Filled orange = running call · Orange outline = still on the stack · Grey = finished

Local variables

formula'(p>q)=(~q>~p)'

Orange = changed in this step

Call stack

  1. truth_table()line 9
Step 1 of 655
Black = row · Orange = key stepcolumns: 0/1 · row: 0/4 · tautology: 0/1
    Explanation

    The idea

    A propositional formula's value depends only on its variables' values. With n variables there are 2^n assignments; the truth table has a row for each.

    To keep it readable, besides the variables' columns there is a column for every subformula, from the innermost outwards. Each column only needs the columns before it.

    The operators

    | Typed | Operator | True when | |---|---|---| | ~ | ¬ | the operand is false | | & | ∧ | both are true | | \| | ∨ | at least one is true | | > | → | the antecedent is false or the consequent true | | = | ↔ | both have the same value |

    Classifying

    • Tautology: every row is true; rules of inference, for example.
    • Contradiction: no row is true.
    • Contingent: some rows.

    Two formulas are equivalent if A ↔ B is a tautology.

    The input

    The variables are p, q, r and s. ~ binds tightest and = loosest; > groups to the right.