1. DFAs and NFAs
Introduction
Definitions
- Alphabet,
: set of symbols. - String: sequence of symbols.
= empty string -
: set of all strings - Language
: subset of
Concatenation
Strings
Concatenation of strings is associative: brackets are not needed.
Languages
i.e. A.map(a => B.map(b => a+b))
Language concatenation is associative.
Powers: Strings/Characters
Base case:
Powers: Languages
Thus,
Properties
Deterministic Finite Automata
Where:
-
is a non-finite, empty set (vertices) of states -
is the start state (arrow from nowhere) -
is the set of end states (double circle) -
is the input alphabet - a non-finite, empty set. Each symbol represents a transition. Every transition can occur at any state, even if it just loops back -
is the transition function: - a function taking the state and transition as inputs, and outputting an end state
Extended Transition Function
- The string
is accepted by if - The language accepted by
, - The language
is regular if for some DFA
All finite languages are regular (e.g. OR every single string) but not all infinite languages are regular.
Regular languages are closed over:
- Complement (inverse)
- Intersection
- Union
- Concatenation
- Star
Proving Regular Languages Are Closed under the Complement Operator
Let
By their definitions,
Swap accepting and non-accepting states:
Let
To do this, we first show that
The first requirement,
The definition of the extended transition function is
Intersection of Two Automata
Let
Only the alphabet is the same between the automata
Idea: keep track of the state
Now, we need to define the transition function where
Need to show
Union of Two Automata
Let
Non-Deterministic Finite Automata
NFA: a state can have zero or multiple transitions using a single symbol.
If any route leads to an accept state, the string should be accepted.
Extended Transition Relation
That is, for every state it can be in, calculate the possible transition(s) for the next symbol, put them all in a set, and repeat until the input string is empty.
The language accepted by
NFDA to DFA Conversion Through Subset Construction
States become a set containing all states it could be in given the transitions. If it can be in the states
Proving Every NFA is Accepted by a DFA
Idea: keep track of all states the NFA
Subset automaton (DFA)
Now show that
The definition of
Hence,
NDFA with -Transitions
Example use case: union of two regular languages: one new start state, with two
The sequence can be of length zero, or be arbitrarily long. Note that the
To convert an NDFA with
- Replace each state by its
closure - Add state transitions to any state that is accessible from any member of the closure
-
- Replace the start state with
Extended Transition Relation
2. Regular Expressions
Patterns
Atomic
-
matched by the empty string -
matched by nothing -
matches any symbol in
Compound
-
matched by string if matches or -
: matches both -
: if string , matches and matches -
: doesn’t match -
: matches or if is an empty string -
: zero or more characters -
: one or more characters
Language Definition
-
-
-
-
-
-
- RHS: iterated language operation (concatenating language with itself infinitely many times)
-
-
-
Order of operations:
, concatenation, union
Proving Every Language Generated by a Regular Expression is an NFA
Where
∅:
---> ◯ ⭘
a: ε:
a ε
---> ◯ ---> ⭘ ---> ◯ ---> ⭘
p|q:
ε ε
---> ◯ p ◯ ---
ε / \
---> ◯ ---> ◯ ⭘
\ ε ε /
---> ◯ q ◯ ---
pq:
ε ε ε
---> ◯ ---> ◯ p ◯ -----> ◯ q ◯ ---> ⭘
p*:
ε ε
---> ◯ ---> ◯ p ◯ ---> ⭘
| ^ | ^
| -------- |
----------------------
Additionally:
Finite Automata to RegExp
- Labelled with RegExps as well as symbols from the alphabet (or
) - One accept state
- At most one transition between states
- No transitions into the start state or out of the end state
Every language accepted by an NFA is generated by a RegExp. We can successively remove states and replace them with longer and longer RegExps as transitions. To remove
To remove q_j, for every qi -> qj -> qk, where qj not qi or qj:
rij rjk
qi ---------> qj ---------> qk
| ^ | ^
| |__| |
| rjj |
|___________________________|
rik
rik | (rij (rjj)* rjk)
qi ----------------------> qk
For two-state loops:
rij rjk
qi ---------> qj ---------> qk
^ rji |
|------------|
Remove q_j: i -> j -> k, and i -> j -> i
r_ij r_jk
qi ---------> qk
^ |
|__|
rij rji
(rij rji)* r_ij r_jk
qi -----------------------> qk
Minimization of DFA
- Eliminate states that can’t be reached from the start state
- Find equivalent states
- Collapse equivalent states
Equivalence of states
i.e. states are equivalent if, for all strings, if you start at
If states are not equivalent, they are distinguishable:
- If one state is accepting and another is not accepting,
can be passed and they are clearly distinguishable - If two states
and are distinguishable by the string , if and , then this means and produce different outputs for the string so they are also distinguishable
Algorithm to find distinguishable states
- Create a table (staircase) of pairs where one is accepting and one is not (i.e. distinguishable)
- For remaining pairs, see what happens under all transitions. If the pair of states you end up in are known to be distinguishable for any transition, then the pair you started with are also distinguishable. If not, no new information is obtained
- Repeat until no new information is obtained
- Reflexive:
- Symmetric:
implies - Transitive:
and implies
Thus it is an equivalence relation
-
is the equivalence class of -
is an representative of - e.g. if
,
- e.g. if
-
is the quotient of by - i.e. the set of all equivalence classes
Minimization Algorithm
- Let DFA
- The quotient automation
- A state of
is an equivalence class of -
-
-
- The transition function is well-defined: it doesn’t matter which representative of
is chosen
-
Notes:
- Two minimal DFAs for the same language are identical, except for the names of the states
- Minimization does not work for NFAs
Decision Problems
Languages
- Membership:
? - Emptiness:
? - Finiteness:
? - Universality:
? - Intersection Emptiness:
? - Inclusion:
- Equivalence:
Membership
If the string
Run
Emptiness
If no string is accepted by the language.
Reachability to an accepting state. Use BFS or DFS.
Running time is linear in the size of the input
Finiteness
If the number of strings accepted by the language is finite.
- Check if there is a cycle on path from start state to an accept state
- Remove all states from which accept state is not reachable
- Check if the cycle is still reachable from start state
- Infinite if this is true
Running time is linear in the size of
Universality
If the language accepts all possible strings.
True if and only if
Running time is linear.
Intersection Emptiness
True if
Reduce the problem to the emptiness problem by constructing the product automaton.
- Constructing product DFA is proportional to
times : it is quadratic - Running time is linear in the size of the input: the product automaton, not
- So the running time is quadratic as well
Inclusion
If
Hence, take the complement of the
Equivalence
Use inclusion property going both ways.
Running time is quadratic.
Non-Regular Languages
Not all languages are regular. Counterexample:
- In every state, there is exactly one transition for each symbol
- There are finitely many states
- A DFA can accept infinitely long strings
- So there must be a loop
- Which means its behavior will repeat
Assume
- Pass in a string longer than
: it must go through states. Assume it is - Therefore it must repeat through one state
- Let
be the number of ’s required to get to the repeated states the first time - And
the number of ’s to get to the repeated state the second time -
-
- So
must be in the language, but can’t be in the language - So by contradiction,
can’t be a DFA
- And
Pumping Lemma
If
-
-
-
for every
Three parts: getting to cycle, looping through the cycle
To show
Set
Therefore
Hence,
Modelling Independent Processes
Shuffling:
-
: insert next character from or into output. Returns set of all possible shuffles -
Shuffling languages: every possible shuffle of every combination of two strings (one from each language)
Shuffle of the same language: NFA with
Product Automaton
Product of two automaton accepts the intersection of the two languages - strings that satisfy conditions for both languages. The languages must have the same alphabet.
Create by drawing a matrix of states, each axis representing states from one automaton (useful to have different naming schemes for each automaton such as 1, 2, 3 etc. for one and a, b, c etc. for the other).
To be in a state, it is in both states simultaneously. To determine transitions, determine the state transitions individually, and find the ‘coordinates’ for the product by concatenating the two. e.g. if you are in state a3, to determine the transition for symbol 0, let transition(dfa1, a, 0) = a and transition(dfa2, 3, 0) = 4, then draw a transition to the resultant state a4. Accept states are states with are accepting in both automata.
3. Context-Free Grammars
Context-Free Grammars
Where:
-
is a finite set of non-terminals. They are always denoted with upper case letters -
is a finite set of terminals that is disjoint from -
is a finite set of productions -
is the start symbol
Productions
A production,
Multiple productions written as
The RHS can be empty: in this case, the RHS is
The LHS is always one non-terminal but the RHS can be any length and be made up of terminals and non-terminals.
In
Derivability
On a relation
-
for each -
is derivable from in steps if for each : -
iff -
is derivable from if it is derivable in any number of steps: . This relation is the reflexive-transitive closure of the relation
Context-Free Languages
- Sentential form: any
derivable from start symbol - Sentence: sentential form containing only terminal symbols
-
is the *language generated by . i.e. all possible strings of terminals and all sentential forms - The language
is context free if for some CFG
Building Blocks
-
- Gives
where
- Gives
-
- Gives the RegExp equivalent to
- Gives the RegExp equivalent to
-
- Balanced parentheses
Leftmost and Rightmost
Leftmost: if there are multiple non-terminals, always replace the left-most one first. The choice of production will be determined by the sentence you are trying to get.
If there is more than one leftmost derivation, the sentence is ambiguous. The CFG that generates it is also ambiguous.
The CFL is inherently ambiguous if every CFG generating it is ambiguous e.g.
Regular Grammar
For each
-
-
That is, the RHS is or a terminal followed by a non-terminal
Every regular language is generated by a regular grammar:
- For each state and transition, the production will be the concatenation of the transition symbol and state it goes to
- Accept states will also have the epsilon
Every regular language is accepted by a DFA, NFA (with or without
Regular languages are a strict subset of CFLs.
Left/Right Regular Grammars
If
- Right regular:
e.g. - Left regular:
e.g. There are other forms such as , .
$ \omega \in \Sigma N\cup N\Sigma \cup { \epsilon }$ and
Chomsky Normal Form
CFG
- two non-terminals or
- one terminal
Every CFG can be transformed into Chomsky normal form (unless
-
-productions of the form
- For productions of the form
, add the production -
: and can be empty strings or be multiple characters long - Do this recursively until there are no more changes
- Then, delete all
-productions
-
- Unit-productions of the form
where exists
-
- Replace with
- Then remove all unit productions
- Non-generating non-terminals
- Any non-terminal that can never be transformed into a terminal
- Create a set of generating non-terminals: start by including any non-terminals that have a production that generates only terminals
- Then add non-terminals that have a production that generates only terminals or generating non-terminals
- Remove all productions containing non-terminals that are not in the set of generating non-terminals
- Non-reachable non-terminals
- Reachable if
for - That is, the start symbol can be transformed into the non-terminal
- Remove all productions containing non-reachable non-terminals
- Productions with RHS length
that contain terminals
- Create a new production
for each terminal that meets the criteria - Then replace the productions to use
instead of
- Productions with RHS length
(should only be left with productions with only non-terminals of this length)
- For
, create a new production and non-terminal , then let - Repeat if
is too long
- Repeat if
If
- Create a new non terminal
, production - For each production
of the start symbol, add - Make
the new start symbol
Cocke-Younger-Kasami Algorithm
Given a string
- Test membership of CFL
-
- But there are a lot of (infinitely many) strings
Chomsky normal form means that each non-terminal produces at least one terminal. Therefore, there is an upper bound on the length of derivations that need to be checked (exponential).
Use dynamic programming to improve speeds.
If the original problem is
- Build a staircase table of character indexes (one-indexing)
- Starting with the main diagonal (thus one-length substrings), for each cell, build a set,
, of non-terminals from which you can derive that character - With the next diagonals:
- Split up the string into two substrings,
and - For all non-terminals
and , if there is a production such that , add to
- Split up the string into two substrings,
Hence, the algorithm is
If
Pushdown Automata
Uses a stack. Transitions can depend on symbols at the top of the stack, and modify symbols at the top of the stack. The stack and transition alphabet can be different.
Where:
-
is the stack alphabet, a finite set -
-
is a transition function of the form i.e. based on NFA with transitions, but with a stack
For a transition to take place, there must be a normal transition, but the character on the top of the stack must be the same one as specified in the transition (unless it is
Upon reading, the element is removed from the stack, and an element is added to the stack. The syntax is character, stack symbol to read/pop stack symbol to push e.g.
For a string to be read, it must be on an accept state AND the stack must be empty.
Configuration
Where:
-
is the current state -
is the remaining input -
is contents of the stack
The configuration is a snapshot of the current state of
The language accepted by
Converting CFGs to PDAs
The resultant PDA will have two states: a start state
A transition from
For each terminal
4. Compilers
Compilers vs Interpreters
Given a high-level program
Structure of a Compiler
- Scanner/lexical analyser
- Convert source code into tokens
- Parser/syntactic analyser
- Parse into syntax tree
- Semantic analyser: annotate syntax tree
- Type checking, some optimizations
- Machine-independent optimizer
- Converts to intermediate form
- Code generator
- Converts to target code
- Machine-dependent optimizer
- Generates optimized target code
Lexical Analysis
Reads source code character-by-character, and outputs sequence of tokens to feed to parser.
In reality, the scanner is usually called by the parser, producing a token at a time.
Examples:
- Reserved words
if,then,else,while
- Symbols
+,-,<,(,),+=
- Identifiers: variable names etc.
- Number constants
- String constants
The scanner needs to distinguish between reserved words and symbols, and identifiers and constants.
Each type of token can be described using regular expressions. Reserved words/symbols may be identified by string literals, while others may be defined via a sequence of definitions.
The sequence of definitions must not be cyclic. Use NFA to do so. Example:
digit := 0-9, where0-9is a character classlower := a-zupper := A-Zletter := lower|upperidentifier := (letter|_)(letter|digit|_)*, where_is
Scanners and Automata Example: Integers
- Construct an NFA for each type of integer: end state should identify the type of integer
dec_integer := 0+ | non_zero_digit digit*oct_integer := 0(o|O)oct_digit+
- Combine NFAs using new initial state and
transitions to the start of each existing NFA - Convert to a DFA and minimize
- Mark accept states using subset construction
Operation:
- Be greedy: consume as much input as possible
- That is, while there is more input and a transition is possible
- If more than one accept state is possible, the user must specify which to return
- If the result is not accepting, return to the last accepting state
- Put corresponding symbols back into the input
- If none is possible, return an error
Extensions
Extended regular expressions:
- Eliminate at regular expression state e.g.
- Reducing
to can cause a state explosion if the expression is nested - Special automata can be devised in these cases
- Eliminate at the automaton stage
Example: C style /**/ Comments
- Delimited by multiple characters, so can’t use character classes
- Use regular expression:
- Where
is any character
- Where
Constructing the minimal DFA:
- Construct NFA for
- Convert to DFA
- Complement the DFA
- Plug result in between NFAs for
and - Convert to DFA
- Minimize
This can be automated:
- Lexical structure defined with a sequence of definitions using extended regular expressions
- Scanner generator uses the description to produce a scanner
- PLY is an example of such generator
Syntax Analysis
- Parser produces the syntax tree
- Usually produces AST
- Lookahead tokens allow it to more efficiently recognize the structure
- May generate code on the fly, instead of constructing a full syntax tree
Backus-Naur form:
- Each rule describes structure of program fragment
- Rules can be recursive
- Example:
Expression := Expression Arithmetic Expression | (Expression) | number | identifierComparison := Expression Relation ExpressionRelation := = | != | < | <= | >= | >Statement := Statement| Statement;Statement
- Terminals are single tokens (e.g.
Number,identifier)- They are the leaves of the AST
- Item on the LHS of a rule are non-terminal
Syntax Trees
- Each leaf is labelled with a terminal
- Each inner node corresponds to the application of a rule
- e.g.
if,x,>,ywould be the leaves of the tree xandyare expressions, and>is a relation- Together, they form a comparison
- The if and the comparison together are part of the
Ifrule
- e.g.
The BNF grammar can be ambiguous for a given token syntax. To fix this, the language may specify precedence rules, or the programmer may use parentheses.
In regular expressions, the choice on the left of the ‘or’ will have precedence, and this can be used for precedence. For example:
Expression := Term | Expression Additive TermAdditive := +|-Term := Factor | Term Multiplication FactorMultiplicative := *|-Factor := (Expression) | number | identifier
Types:
- Top down: LL, recursive descent
- Bottom up: LR, LALR, SLR, shift-reduce
- LR more expressive than LL: defers decisions until more information is available
- Shift-reduce more expressive, but way harder to understand
BNF Extensions
[p]meanspis optional: shorthand for-
is also allowed
Syntax Diagram
- Terminals in circles
- Non-terminals in rectangles
- Connect using arrows, loops allowed
- Start of diagram: line, with name of end result above it
- End of diagram: arrow
Recursive-Descent Parsers
Mutually recursive functions: Every addition/subtraction operand is a term, which is either a number/identifier or multiplication/division operation. Thus, this hierarchy enforces the order of operations: for an addition to occur, all of the multiplication must have already been run;
# Expression = Term((+|-)Term)*
def expression():
term() # Call function to parse term
while lookahead() in [ADD, SUB]: # Check if next term is plus or minus
consume(ADD, SUB) # Consume that next token, which is in expected tokens:
add or subtract
term()
# Term = Factor((*|/)Term)*
def term():
factor()
while lookahead() in [MUL, DIV]:
consume(MUL, DIV)
factor()
# Factor = (Expression) | number | identifier
def factor():
if lookahead() = = LPAR:
consume(LPAR)
expression()
consume(RPAR)
elif lookahead() in [NUM, ID]:
consume(NUM, ID)
else:
raise Exception
Extended NBF to Recursive-Descent Parser
Two types of recursion:
- Recursion over the structure of the regular expression
- Recursion over structure of the grammar
Operation:
- Every non-terminal
nbecomes a function - Body of function from RHS of the BNF rule:
parse(RHS) - Parse function:
- If there are conditions, use lookahead and if statements
- For star, use while loops
- For multiple tokens, just run parse on them sequentially
- If the passed token is a terminal, consume the token
Even with single token lookahead, there can be ambiguities. The language may need to be changed to resolve this.
Abstract Syntax Tree
Classes for each type token (e.g. Expression, Number, Identifier). If number or identifier, needs to store the value. If it is an expression, it needs to store the left- and right-hand operands and the operator.
Details such as parentheses are omitted: the structure of the tree contains this information.
Previously, there needed to be multiple types of arithmetic nodes e.g. (+, *), but with this, only one type of node is required.
Basically the same as parser, except it returns a object.
Semantic Analysis
- Type checking
- Type inference
- Declaration: declared multiple times in different scopes?
- Definite assignment? Assigned before use?
Type systems
- Type: a set of possible values
- Many functions cannot apply to all arguments e.g. Addition on lists won’t work
- Processors just work on bits. Garbage in, garbage out
- Type checking
- Dynamic: check at run-time
- Static: check during complication
- Also used for compiler optimizations
Attribute Grammars
An extension of CFGs:
- Define functions to calculate attributes
- Define attributes for specific terminals/non-terminals: store the name of the attribute, domain for the attribute and a list of terminals/non-terminals it applies to
- Define rules and attribute values e.g.
Here,
Instead of defining it using the star symbol, it has been defined recursively using two rules.
Synthesized attributes:
- The information is propagated bottom-up
- Type of number constant determined by scanner
- Type of identifier determined by declaration or assignments
Type checking:
- Type of identifier and expression must match, or be able to be automatically converted
Inherited attributes:
- Value of non-terminal on RHS of rule depends on other values
- Information is propagated top-down
Machine-Independent Optimization
Takes place on a syntax tree or other intermediate form. Whatever optimization is done must not change the meaning of the program.
- Constant Propagation
- When a variable is used, if that variable is assigned a constant value, the variable can be substituted with the constant.
- Constant Folding
- Constant expressions can be evaluated
- The compiler must implement the semantics of the program
- e.g. Python compiler compiling Java: must ensure integer addition uses the correct integer size
- Constant expressions can be evaluated
- Dead code elimination
- Code that does not affect the result can be eliminated
- Moving calculations out of loops
- Reorder calculations
- Unroll loops
- Removing tail recursion
- Eliminating common sub-expressions
Optimizations often lead to further optimization:
- Constant folding enables constant propagation
- Constant propagation enables further constant folding
- Constant folding enables dead-code elimination
Constant Folding: evaluating arithmetic expressions that only use constants. Use similar structure to attribute grammars, with a helper function that evaluates arithmetic expressions if the operands are known, and rules which
Code Generation
Traverses the AST, emitting code as soon as a sufficient portion is processed. It outputs a data structure representing code for a virtual machine, after which it can be further analysed and optimized.
- Expression: program fragments that yield a value
- Statements: fragments that modify the values of variables
- Declarations: information for type checking etc.
JVM
Stack:
- Calculations performed on stack
- Operations pop operands from the stack, then push the result onto the stack
sipushstands for short integer pushiadd,isub,imul,idiv
e_1 + e_2pushes the code fore_1ande_2onto the stack, and then runsiadd- The invariant is that the code for
e_1will end up adding one element to the stack - In the intermediate steps, it may add any number of elements onto the stack, but they will all be popped away
- The invariant is that the code for
Variables:
- Identifiers refer to variables
- Value of variable stored in memory: this is known as their R-value
- To access and modify the variable, use the location/address of the variable: the L value
- The L-value is fixed at compile time
- During compile time, keep a map between the variable name and L-value
- Frames
- Contains arguments, local data, calculation stack
- Local/statically allocated data offset relative to frame
- Global/dynamically allocated data in absolute memory
- Assume a single frame for this example
iload npushes value stored in locationnon to the stackistore npops value from stack, and stores it in locationn
Control flow
- Labels mark positions in the code
l1
- Unconditional jumps tells the machine to go to where the label is defined
goto l1
- Conditional jumps do so if a given condition holds
- Removes top two elements from stack
- e.g.
if_icmpeq l1: f integer compare equal if_icmpeq := =,if_icmpne := !=if_icmpge := <=,if_icmpgt := <if_icmple := >=,if_icmplt := >
Machine-Dependent Optimization
Peephole optimization: looks at only a small number of generated instructions, replacing them with a shorter/faster sequence of instructions.
Example: it may read a window of 3 instructions, working its way down one instruction at a time.
5. Computability and Complexity
Decision Problems
The algorithm must terminate for all inputs. If there is no such algorithm, L is undecidable.
Optimization problems can be encoded into decision problems: for example, to find the length of shortest path, simply check if there a path with length less than
There are uncountably many languages over the alphabet:
Every decidable language is also semi-decidable: if no is outputted, just pass it through to an endless loop.
Some undecidable languages are semi-decidable.
If
Algorithms correspond to a function
NB: Functions on numbers can be encoded using binary (otherwise, the alphabet would be infinite).
Semi-decidable:
-
: is computable iff there is an algorithm that outputs and terminates for all inputs - Partial function:
or undefined - Partially computable if there is an algorithm that outputs
whenever is defined
Turing Machines
- Extension of finite automata
- Infinite tape: cells that store a symbol
- Machine has read/write head over one cell on the tape
- Finite automaton portion of the Turing machine called the finite control
- Transitions may depend on the symbol under the head
- Transitions may change the symbol under the head
- The machine halts as soon as it reaches the accept or reject state
Where:
-
is the set of states -
is the input alphabet, which cannot contain the blank symbol -
is the tape alphabet; - That is, the input alphabet is a subset of the tape alphabet
-
is the blank symbol
-
is the start state -
is the accept state -
, is the reject state -
is the transition function:
The TM operates as follows:
- Input is placed on the tape - all other cells are set to blank
- Starts with head over the first input symbol
- If
is in state with symbol under the head, is consulted - Tape head moves in direction
: (left), (right), or (none) - Transitions in the format
, where is the symbol that should be under the head for the transition to occur, is the symbol it is replaced with, and is the direction the head should move in - Blank symbols can be introduced and removed
Notes:
-
is deterministic -
may or may not halt -
is total if accepts all inputs -
can be used to accept a language (ignoring tape contents), or to compute a function
Configuration
Storing the current state of the Turing machine:
-
is the current state -
is the tape contents to the left of the head -
is the tape contents under and to the right of the head
Since only one cell on the tape can be modified at a time, a configuration relation can be used to describe transitions. e.g. where
-
accepts if -
rejects w if -
is total if it halts on all inputs -
- A language is semi-decidable if it is a language generated by a Turing machine
- A language is decidable if it is a language generated by a total Turing machine
Notes:
-
computes a partial function : -
if where - The function is partially computable if it is defined by a Turing machine
- The function is computable if it is defined by a total Turing machine
Computation Models
There are many computation models e.g. TMs with multiple tapes, pushdown automata with two stacks etc. Many of these are equivalent: they accept the same languages and compute the same functions.
Church-Turing thesis: the intuitively computable functions (that is, that humans can compute - hard to formalize) are those partially computable by a Turing machine.
Chomsky Hierarchy
Where:
-
is the non-terminal set -
is the terminal set, disjoint from -
is a finite set of productions -
is the start symbol, a non-terminal
Different grammars are obtained by restricting the form of each production
- Type-0 (general):
- That is, the number of terminals and non-terminals on the LHS is bigger than
- That is, the number of terminals and non-terminals on the LHS is bigger than
- Type-1 (context-sensitive):
- LHS can’t be smaller than the RHS
- Type-2 (context-free):
- The LHS is a non-terminal
- Type-3 (regular): x$ \in N, y \in \Sigma \cup \Sigma N$
- The LHS is non-terminal, and the RHS non-terminal, or a non-terminal followed by a terminal
Notes:
- For type-1 and type-3 grammars, the
string is not covered - As you go to more general grammars, everything gets more difficult (e.g. membership problem exponential for type-1, impossible for type-0)
- Type-1 languages are closed under the complement operator
- Type-1 languages are defined by a linearly space-bounded non-deterministic automata
- The amount of tape that can be used is some multiple of the input size
- Every type-1 language is decidable
| Grammar | Language | Automata |
|---|---|---|
| Type-0 | General/Recursively Enumerable | Turing Machine (or NTM) |
| Type-1 | Context-sensitive | Linearly space-bounded non-deterministic automata |
| Type-2 | Context-free | Push-down automata (not deterministic PDA) |
| Type-3 | Regular | Deterministic-Finite Automata (or NFA) |
Undecidability
Programs can be the input of other programs e.g. compiler takes a program as input, Python interpreter.
Special Halting Problem
The following, called the special halting problem, is undecidable:
Thus, the SHP is the set of all programs which, when given itself as input, halts.
Proof
- If the SHP is decidable,
for a total TM - That is,
is a total TM that checks if a given TM halts when given itself as an input
- That is,
- Construct a TM
that is like , except that it goes into an endless loop if accepts - Thus,
halts when given itself as input. Why? -
rejects -
-
does not halt on
-
def M_SHP(program_str):
return True if exec(program_str)(program_str) halts else False
def M_dash(program_str):
if M_SHP(program_str):
while True:
continue
return False
M_dash(as_string(M_dash))
# If false is returned, then it does not halt so it cannot have returned false
# If it does not terminate, then it must halt, but it cannot have halted
# Hence, M_SHP cannot exist
Halting Problem
Assume there is some total TM which determines if some program given some arbitrary string terminates. If this is possible, solving the special halting problem is trivial - just pass the specific input of
Some other undecidable languages:
- If problem halts on empty tape
- If two TMs are equivalent
- If TM enters a given state
- If the language generated by a given TM is a subset of some arbitrary se (as long as it is not the empty or universal set)
Complexity Classes
Running times of programs:
Function
The program can be solved efficiently if it can be solved in polynomial time. These languages are in the complexity class
- Shortest path between two nodes in a graph
- Every CFL
-
- Deterministic primality testing
Non-Deterministic Turing Machine (NTM)
-
is the number of steps in the longest transition sequence -
- Non-deterministic polynomial time is the complexity class denoted by
-
, but does ? - An NTM can guess the output and then verify its correctness in polynomial time
Some problems known to be in
- Does a graph contain a path that visits every vertex exactly once?
- Hamilton path problem
- Given sets of integers
and an integer , is there a subset of that sums to - Partitioning a list of integers such that the sums of the two subsets are equal
- Satisfiability formula: given logic formula with Boolean variables and operations, can it be true?
Problem Complexity
Compare problem complexities by reducing one to another:
-
is polynomial-time reducible to : if there is a function that runs in polynomial time that maps every input in to every input in - Thus, if
, and , then
NP-Complete Problems
- Must be
- Must be
-hard -
If these two conditions are met, then every other
Showing that a problem is
The satisfiability problem (SAT) was the first problem shown to be
- There is an NTM for the problem running in polynomial time
- Every problem in
-
- So 3-SAT is also
-complete
- So 3-SAT is also
-
- So CLIQUE is also
-complete
- So CLIQUE is also
-
Hence, there is a tree of reductions.
CLIQUE
Finding the largest subset of the graph that is complete - all pairs of nodes are connected.
SAT
Given logic formula:
Output: can the variables be set such that the formula evaluates to true?
On a NTM, guess and verify:
- Evaluating the formula once for some given set of variables: linear time
- Use NTM to transition variables between 0 and 1 and generate all possible combinations
- Choices are possible; there is no set implementation for how the choices are picked
3-SAT
Limit the structure of logic formulas using Conjunctive Normal Form (CNF):
Every formula can be converted into CNF.
If the distributivity rule is used in highly nested formulas there is exponential blow-up as each use of it causes duplication: thus, it cannot be used.
S-SAT
Given formula in CNF with exactly 3 literals per clause:
- Push down negations using de Morgan/double negation
- New variable
for each inner node (non-literal) in the syntax tree - Require
, root node to be true -
to be true -
to be true -
- Require
- Rewrite the equivalences as clauses:
-
- Use definitions of
and to convert them into clauses:
-
- Expand short clauses with new variables
- If
-
- So now there are three variables
- If
Result is in 3-CNF, obtained in polynomial time, is satisfiable if and only if the original formula is satisfiable.