35

Finite state machine

A deterministic finite state machine is a simple computation model, widely used as an introduction to automata theory in basic CS courses. It is a simple model, equivalent to regular expression, which determines of a certain input string is Accepted or Rejected. Leaving some formalities aside, A run of a finite state machine is composed of:

  1. alphabet, a set of characters.
  2. states, usually visualized as circles. One of the states must be the start state. Some of the states might be accepting, usually visualized as double circles.
  3. transitions, usually visualized as directed arches between states, are directed links between states associated with an alphabet letter.
  4. input string, a list of alphabet characters.

A run on the machine begins at the starting state. Each letter of the input string is read; If there is a transition between the current state and another state which corresponds to the letter, the current state is changed to the new state. After the last letter was read, if the current state is an accepting state, the input string is accepted. If the last state was not an accepting state, or a letter had no corresponding arch from a state during the run, the input string is rejected.

Note: This short descruption is far from being a full, formal definition of a FSM; Wikipedia's fine article is a great introduction to the subject.

Example

For example, the following machine tells if a binary number, read from left to right, has an even number of 0s:

http://en.wikipedia.org/wiki/Finite-state_machine

  1. The alphabet is the set {0,1}.
  2. The states are S1 and S2.
  3. The transitions are (S1, 0) -> S2, (S1, 1) -> S1, (S2, 0) -> S1 and (S2, 1) -> S2.
  4. The input string is any binary number, including an empty string.

The rules:

Implement a FSM in a language of your choice.

Input

The FSM should accept the following input:

<States>       List of state, separated by space mark.
               The first state in the list is the start state.
               Accepting states begin with a capital letter.
<transitions>  One or more lines. 
               Each line is a three-tuple:
               origin state, letter, destination state)
<input word>   Zero or more characters, followed by a newline.

For example, the aforementioned machine with 1001010 as an input string, would be written as:

S1 s2
S1 0 s2
S1 1 S1
s2 0 S1
s2 1 s2
1001010

Output

The FSM's run, written as <State> <letter> -> <state>, followed by the final state. The output for the example input would be:

S1 1 -> S1
S1 0 -> s2
s2 0 -> S1
S1 1 -> S1
S1 0 -> s2
s2 1 -> s2
s2 0 -> S1
ACCEPT

For the empty input '':

S1
ACCEPT

Note: Following your comments, the S1 line (showing the first state) might be omitted, and the following output is also acceptable:

ACCEPT

For 101:

S1 1 -> S1
S1 0 -> s2
s2 1 -> s2
REJECT

For '10X':

S1 1 -> S1
S1 0 -> s2
s2 X
REJECT

Prize

A 250 rep bounty will be given to the shortest solution.

Reference implementation

A reference Python implementation is available here. Note that output requirements have been relaxed for empty-string input.

Addendum

Output format

Following popular demand, the following output is also acceptable for empty input string:

ACCEPT

or

REJECT

Without the first state written in the previous line.

State names

Valid state names are an English letter followed by any number of letters, _ and digits, much like variable names, e.g. State1, state0, STATE_0.

Input format

Like most code golfs, you can assume your input is correct.

Summary of answers:

The sed 137 solution is the shortest, ruby 145 is #2. Currently, I can't get the sed solution to work:

cat test.fsm | sed -r solution.sed
sed -r solution.sed test.fsm

both gave me:

sed: -e expression #1, char 12: unterminated `s' command

so unless It there are clarifications the bounty goes to the ruby solution.

Community
  • 1
  • 1
