2

I'm having trouble coming up with a proper automaton that succesfully accepts the following:

A string composed of any number of {a, b, c}* in any order of course, but n(a) + n(b) != n(c)

Initially, I had assigned 'x' to be pushed into the stack for any 'a' or 'b' and remove 'x's from the stack when a 'c' is encountered. This would loop between two states as needed. Of course, this was complicated in the case that any number of c's precedes a's and b's. I obviously can't remove from an empty stack. Any ideas or suggestions are welcome!

TacoB0t
  • 329
  • 4
  • 19

2 Answers2

1

This PDA is not trivial. You have to work with two branches (one for increment (A and B) and one for decrement (C)). The trick is to place a top-of-the-stack-marker and look for this marker. if you hit it hold on (don't go further on your band) switch branch and push negatives on your branch.

In the following I define a transition by a triple: (input, pop, push)

For example:

  1. (A,ϵ,#) means: read A from input, pop nothing, push # to the stack
  2. (ϵ,#,B) means: read nothing (stop at the actual position and don't go further in this step), pop #, push B
  3. (AB,ϵ,X) means: read A or B and push X

For your example:

S = starting state
Q1 = "switch branches" state, from here the stack is empty and we move in the branch we need in dependend of the next character read from the input band.
Q2, Q3 = States of the there-is-an-A-or-B-next branch
Q4, Q5 = States of the there-is-a-C-next branch
Q6 = State of acceptance (the only one. all other states are not accepting)

Now the transitions:

S -(ϵ,ϵ,#)-> Q1 ' # is our top-of-the-stack-marker

' first branch (for more As and Bs)
Q1 -(AB,ϵ,+)-> Q2 ' for every A or B push a + sign on the stack
Q2 -(AB,ϵ,+)-> Q2
Q2 -(C,+,ϵ)-> Q3 ' for every C consume a + sign
Q3 -(AB,ϵ,+)-> Q2
Q3 -(C,+,ϵ)-> Q3
Q3 -(ϵ,#,#)-> Q1 ' if we reach the top of the stack, return to the Q1 state
Q2 -(ϵ,#,#)-> Q1
Q2 -($,+,+)-> Q6 ' if the input is processed and the stack is not empty => go in the state of acceptance
Q3 -($,+,+)-> Q6 ' if the input is processed and the stack is not empty => go in the state of acceptance

' second branch (for more Cs)
Q1 -(C,ϵ,-)-> Q4 ' for every C push a - sign on the stack
Q4 -(C,ϵ,-)-> Q4
Q4 -(AB,-,ϵ)-> Q5 ' for every A or B consume a - sign
Q5 -(AB,-,ϵ)-> Q5
Q5 -(C,ϵ,-)-> Q4
Q5 -(ϵ,#,#)-> Q1
Q4 -(ϵ,#,#)-> Q1
Q4 -($,-,-)-> Q6 ' if the input is processed and the stack is not empty => go in the state of acceptance
Q5 -($,-,-)-> Q6 ' if the input is processed and the stack is not empty => go in the state of acceptance

Logic describtion:

We push a #-sign on the stack to detect the top of the stack. Now we operate in two "zones". The first zone handles a positive number of A and Bs compared to Cs and the other zone handles a negative number of As and Bs compared to Cs.

We're working with three signs that are part of the Stack:

  1. '# = top marker
  2. '+ = there are more As and Bs read than Cs
  3. '- = there are more Cs read than As or Bs

The first transition is from starting state to a new starting state. This transition is only used in reason of pushing the top marker on the stack. The new original starting state is never reached again. The new starting state is the switch and is hit everytime we hit the top marker on the stack. From this state we're visiting the zones in dependency of the next A and B or C.

I hope that explains it. The described automaton should give you a good idea. I think there is a little error in it, but if you follow the idea and you got it right then you can fix it :)

I build the described automaton with an online simulator to test it. You can find it here. This simulator uses epsilon as initialization for the input band and for doing nothing... so it's a bit confusing. I added some tests that can be run by clicking the green arrow next to "Bulk Testing".

Joshua K
  • 2,407
  • 1
  • 10
  • 13
  • @melpomene That was meant with the little error in here and was left for the questioner. But you'*re right. I post the full, tested solution in a few minutes. – Joshua K Oct 27 '17 at 13:08
  • @melpomene thanks for your comment. I changed it and now it should be clear, right? If not: please feel free to comment again or change the answer directly :) – Joshua K Oct 27 '17 at 13:26
  • What's the `$` thing? Is that allowed? I thought the input had to consist of `a`, `b`, `c` only. – melpomene Oct 27 '17 at 13:28
  • @melpomene `$` is the blank sign on the band. The input band of an automaton is preset with blank signs (in my case `$` signs) and the input is anywhere on this band. The read pointer points at the first character of the input. But after the word you came to a blank sign (or in my case `$` sign). You can also use epsilon or underscore or whatever you want. – Joshua K Oct 27 '17 at 13:40
  • Awesome! After adding the b transitions, it worked great. Thanks! – TacoB0t Oct 31 '17 at 02:33
1

We can try to simplify the problem somewhat. You want n(a) + n(b) != n(c). In the absence of other restrictions, this means that as and bs are indistinguishable as far as the PDA is concerned; so this language is the same as n(a) != n(c) (if we make the PDA pretend that bs are as). If we can make a PDA for this simpler language, we are done.

We might ask whether the goal is a deterministic or just any PDA. Not all context-free languages have deterministic PDAs, so, in general, PDA refers to nondeterministic ones. If we assume this is fine for this problem, we can further simplify the condition n(a) != n(c) by writing down the equivalent condition n(a) < n(c) or n(a) > n(c). This makes our lives a little easier since now we only need to come up with a PDA for n(a) < n(c), and then we're done: a PDA for our language will branch nondeterministically to two PDAs, one for each of the directions.

So how do we get a PDA for n(a) < n(c)? Well, we can start with a PDA for n(a) = n(c) and, instead of accepting with no input and an empty stack, accept with no input and a stack indicating there were more cs than as. What does a PDA for n(a) = n(c) look like? We need a strategy. The trick is to use the stack to keep track of the running difference n(c) - n(a) as follows:

  • if n(c) - n(a) = 0, the stack is empty and the topmost symbol is Z, the bottom of the stack symbol.
  • if n(c) - n(a) > 0, the stack contains n(c) - n(a) instances of c.
  • if n(c) - n(a) < 0, the stack contains n(a) - n(c) instances of a.

To accept n(a) = n(c), we read as and cs and exhaust the input. Then, we see what's on top of the stack:

  • If the stack is empty, n(a) = n(c) (see the first bullet above)
  • If the stack has a c on top, n(a) < n(c) (see the second bullet above)
  • If the stack has a a on top, n(a) > n(c) (see the third bullet above)

To accept n(a) = n(c), we transition nondeterministically to a new accepting state q whenever we see the bottom-of-stack symbol Z on top of the stack.

To accept n(a) < n(c), we transition nondeterministically to a new accepting state q whenever we see a c on top of the stack. In this case, if you want to accept with an empty stack, you can clear out the stack in the accepting state.

To accept n(a) > n(c), we transition nondeterministically to a new accepting state q whenever we see a a on top of the stack. In this case, if you want to accept with an empty stack, you can clear out the stack in the accepting state.

Observe that we can handle n(a) < n(c) and n(a) > n(c) together; we don't even need to branch nondeterministically up front. Here's a sample implementation of the PDA just described:

Q    x    s    Q'    s'
///////////////////////
// count a           //
///////////////////////
q0   a    Z    q0    aZ
q0   a    a    q0    aa
q0   a    c    q0    e
///////////////////////
// count c           //
///////////////////////
q0   c    Z    q0    cZ
q0   c    a    q0    e
q0   c    c    q0    cc
///////////////////////
// move to accepting //
///////////////////////
q0   e    a    q     a
q0   e    c    q     c
///////////////////////
// clear stack       //
///////////////////////
q    e    a    q     e
q    e    c    q     e

You will need to add the transition rules for b like we did for a, but b is otherwise indistinguishable from a and should be treated as such.

Columns in the above table are as follows:

  • Q: the states from which transitions originate
  • x: symbols of the input alphabet which cause a transition, or the empty string e if the transition does not consume input
  • s: the topmost stack symbol
  • Q': the states in which transitions terminate
  • s': what to replace the topmost stack symbol with upon transitioning

For instance, the row

q0   a    Z    q0    aZ

encodes a transition which can be described as follows:

From state q0 with the bottom-of-stack symbol Z topmost on the stack, consume input symbol a and transition to state q0, pushing an a on top of Z.

Patrick87
  • 27,682
  • 3
  • 38
  • 73
  • Thanks for the reply! I'm just having some issues understanding the formatting for the states and transitions in your layout (Q, x, s, Q', s' ?) – TacoB0t Oct 30 '17 at 16:43
  • @TacoB0t Please take a look at an update I just made to clarify the notation. – Patrick87 Oct 30 '17 at 16:58