Adam Matan
  • 128,757
  • 147
  • 397
  • 562
  • 2
    How is this necessarily a homework question?? – MK. Jan 11 '11 at 19:43
  • 6
    @S.Lott: This is way too well-written to have been copied from a university homework page (based on all the other times I've seen horrible copies of homeworks questions). Also, from what I've seen, code-golf solutions are not always readable/clear enough to be accepted as homework solutions. – FrustratedWithFormsDesigner Jan 11 '11 at 19:46
  • In the transitions, are all states capitalized even if they are not accepting states? – Brian Jan 11 '11 at 19:47
  • 10
    The FSM is ineffable. Only those who have been touched by his noodly appendage -- the edges between the nodes of his graph, if you will -- truly understand. – Weston C Jan 11 '11 at 19:49
  • 2
    @Weston C: Amen and pass the sauce. – FrustratedWithFormsDesigner Jan 11 '11 at 19:58
  • 2
    *A nice bounty will be given to the most elegant and short solution.* In code-golf only the shortest solution should win ;-) – ChristopheD Jan 11 '11 at 20:21
  • 1
    Isn't the new home for code golf questions Programmers.SE? – Zoot Jan 11 '11 at 20:46
  • 4
    @Zoot: [Jeff Atwood says no](http://meta.stackexchange.com/questions/73332/have-code-golf-questions-been-implicitly-disallowed-on-stackoverflow-now-that-mi/73372#73372). – Brian Jan 11 '11 at 20:59
  • Still waiting for the version that just translates the input to a regular expression. – Ben Jackson Jan 11 '11 at 21:34
  • 3
    Like these Code Golf and other puzzles? Commit to the [Code Golf & Programming Puzzles area51 proposal](http://area51.stackexchange.com/proposals/4570/code-golf-programming-puzzles?referrer=oAEDY7RBfMtqY44vShJFww2). – moinudin Jan 11 '11 at 21:46
  • 3
    The sample outputs are not consistent. The empty input includes the output of "S1" whereas other outputs do not include a line for the initial or final states. – Brian Jan 11 '11 at 22:34
  • 6
    Like these code golf and other puzzles? Do not commit to the area51 proposal (because then they won't show up on SO). – Brian Jan 11 '11 at 22:36
  • 1
    Indeed, the expected behavior is not "smooth". Either the final state should always be printed or it should not be printed at all for the empty string case. Yes, it can be handled as exception but there is no need to ask for that. – Nas Banov Jan 12 '11 at 04:59
  • @Brian, Nas - What bahaviour do your prefer? – Adam Matan Jan 12 '11 at 06:37
  • 1
    @Adam Matan: i think most consistent behavior to the description "FSM's run, written as -> , followed by the final state" would be outputting all state transitions, followed by the final step, which is of the type "S9 X -> REJECTED" or "S9 ∅ -> ACCEPTED" or "s9 ∅ -> REJECTED" where ∅ symbolizes end-of-input (i was inclined to say `epsilon` but i think that was reserved for non-consuming-input transitions). I suppose that ∅ should be printed as empty character and maybe you want the decision clearly on new line, so "S9 X\nREJECTED" or "S9 \nACCEPTED" or "s9 \nREJECTED"? – Nas Banov Jan 12 '11 at 07:19
  • I would think either @Nas' behavior is rational, or the behavior as spec'd except that for empty input there should be no state printed, just ACCEPT or REJECT. - On the other hand, the quirky output format could be considered a feature, making the code-golf squirrelly, and sort of dis-favoring any particular approach to output. – MtnViewMark Jan 12 '11 at 07:48
  • Is the `REJECT` in last case because of not capital state or because of undeclared char? – Nakilon Jan 12 '11 at 08:45
  • @Nakilon: in the last case it is because there is no transition from `s2` on `X` character. *"If ... a letter had no corresponding arch from a state during the run, the input string is rejected"* . Or you can argue because it is not in the alphabet, it does not matter. But it is not because the state is not final - for a word to be recognized by FSM, all chars must be exhausted. *After the last letter was read, if the current state is an accepting state, the input string is accepted.* - that's the only rule of acceptance – Nas Banov Jan 12 '11 at 09:59
  • @Adam: Both behaviors are equally reasonable, though personally I prefer omitting the final state. You could also just make it optional, though omitting final state will usually yield shorter code. – Brian Jan 12 '11 at 15:07
  • @MtnViewMark: re quirky feature, i believe we should be bound by gentlemen rule: no unnecessary suffering just for fun :-). But otherwise this reminds me of the initial dispute (i was told) over the behavior of unix `echo` command: should `echo` w/ no arguments output anything at all (like new-line) or not? – Nas Banov Jan 13 '11 at 01:13
  • 1
    @Nas: There seems to me to be two styles of code-golf: In one, the puzzle author carefully specs input and output, and expects a working program that passes the spec. This kind of golf is about meeting the "letter of the law" using every crooked trick you can think of. In the other style, the author leaves things vague, and is usually accepts just a function. There, one has more latitude to interpret the problem and showcase your language of choice. I admit I like the first style best, because it is hard to meaningfully compare code size in the second. – MtnViewMark Jan 13 '11 at 07:24
  • Very cool. Your spec says transitions has to be one or more lines while you later show sample input that has zero transitions defined... I wrote my code to the former. Perhaps you should clarify? – Guy Sirton Jan 15 '11 at 23:48
  • @Guy: That's something I got wrong when designing mine too, all the 'inputs' above are just the final line. They all start with the states and transitions in the example. – Nemo157 Jan 19 '11 at 08:35
  • @Nas of course, I'm, following. – Adam Matan Jan 20 '11 at 06:23
  • Unless you guarantee that the input string doesn't contain a space, the problem isn't well defined. So I'll assume you do make that guarantee - but it would be good to document it. – Peter Taylor Jan 22 '11 at 11:45
  • @Brian Just seen your comment and I'm shocked it got 6 up-votes. You make out as if relocating code golfs is a bad thing and I couldn't disagree more. We launched 2 days ago with a private beta and already have 35 code golf questions, compared to a whopping 0 on SO in the same time-frame. With a separate site, we get rep (no CW) and can customise the site to suit code golf and other challenges. We can bring in more golfers and we can moderate ourselves without the concern of haters shutting us out (e.g. look at fizzbuzz). I'd love to hear why you prefer to keep them where they're unwanted. – moinudin Jan 30 '11 at 17:20
  • @marcog: Because I don't feel like having another website to visit? I'd rather have them show up a limited amount on SO then show up a lot somewhere else. – Brian Jan 31 '11 at 14:09
  • @Brian Create a tag set on http://stackexchange.com and you only have to visit one page. – moinudin Jan 31 '11 at 14:11
  • @marcog: That doesn't give me the same experience as what I have now, since that's not the only way I use SO. – Brian Jan 31 '11 at 14:22
  • @Brian I think I vaguely get what you're saying. Would you mind spending a little time writing a meta post to explain what's lacking so that it can be discussed how to give you the same / better experience? Thanks for explaining yourself rather than raging like some users do. – moinudin Jan 31 '11 at 14:28
  • @marcog: That's a reasonable request. I'll try to do that at some point today or tomorrow. – Brian Jan 31 '11 at 15:03
  • @marcog: Response sent via email. I didn't think it made a very good discussion post, though the individual sections probably make good feature requests. – Brian Feb 02 '11 at 00:28

20 Answers20

23

Python 2.7+, 201 192 187 181 179 175 171 chars

PS. After the problem was relaxed (no need to output state line on empty input), here is new code that's notably shorter. If you are on version <2.7, there is no dict comprehension, so instead of {c+o:s for o,c,s in i[1:-1]} try dict((c+o,s)for o,c,s in i[1:-1]) for the price of +5.

import sys
i=map(str.split,sys.stdin)
s=i[0][0]
for c in''.join(i[-1]):
    if s:o=s;s={c+o:s for o,c,s in i[1:-1]}.get(c+s,());print o,c,'->',s
print'ARCECJEEPCTT'[s>'Z'::2]

And its test output:

# for empty input
ACCEPT

# for input '1001010'
S1 1  ->  S1
S1 0  ->  s2
s2 0  ->  S1
S1 1  ->  S1
S1 0  ->  s2
s2 1  ->  s2
s2 0  ->  S1
ACCEPT

# for input '101'
S1 1  ->  S1
S1 0  ->  s2
s2 1  ->  s2
REJECT

# for input '10X'
S1 1  ->  S1
S1 0  ->  s2
s2 X  ->  ()
REJECT

# for input 'X10'
S1 X  ->  ()
REJECT

Previous entry (len 201):

import sys
i=list(sys.stdin)
s=i[0].split()[0]
t={}
for r in i[1:-1]:a,b,c=r.split();t[a,b]=c
try:
    for c in i[-1]:print s,c.strip(),;s=t[s,c];print' ->',s
except:print('ACCEPT','REJECT')[s>'Z'or' '<c]

I want to apologize before someone slaps me for it: the code behavior is slightly different from the original spec - per question-comments discussion. This is my illustration for the discussion.

PS. while i like the resolution ACCEPT/REJECT on the same line with the final state, it can me moved to solitude (e.g. imagine results are to be parsed by stupid script that only cares about last line being accept or reject) by adding '\n'+ (5 chars) to the last print for the price of +5 chars.

Example output:

# for empty input
S1  ACCEPT

# for input '1001010'
S1 1  -> S1
S1 0  -> s2
s2 0  -> S1
S1 1  -> S1
S1 0  -> s2
s2 1  -> s2
s2 0  -> S1
S1  ACCEPT

# for input '101'
S1 1  -> S1
S1 0  -> s2
s2 1  -> s2
s2  REJECT

# for input '10X'
S1 1  -> S1
S1 0  -> s2
s2 X REJECT

# for input 'X10'
S1 X REJECT
Community
  • 1
  • 1
Nas Banov
  • 28,347
  • 6
  • 48
  • 67
  • 15
    `print'ARCECJEEPCTT'[s>'Z'::2]` is a thing of beauty, worthy of an upvote just on its own :-) – paxdiablo Jan 20 '11 at 03:10
  • 2
    @paxdiablo: it's a travesty, i feel bad for sacrificing readability; but what can i do - i am weak, i sold my soul for 2 char less, from `print('ACCEPT','REJECT')[s>'Z']` – Nas Banov Jan 20 '11 at 03:47
  • There are 1..4 characters in the indent for the `if`. – Apalala Jan 21 '11 at 04:06
  • @Apalala: it's a tab (\t) in the source, that got displayed by S.O. as 4 spaces. Can be replaced with 1 space, will still work. – Nas Banov Jan 21 '11 at 05:57
  • if you use Python2.7 you can say `t={x[:2]:x[2]for x in i[1:-1]}` – John La Rooy Jan 21 '11 at 13:27
  • @gnibbler, thanks - updated; for hist[oe]rical reasons i am on 2.5, so don't use dict comprehension (dict generator? not even sure what's the right name for this!) – Nas Banov Jan 22 '11 at 00:21
  • @Nas Banov: `x[:2]` is a list which is unhashable. But using dict comprehension don't need `t`, `s={(x[0],x[1]):x[2]for x in i[1:-1]}.get(c+s,());` is still shorter. – Kabie Jan 22 '11 at 07:16
  • @Kabie: fixed. good idea, inlining the dictionary saved 4 chars! (impractical for performance reasons as it is) – Nas Banov Jan 22 '11 at 11:07
  • f,p=for,print would also save a few characters, no? Reducing for to f saves one character, and print to p saves another 3. Plus the comma between for,print, and you've saved 3 in total. Minute, but still there. Although I guess this would be getting to the realm of completely unreadable... – TyrantWave Jan 26 '11 at 21:02
  • @TyrantWave: no, that can't be done - `for` and `print` are statements (not functions or else) and cannot be renamed like that. try and see – Nas Banov Jan 27 '11 at 23:04
  • @Nas Banov: Blerg, forgot about that. Print can be done in Python 3.x as it's a function *there*, still no idea why I thought For could. Ah well, would have been nice =p – TyrantWave Jan 28 '11 at 08:08
21

I'm feeling retro today, my language of choice for this task is IBM Enterprise Cobol - char count 2462 4078 (Sorry, pasted from a screen oriented device, trailing spaces are a tragic side effect):

 Identification Division.               
 Program-ID. FSM.                       
 Environment Division.                  
 Data Division.                         
 Working-Storage Section.               

 01 FSM-Storage.                        

*> The current state                    
   05 Start-State      Pic X(2).        
   05 Next-State       Pic X(2).        

*> List of valid states                 
   05 FSM-State-Cnt    Pic 9.           
   05 FSM-States       Occurs 9         
                       Pic X(2).        

*> List of valid transitions            
   05 FSM-Trans-Cnt    Pic 999.         
   05 FSM-Trans        Occurs 999.      
     10 Trans-Start    Pic X(2).        
     10 Trans-Char     Pic X.           
     10 Trans-End      Pic X(2).        

*> Input                                
   05 In-Buff          Pic X(72).      

*> Some work fields                     
   05 II               Pic s9(8) binary.
   05 JJ               Pic s9(8) binary.

   05 Wk-Start         Pic X(2).        
   05 Wk-Char          Pic X.           
   05 Wk-End           Pic X(2).        
   05 Wk-Cnt           Pic 999.         

   05                  Pic X value ' '. 
     88 Valid-Input        value 'V'.   

   05                  Pic X value ' '.                 
     88 Valid-State        value 'V'.                   

   05                  Pic X value ' '.                 
     88 End-Of-States      value 'E'.                   

   05                  Pic X value ' '.                 
     88 Trans-Not-Found    value ' '.                   
     88 Trans-Found        value 'T'.                   


 Linkage Section.                                       

 01 The-Char-Area.                                      
   05 The-Char         Pic X.                           
     88 End-Of-Input       value x'13'.                 
   05 The-Next-Char    Pic X.                           

 Procedure Division.                                    

      Perform Load-States                               
      Perform Load-Transitions                          
      Perform Load-Input                                
      Perform Process-Input                             

      Goback.                                           

*> Run the machine...                                   
 Process-Input.                                         

      Move FSM-States (1) to Start-State                
      Set address of The-Char-Area to address of In-Buff

      Perform until End-Of-Input                        

        Perform Get-Next-State                          
        Set address of The-Char-Area to address of The-Next-Char

        Move Next-State to Start-State                          

      End-Perform                                               

      If Start-State (1:1) is Alphabetic-Lower                  
        Display 'REJECT'                                        
      Else                                                      
        Display 'ACCEPT'                                        
      End-If                                                    

      Exit.                                                     

*> Look up the first valid transition...                        
 Get-Next-State.                                                

      Set Trans-Not-Found to true                               
      Perform varying II from 1 by 1                            
        until (II > FSM-State-Cnt)                              
           or Trans-Found                                       

        If Start-State = Trans-Start (II)                       
          and The-Char = Trans-Char (II)                        

          Move Trans-End (II) to Next-State                     
          Set Trans-Found to true                               

        End-If                                                  

      End-Perform                                               
      Display Start-State ' ' The-Char ' -> ' Next-State        

      Exit.                                                     

*> Read the states in...                                        
 Load-States.                                                   

      Move low-values to In-Buff                         
      Accept In-Buff from SYSIN                          

      Move 0 to FSM-State-Cnt                            
      Unstring In-Buff                                   
        delimited by ' '                                 
        into FSM-States (1) FSM-States (2) FSM-States (3)
             FSM-States (4) FSM-States (5) FSM-States (6)
             FSM-States (7) FSM-States (8) FSM-States (9)
        count in FSM-State-Cnt                           
      End-Unstring                                       

      Exit.                                              

*> Read the transitions in...                            
 Load-Transitions.                                       

      Move low-values to In-Buff                         
      Accept In-Buff from SYSIN                          

      Perform varying II from 1 by 1                     
        until End-Of-States                              

        Move 0 to Wk-Cnt                                 
        Unstring In-Buff                                 
          delimited by ' '                               
          into Wk-Start Wk-Char Wk-End                   
          count in Wk-Cnt                                
        End-Unstring                                     

        If Wk-Cnt = 3                                    

          Add 1 to FSM-Trans-Cnt                         
          Move Wk-Start to Trans-Start (FSM-Trans-Cnt)   
          Move Wk-Char  to Trans-Char  (FSM-Trans-Cnt)   
          Move Wk-End   to Trans-End   (FSM-Trans-Cnt)   

          Move low-values to In-Buff                     
          Accept In-Buff from SYSIN                           

        Else                                                  

          Set End-Of-States to true                           

        End-If                                                

      End-Perform                                             

      Exit.                                                   

*> Fix input so it has newlines...the joys of mainframes      
 Load-Input.                                                  

      Perform varying II from length of In-Buff by -1         
        until Valid-Input                                     

        If In-Buff (II:1) = ' ' or In-Buff (II:1) = low-values
          Move x'13' to In-Buff (II:1)                        
        Else                                                  
          Set Valid-Input to true                             
        End-If                                                

      End-Perform                                             

      Exit.                                                   

  End Program FSM.    
Joe Zitzelberger
  • 4,238
  • 2
  • 28
  • 42
  • 3
    Cobol! So cool someone can write meaningful code in *that* language. – Adam Matan Jan 12 '11 at 06:35
  • 6
    Cobol has an undeserved bad reputation because people remember the very old days of punch cards and code sheets. The 1985 and 2002 standards have really improved the language. – Joe Zitzelberger Jan 12 '11 at 13:51
  • 3
    While I admire anyone who knows COBOL (since I work on System z), I think the idea of Code Golf is to get the _shortest_ answer. So I'm not entirely certain why this one has so many upvotes :-) – paxdiablo Jan 20 '11 at 03:57
  • The length of the code is NOT 2462 - those are only the printable ones. Spaces also count, new lines do count. It is 7230 chars as pasted and **4078** if trailing spaces are stripped. You need to rework your code to claim less than that! – Nas Banov Jan 20 '11 at 06:25
  • 2
    I like how this has more votes than the shorter implementations. – bgw Jan 20 '11 at 21:56
19

sed -- 118 137 characters

This is using the -r flag (+3), for a total of 134+3=137 characters.

$!{H;D}
/:/!{G;s/(\S*)..(\S*)/\2 \1:/}
s/(.* .)(.*\n\1 (\S*))/\1 -> \3\n\3 \2/
/-/{P;D}
/^[A-Z].* :/cACCEPT
s/( .).*/\1/
/:/!P
cREJECT

This should handle inputs without transitions correctly... hopefully it fully complies with the spec now...

Nabb
  • 3,434
  • 3
  • 22
  • 32
  • Really nice one. I should have written a better I\O spec, will remeber it for next time. – Adam Matan Jan 14 '11 at 15:53
  • This is not any `sed` - `-r` for example is GNU extension. no work with OS X sed. pls provide example of use (cmd line) with results? – Nas Banov Jan 18 '11 at 21:07
  • does not confirm to spec regarding '10X' input - does not output `s2 X` before the `REJECT` line. don't know if this should counts as violation but is good to mention somewhere. – Nas Banov Jan 19 '11 at 03:52
  • `sed -r solution.sed even.dfs` does not work, giving me an error message. Please provide a way to run the script, so I can give you the bounty. – Adam Matan Jan 22 '11 at 17:47
  • @Adam You need to use `-f solution.sed` (otherwise it assumes "solution.sed" is the actual sed expression) – Nabb Jan 23 '11 at 01:34
8

Ruby 1.9.2 - 178 190 182 177 153 161 158 154 145 characters

h={}
o=s=p
$<.map{|l|o,b,c=l.split;h[[o,b]]=c;s||=o}
o.chars{|c|puts s+' '+c+((s=h[[s,c]])?' -> '+s :'')}rescue 0
puts s&&s<'['?:ACCEPT: :REJECT

Testing Script

[
  "S1 s2
S1 0 s2
S1 1 S1
s2 0 S1
s2 1 s2
1001010",
  "S1 s2
S1 0 s2
S1 1 S1
s2 0 S1
s2 1 s2
101",
  "S1 s2
S1 0 s2
S1 1 S1
s2 0 S1
s2 1 s2
",
  "S1 s2
S1 0 s2
S1 1 S1
s2 0 S1
s2 1 s2
10X"
].each do |b|
  puts "------"
  puts "Input:"
  puts b
  puts
  puts "Output:"
  puts `echo "#{b}" | ruby fsm-golf.rb`
  puts "------"
end

Outputs

All input starts with:

S1 s2
S1 0 s2
S1 1 S1
s2 0 S1
s2 1 s2

Input: '1001010'
Output:
S1 1 -> S1
S1 0 -> s2
s2 0 -> S1
S1 1 -> S1
S1 0 -> s2
s2 1 -> s2
s2 0 -> S1
ACCEPT

Input: '101'
Output:
S1 1 -> S1
S1 0 -> s2
s2 1 -> s2
REJECT

Input: 'X10'
Output:
S1 X
REJECT

Input: ''
Output:
ACCEPT

Input: '10X'
Output:
S1 1 -> S1
S1 0 -> s2
s2 X
REJECT
Nemo157
  • 3,559
  • 19
  • 27
  • It definitely doesn't, I'll add the testing script I am using. Note that in the input definition above `states` and `transitions` are mandatory and only `input word` may be blank. The 153 char revision works with completely empty input, when I noticed that that is an invalid input I had to increase it to this 170 char version to handle the actual "blank" input. – Nemo157 Jan 19 '11 at 03:35
  • @Nemo157: i am confused here. The FSM in the example *should* return ACCEPT, that is what is specified in the problem and that is how it should be anyway, that's how FSM work. Are you saying you don't think REJECT on empty string for this FSM being a problem? – Nas Banov Jan 19 '11 at 07:48
  • @Nas: whoops, I read that description so many times, yet I always saw REJECT. That should simplify the code alot. Thanks :) – Nemo157 Jan 19 '11 at 07:50
  • You can use p instead of $~ for nil. – Nabb Jan 20 '11 at 14:29
  • Looks like you can use `h,o,s={}` to save a couple more characters. – Nabb Jan 21 '11 at 03:11
5

Adam provided a reference implementation. I didn't see it before I made mine, but the logic is similar:

Edit: This is Python 2.6 code. I did not try to minimize length; I just tried to make it conceptually simple.

import sys
a = sys.stdin.read().split('\n')
states = a[0].split()
transitions = a[1:-2]
input = a[-2]
statelist = {}
for state in states:
    statelist[state] = {}

for start, char, end in [x.split() for x in transitions]:
    statelist[start][char] = end

state = states[0]
for char in input:
    if char not in statelist[state]:
        print state,char
        print "REJECT"
        exit()
    newstate = statelist[state][char]
    print state, char, '->', newstate
    state = newstate
if state[0].upper() == state[0]:
    print "ACCEPT"
else:
    print "REJECT"
Brian
  • 25,523
  • 18
  • 82
  • 173
  • Does not handle the empty line or the `10X` example – ChristopheD Jan 11 '11 at 21:46
  • Broken: Traceback (most recent call last): File "FSM-r2.py", line 10, in for start, char, end in [x.split() for x in transitions]: ValueError: need more than 1 value to unpack – MtnViewMark Jan 12 '11 at 07:55
  • @MtnViewMark: Usage: `python FSM-r2.py < input.txt` . Input must be newline-delimited and not have a newline at the end of the final line. – Brian Jan 12 '11 at 15:05
  • @Brian: Ah, the problem explicitly states the input string will be followed by a newline. – MtnViewMark Jan 12 '11 at 16:42
4

Python, 218 characters

import sys
T=sys.stdin.read()
P=T.split()
S=P[0]
n="\n"
for L in P[-1]if T[-2]!=n else"":
 i=T.find(n+S+" "+L)
 if i<0:print S,L;S=n;break
 S=T[i:].split()[2];print S,L,"->",S
print ("REJECT","ACCEPT")[S[0].isupper()]
Keith Randall
  • 22,985
  • 2
  • 35
  • 54
4

Haskell - 252 216 204 197 192 characters

s%(c:d,t)=s++' ':c:maybe('\n':x)(\[u]->" -> "++u++'\n':u%(d,t))(lookup[s,[c]]t)
s%_|s<"["="ACCEPT\n"|1<3=x
x="REJECT\n"
p(i:j)=(words i!!0)%(last j,map(splitAt 2.words)j)
main=interact$p.lines

Conforms to output specification.

Ungolf'd version:

type State = String
type Transition = ((State, Char), State)

run :: [Transition] -> State -> String -> [String]
run ts s (c:cs) =  maybe notFound found $ lookup (s,c) ts
  where
    notFound =  stateText                 : ["REJECT"]
    found u  = (stateText ++ " -> " ++ u) : run ts u cs
    stateText = s ++ " " ++ [c]

run _ (s:_) "" | s >= 'A' && s <= 'Z' = ["ACCEPT"]
               | otherwise            = ["REJECT"]

prepAndRun :: [String] -> [String]
prepAndRun (l0:ls) = run ts s0 input
  where
    s0 = head $ words l0
    input = last ls
    ts = map (makeEntry . words) $ init ls
    makeEntry [s,(c:_),t] = ((s,c),t)

main' = interact $ unlines . prepAndRun . lines

A good puzzle is why init isn't needed in the golf'd version! Other than that, rest are all standard Haskell golf techniques.

MtnViewMark
  • 5,120
  • 2
  • 20
  • 29
4

Perl — 184 characters

(Count excluding all newlines, which are optional.)

($s)=split' ',<>;$\=$/;
while(<>){chomp;$r{$_[1].$_[0]}=$_[2]if split>2;$t=$_}
$_=$t;
1 while$s&&s/(.)(.*)/print"$s $1",($s=$r{$1.$s})?" -> $s":"";$2/e;
print$s=~/^[A-Z]/?"ACCEPT":"REJECT"

Also, this 155-character program does not implement the intermediate outputs, but executes the machine entirely as a repeated substitution on the whole FSM definition (changing the start state and input string). It was inspired by, but not derived from, the sed solution. It could be shortened by 2 characters by converting the (?:...) into a (...) and renumbering as needed.

$/="";$_=<>;
1 while s/\A(\S+)(?: +\S+)*\n(.*\n)?\1 +(.) +(.+)\n(.*\n)?\3([^\n]*)\n\z/$4\n$2$1 $3 $4\n$5$6\n/s;
print/\A[A-Z].*\n\n\z/s?"ACCEPT\n":"REJECT\n"
Kevin Reid
  • 37,492
  • 13
  • 80
  • 108
4

Python 3, Chars: 203

The output format seems a bit hard to fit.

import sys
L=[i.split()for i in sys.stdin]
N,P=L[0][0],print
for c in L[-1]and L[-1][-1]:
 if N:O,N=N,{(i[0],i[1]):i[2]for i in L[1:-1]}.get((N,c),'');P(O,c,N and'-> '+N)
P(('REJECT','ACCEPT')[''<N<'_'])
Kabie
  • 10,489
  • 1
  • 38
  • 45
4

MIXAL 898 characters

    ORIG    3910
A   ALF ACCEP
    ALF T    
    ORIG    3940
R   ALF REJEC
    ALF T    
    ORIG    3970
O   CON 0
    ALF ->   
    ORIG    3000
S   ENT6    0
T   IN  0,6(19)
    INC6    14
    JBUS    *(19)
    LDA -14,6
    JANZ    T
    LDA -28,6(9)
    DECA    30
    JAZ C
    DECA    1
    JANZ    B
C   LD2 0(10)
    ENT4    -28,6
    ENT5    9
D   JMP G
    ENT3    0
F   INC3    14
    LD1 0,3(10)
    DEC2    0,1
    J2Z M
    INC2    0,1
    DEC3    -28,6
    J3NN    U
    INC3    -28,6
    JMP F
M   INC2    0,1
    LD1 0,3(36)
    DECA    0,1
    JAZ H
    INCA    0,1
    JMP F
H   INCA    0,1
    ST2 O(10)
    LD2 1,3(10)
    STA O(36)
    ST2 O+1(37)
    OUT O(18)
    JBUS    *(18)
    JMP D
    HLT
E   LD1 0(10)
    DEC1    0,2
    J1Z B
U   OUT R(18)
    JBUS    *(18)
    HLT
B   OUT A(18)
    JBUS    *(18)
    HLT
G   STJ K
    ST5 *+1(36)
    LDA 0,4
    JAZ E
    DECA    30
    JAZ I
    DECA    1
    JANZ    W
    INCA    1
I   INCA    30
    DEC5    45
    J5NN    J
    INC5    54
    JMP K
J   INC4    1
    ENT5    9
K   JMP *
W   ST2 O(10)
    INCA    31
    STA O(36)
    STZ O+1
    OUT O(18)
    JBUS    *(18)
    JMP B
    END S

Deify Knuth!

RedPain
  • 205
  • 1
  • 5
3

Haskell - 189 characters

main=interact$r.lines
r f=g t z$last f where{(z:_):t=map words f;g _ s""|s<"["="ACCEPT\n";g([q,j,p]:_)s(i:k)|i:s==j++q=s++' ':i:" -> "++p++'\n':g t p k;g(_:y)s i=g y s i;g _ _ _="REJECT\n"}

EDIT: Does not correctly implement the output for no-transition rejection.

Line-broken version and variable guide:

-- r: run FSM
-- f: fsm definition as lines
-- z: initial state

-- g: loop function
-- t: transition table
-- s: current state
-- i: current input
-- k: rest of input

-- q: transition table match state
-- j: transition table match input
-- p: transition table next state
-- y: tail of transition table

main=interact$r.lines;
r f=g t z$last f where{
(z:_):t=map words f;
g _ s""|s<"["="ACCEPT\n";
g([q,j,p]:_)s(i:k)|i:s==j++q=s++' ':i:" -> "++p++'\n':g t p k;
g(_:y)s i=g y s i;
g _ _ _="REJECT\n"}

I got the s<"[" technique from MtnViewMark's solution; the rest is my own design. Notable characteristics:

  • The input is left as junk in the transition table. This is OK as long as the input does not contain two spaces; but note that the transition rule format is arguably unfriendly to transitioning on the space character anyway.
  • Stepping through the input string and searching the transition table are the same function.
  • Both REJECT cases are handled by the same fallthrough.
Kevin Reid
  • 37,492
  • 13
  • 80
  • 108
3

Common Lisp - 725

(defun split (string)
  (loop for i = 0 then (1+ j)
     as j = (position #\Space string :start i)
     collect (subseq string i j)
     while j))

(defun do-fsm ()
  (let* ((lines (loop for l = (read-line *standard-input* nil)
      until (not l)
     collect (split l)))
  (cur (caar lines))
  (transitions (subseq lines 1 (- (length lines) 1))))
    (if (or (loop for c across (caar (last lines))
      do (format t "~a ~a" cur c)
        when (not (loop for tr in transitions
       when (and (equal cur (car tr))
          (equal c (char (cadr tr) 0)))
       return (progn (format t " -> ~a~%"
        (setq cur (caddr tr)))
       t)
         ))
        return t)
     (lower-case-p (char cur 0)))
 (format t "~%REJECT~%")
 (format t "ACCEPT~%"))))

No real attempt to minimize the code -- Common Lisp pays a heavy penalty in the required input processing, so I don't think there's much chance of this solution winning :-)

Dr. Pain
  • 689
  • 1
  • 7
  • 16
2

Ruby — 183

h={}
r=$<.read
t=s=r.split[0]
i=r[-1]=="
"?"":r.split[-1]
r.scan(/(\S+) (.) (.+)/){|a,b,c|h[[a,b]]=c}
i.chars{|c|puts s+" #{c} -> #{s=h[[s,c]]}"}
puts s&&s[/^[A-Z]/]?"ACCEPT":"REJECT"

Really, strange output specification. Here how my works: http://ideone.com/cxweL

Community
  • 1
  • 1
Nakilon
  • 34,866
  • 14
  • 107
  • 142
2

Rexx 205 characters

(This answer went through few edits as I initially just posted some code for general interest and then decided to actually post a real solution)

Here's a Rexx version to give people a taste for that less known lanugage. Rexx http://en.wikipedia.org/wiki/REXX is an interpreted language used in IBM's VM/CMS mainframe operating system and later in IBM OS/2 (and I believe there was an Amiga variant). It's a very expressive language and an amazing general purpose/"scripting" language.

Parse pull i .
d.='~'
Do until l='';Parse pull o l d.o.l;End
Do j=1 to LENGTH(o)
t=SUBSTR(o,j,1);p=i t;i=d.i.t
If i=d. then Do;Say p;Leave;End
Say p '->' i
End
Say WORD('ACCEPT REJECT',c2d(left(i,1))%32-1)

This can be run with the Regina Rexx interpreter.

Handling the incorrect transition scenario with its unique output and also testing for uppercase is a bit expensive.

Code from some older edits below for people interested in the Rexx syntax, those aren't 100% compliant with the output requirements but are functional (all code in this answer works with the samples I pasted below but the code above handles the other required corners):

Older short version:

Parse pull i .
Do until l = ""; Parse pull o l d; t.o.l = d; End
Do j=1 to LENGTH(o); t=substr(o,j,1); Say i t "->" t.i.t; i=t.i.t; End
If LEFT(i,1)='S' then Say 'ACCEPT'; else say 'REJECT'

Longer version:

Parse pull initial . /* Rexx has a powerful built in string parser, this takes the first word into initial */

Do until letter = "" /* This style of do loops is a bit unusual, note how it doesn't matter that letter isn't defined yet */
  Parse pull origin letter destination /* Here we parse the inpt line into three words */
  transition.origin.letter = destination /* Rexx has a very powerful notion of associative containers/dictionaries, many years pre-Python */
End

/* Now we take the last line and iterate over the transitions */
Do i = 1 to LENGTH(origin) 
  t = substr(origin, i, 1) /* This is the actual letter using Rexx's string functions */
  Say initial t "->" transition.initial.t /* Say is like print */
  initial = transition.initial.t /* Perform the transition */
End

/* check for uppercase in the current state */
if left(initial, 1) = 'S' then Say 'ACCEPT'; else say 'REJECT'

Sample in/out:

S1 s2
S1 0 s2
0
S1 0 -> s2
REJECT

S1 s2
S1 0 s2
S1 1 S1
s2 0 S1
s2 1 s2
1001010
S1 1 -> S1
S1 0 -> s2
s2 0 -> S1
S1 1 -> S1
S1 0 -> s2
s2 1 -> s2
s2 0 -> S1
ACCEPT
Guy Sirton
  • 8,331
  • 2
  • 26
  • 36
2

Lua, 356

Takes any nonspace characters for states, and any non-space one characters for transition letters. Though it seems not shortest, I'll post it any way. Could save 25 chars printing tabs instead of spaces.

Readable version:

i=io.read
p=print
S={}
i():gsub("(%S+)",function (a) f=f or a S[a]={} end )
l=i"*a"
for a,t,d in l:gmatch"(%S+) (%S) (%S+)"do
    S[a][t]=d
end
I=l:match"(%S+)%s$"or"" -- fixes empty input
function F(a,i)
    t=I:sub(i,i)
    if t==""then
        p"ACCEPT"
    elseif S[a][t] then
        p(("%s %s -> %s"):format(a,t, S[a][t]))
        return F( S[a][t],i+1)
    else
        if t~=""then p(a.." "..t)end p'REJECT'
    end
end
F(f,1)

Golfed version + in- an output.

i=io.read p=print S={}i():gsub('(%S+)',function(a)f=f or a S[a]={}end)l=i'*a'for a,t,d in l:gmatch"(%S+) (%S) (%S+)"do S[a][t]=d end I=l:match'(%S+)%s$'or''function F(a,i)t=I:sub(i,i)if t==""and a:match'^%u'then p'ACCEPT'elseif S[a][t]then p(('%s %s -> %s'):format(a,t,S[a][t]))return F(S[a][t],i+1)else if t~=''then p(a.." "..t)end p'REJECT'end end F(f,1)
-- input --
A B C   
A B B
A C C
A A A
B A A 
B B B
B C C
C A A 
C B B
C C C
AABCCBCBAX
-- output --

A A -> A
A A -> A
A B -> B
B C -> C
C C -> C
C B -> B
B C -> C
C B -> B
B A -> A
REJECT
jpjacobs
  • 9,359
  • 36
  • 45
2

bash - 186 185 184 chars

declare -A a
read s x
while read f m t&&[ $m ];do a[$f $m]=$t;done
for((i=0;i-${#f};i++))do b="$s ${f:i:1}";s=${a[$b]};echo $b -\> $s;done
[ "$s" = "${s,}" ]&&echo REJECT||echo ACCEPT

Note that this does actually require bash - POSIX sh doesn't have associative arrays or the C-style for syntax (and probably doesn't have all the parameter expansions used either, although I haven't checked).

Edit: alternatively, for the exact same length,

declare -A a
read s x
while read f m t&&[ $m ];do a[$f $m]=$t;done
while [ $f ];do b="$s ${f:i:1}";f=${f:1};s=${a[$b]};echo $b -\> $s;done
[ "$s" = "${s,}" ]&&echo REJECT||echo ACCEPT
Peter Taylor
  • 4,918
  • 1
  • 34
  • 59
0

Python (2.6) ~ 269 characters.

Probably still room for improvement, hints welcome. Handles specifications I think.

import sys;a=sys.stdin.readlines();b=a[0].split()
f=b[0];d=dict((x,{})for x in b);s=''
for x,y,z in map(str.split,a[1:-1]):d[x][y]=z
for g in a[-1]:
 try:s+=f+' '+g;f=d[f][g];s+=' -> '+f+'\n'
 except:s+='\n';break
print s+("REJECT","ACCEPT")[ord(f[0])<90 and g in d[f]]
Community
  • 1
  • 1
ChristopheD
  • 112,638
  • 29
  • 165
  • 179
  • 2
    I don't think it is safe to hardcode `S1` into your application. There is no guarantee that there is an `S1` state in the FSM. – Brian Jan 11 '11 at 20:55
  • Well there is an `S1` state in this particular code-golf question we're answering, so I think that this should be acceptable. Code golf answers about not about code you'd use in real life. – ChristopheD Jan 11 '11 at 21:00
  • Wouldn't this only work for states of the form [Ss][01] and letters in [01]? – Keith Randall Jan 11 '11 at 21:33
  • @Keith Randall, Brian: I've updated my post to make it handle more generic cases. – ChristopheD Jan 11 '11 at 22:13
  • Output doesn't conform to the specification. The output for cases 1 and 3 output an additional two lines before the final ACCEPT or REJECT. – MtnViewMark Jan 12 '11 at 07:40
0

Lua - 248 227

r=...
p=print
M={}
s=r:match('(%a%d)')
for i,n,o in r:gmatch('(%a%d)%s(%d)%s(%a%d)')do
M[i]=M[i]or{}
M[i][n]=o
end
for c in r:match('%d%d+'):gmatch('(%d)')do
z=s
s=M[z][c]
p(z,c,'->',s)
end
p(s==s:upper()and'ACCEPT'or'REJECT')

check running version on codepad old version

Łukasz Gruner
  • 2,929
  • 3
  • 26
  • 28
  • does not seem to work correctly with input 'X10' and '10X' (does not confirm to spec), also seems to give error on empty input. i am not sure since i used the codepad to test. can you paste results for input "", "10X" and "X10"? – Nas Banov Jan 19 '11 at 03:38
  • "X10" - http://codepad.org/cj4GAApr || "10X" - http://codepad.org/qCKyWvBP || "" - does not work - will fix in a few hours – Łukasz Gruner Jan 19 '11 at 09:54
  • @Łukasz: to confirm, the output for "X10" is wrong (should stop at first character X instead of going S1, s2); for "10X" spec says should have shown line of the kind "s2 X" before REJECT - i imagine at least you can warn of that behavior. – Nas Banov Jan 19 '11 at 20:12
0

C# - 453 375 353 345 characters

This doesn't win (not that anyone should have expected it to), but it was fun to write anyway. I kept the leading spaces and newlines for legibility:

using System;
class P
{
  static void Main()
  {
    string c,k="";
    var t=new string[99999][];
    int p=-1,n;
    while((c=Console.ReadLine())!="")
      t[++p]=c.Split(' ');

    c=t[0][0];
    foreach(var d in t[p][0]){
      k+=c+' '+d;
      for(n=1;n<p;n++)
        if(c==t[n][0]&&d==t[n][1][0])
      {
        c=t[n][2];
        k+=" -> "+c;
        break;
      }
      k+="\n";
      if(n==p){
        c="~";
        break;
      }
    }
    Console.Write(k+(c[0]>'Z'?"REJECT":"ACCEPT"));
  }
}

In my last update I was able to save 22 characters by assuming a practical limit to the number of input rows (namely 99,999). In the worst case, you'd need to up that to the Int32 max of 2,147,483,647 which would add 5 chars. My machine doesn't like the idea of an array that long though...

An example of the execution:

>FSM.exe
S1 s2
S1 0 s2
S1 1 S1
s2 0 S1
s2 1 s2
1001010

S1 1 -> S1
S1 0 -> s2
s2 0 -> S1
S1 1 -> S1
S1 0 -> s2
s2 1 -> s2
s2 0 -> S1
ACCEPT
chezy525
  • 4,025
  • 6
  • 28
  • 41
  • Change `string[][]` into `var`. –  Jan 21 '11 at 23:46
  • there seems to be a lot of white space you could get rid of. – aarona Jan 22 '11 at 07:13
  • @yodaj007, thanks! don't know why I missed that... It also works for the type in the `foreach`... – chezy525 Jan 22 '11 at 23:19
  • @DJTripleThread, yes everything can be on one line (that's how I get the character count). However, that makes the code rather difficult to read, so I kept the line-breaks and leading spaces. – chezy525 Jan 22 '11 at 23:22
0

F# 420

Not bad for immutable golf I think. I didn't do very good on the course today though.

open System
let f,p,a=Array.fold,printf,Map.add
let l=Console.In.ReadToEnd().Split '\n'
let e,s=l.Length,l.[0].Split ' '
let t,w=Map.ofList[for q in s->q,Map.empty],[|"ACCEPT";"REJECT"|]
let m=f(fun t (r:String)->let s=r.Split ' 'in a s.[0](t.[s.[0]]|>a s.[1].[0]s.[2])t)t l.[1..e-2]
try let r=l.[e-1].ToCharArray()|>f(fun s c->p"%s %c "s c;let n=m.[s].[c]in p"-> %s\n"n;n)s.[0]in p"%s"w.[int r.[0]/97]with|_->p"%s"w.[1]

33 lines for un-golfed F#. I'll update again in a bit after I've golfed.

open System

let input = Console.In.ReadToEnd()
//let input = "S1 s2\nS1 0 s2\nS1 1 S1\ns2 0 S1\ns2 1 s2\n1001010"
let lines = input.Split '\n'
let length = lines.Length
let states = lines.[0].Split ' '

let stateMap = Map.ofList [for state in states -> (state, Map.empty)]

let folder stateMap (line:String) =
    let s = line.Split ' '
    stateMap |> Map.add s.[0] (stateMap.[s.[0]] |> Map.add s.[1].[0] s.[2])

let machine = Array.fold folder stateMap lines.[1 .. (length-2)]

let stateMachine state char =
    printf "%s %c " state char
    let newState = machine.[state].[char]
    printfn "-> %s" newState
    newState

try
    let result = 
        lines.[length-1].ToCharArray()
        |> Array.fold stateMachine states.[0]

    if Char.IsUpper result.[0] then
        printf "ACCEPT"
    else
        printf "REJECT"
with
    | _ -> printf "REJECT"
gradbot
  • 13,732
  • 5
  • 36
  • 69