enum State { S1, S2, S3, S4, ... , END }
State transition(State current_state, int input) {
switch current_state
case S1:
switch input
case 100: new_state = S3
do_action1()
case 101: new_state = S4
do_action2()
default: error()
case S2:
switch input
case 100: new_state = S3
do_action3()
case 206: new_state = S11
do_action4()
case 207: new_state = S12
do_action5()
case 208: new_state = S19
do_action6()
default: error()
case S3:
switch input
...
...
return new_state
}
run_state_machine(int[] inputs) {
State state = S1
foreach input in inputs
state = transition(state, input)
}
That's it. It's simple to understand and it's clear what the legal transitions are from each state based on the input. You can attach actions and data update on each state transition to do something useful.You know, that kind of code:
if(start) {
if(!running) ...
}
if(stopped & !jumping) {
} else if(running) ...
Stop and write a proper case statement (aka a state machine). You may discover that combinations of booleans (in my example, stopped & !jumping) deserve to be their own state. The insight is that the resulting code should be one clean case statement, with no nested ifs allowed. Then you know you cover all the states/cases.That switch-case alternative you’re suggesting? That’s just another manifestation of the same concept behind “If” conditions. So, really, with case statements, you’re still implementing nested conditions, just in mixed ways (a mix of case statements and “If” conditions)
The distinguishing characteristic between state machine and bad logic is that the top level conditions are the states, rather than the inputs.
EDIT: for clarity.
[1] https://dev.to/davidkpiano/you-don-t-need-a-library-for-stat...
Me: So, each instance should represent a state, and include a list of states it could transition to, and methods to transition to them? Is that what you had in mind?
Them: Well, if that's what you think a state machine is, then sure.
Me: There are a million ways I could implement this. What context will this be used in? I can make sure I write something that meets those requirements.
Them: Do you not know what a state machine is?
Me: Of course. But how are you planning to use it?
Them: I can't believe you don't know what a state machine is.
So as far as I can tell, I have no idea how to implement one.
(I'm so glad I didn't get that job.)
They're probably glad they didn't hire you either.
...and chances are that the interviewer would be fine with any of them, or ask questions after you've shown a solution.
If I'd asked such a question, and got no sense of pushback, I'd be a little nervous about hiring that person and having them need close supervision - because they were to scared to point out ambiguity in direction.
That's the exact opposite impression I have; someone who doesn't need to ask many questions, as long as they can justify their decisions well, is definitely more preferable to someone who constantly asks and needs to be lead every little step of the way.
Asking for clarification before starting isn't anywhere close to "needs to be lead every little step". If you are hiring a work crew for roadside ditch digging than your interview method might be a good idea, but for a job where implementation details matter - bad idea. One of my more difficult professional experiences was fighting the urge to displace blame for a wasted week of work, which would have been avoided had I asked for the tasking to be described back to me - or had he spoken up and said "Do you mean this or that?"
questions like that don't show you how they'll be on the job, they're an opportunity to see their thought process, but you won't get that without talking to them.
A better answer would be either "Yep sounds good to me" or something along the lines of "Let's step back and first define what a state machine should do".
Even if GP was not getting the job it is the responsibility of a good interviewer that GP leaves the room having learned something. If GP left this interview still not knowing what a state machine is (or what the interviewer thinks a state machine is) or no closer to an implementation of a state machine then the interviewer has failed as much as the candidate.
I strongly disagree. An interview is an assessment, not a tutorial.
When the CTO says “we need a thing”, I don’t sit down and start writing. I try to learn what we’ll need so I can build something that satisfies my stakeholders’ needs.
If I were interviewing you, and said “hey userbinator, make a paragraph”, and you did that without asking WTF I was talking about, the rest of the session may not go well.
they may be glad they didn't hire them, it's hard to say how they feel, but i bet they're one of those companies out their complaining they can't seem to hire anyone at any price.
At that point, it's probably best to stop arguing, and code. They probably want to argue with you about your code, not their spec.
But when I got the reply "Do you not know what a state machine is?", I think I might have thanked them for their time, and left. I would have been sorely tempted to come back with "Yes, a state machine is an abstraction, and you seem to want a concrete implementation, but you don't seem to want to fill in the details of your requirement. Is it a good use of our time together for me to complete your spec, so there's something for me to code up?"
It sounds like they didn't want you; perhaps your face didn't fit. They say most interviews are decided in the first couple of minutes.
def next_state(state):
if state == 90000:
return 0
return state + 1
But more seriously; once I was asked to implement offsetof in an interview for an entry level C++ position. I didn't quite remember that this was impossible during the heat of the interview, but I did remember enough to not produce an incorrect solution that relies on undefined behavior. I didn't get that job. enum State { A, B, C };
enum Action { A, B };
type StateData =
| { state: State.A, data: whatever }
| { state: State.B, data: whatever }
| { state: State.C, data: whatever };
type ActionData =
| { type: Action.A, data: whatever }
| { type: Action.B, data: whatever };
function transition(prev: StateData, action: ActionData): StateData {
switch(action.type) {
case Action.A:
return { type: State.B, data: whatever };
case Action.B:
return { type: State.C, data: whatever };
}
}
Then you can for example if you were using this in react do something like function MyComponent() {
const [state, setState] = useState<StateData>({ state: State.A, data: whatever });
return <button onClick={() => setState(transition(state, { action: Action.A, data: whatever }))}>Go!</button>;
}Basically, you have a variable that holds an (immutable) object representing the current state. All states that can be assigned to that variable (all abstract states the system can be in) implement a common interface (the static type of the variable), which in particular includes a method (or methods) for asking the state what the next state is given a particular event. The method then returns the new state. Whenever an event occurs, you ask the current state stored in the variable (call its respective method) to compute the next state given the current event, and then you assign that new state returned by the method to your state variable.
The common interface usually comprises further methods that define/implement the behavior of the system in the given state. For example, if the states correspond to the different modes of an editor, there could be a method that implements what happens when a key is pressed in the specific state.
For example:
If a pull request goes from Draft->In Review the FSM might perform an "assign reviewer" action.
If a PR goes from Changes Requested->In Review, there is already a reviewer assigned, so it just performs "notify reviewer".
Depending on the system and implementation constraints one type works better than the other (e.g. Mealy machines can implement Moore machine behavior with one less state, which may save hardware).
Draft->Closed/Abandoned
Draft->In Review
Changes Requested->In Review
I'm not sure "assign reviewer" would fit either in Draft.exit() or InReview.entry(). I guess you'd implement something like "ensure reviewer assigned" in InReview.entry() instead. Which would work, but would itself contain the sort of state-dependent logic that a state machine is meant to surface.
Or, perhaps an additional transitional state.
https://cs.brown.edu/~sk/Publications/Talks/SwineBeforePerl/
https://cs.brown.edu/~sk/Publications/Papers/Published/sk-au...
typedef struct State State, *StateMachine;
typedef void (*Event)(StateMachine *sm);
struct State {
Event TurnOn;
Event TurnOff;
Event MakeStuck;
};
void IdleTransition(StateMachine *sm);
void BecomeOn(StateMachine *sm);
void BecomeOff(StateMachine *sm);
void BecomeStuck(StateMachine *sm);
const State StateOn = {
.TurnOn = IdleTransition,
.TurnOff = BecomeOff,
.MakeStuck = BecomeStuck
};
const State StateOff = {
.TurnOn = BecomeOn,
.TurnOff = IdleTransition,
.MakeStuck = BecomeStuck
};
const State StateStuck = {
.TurnOn = IdleTransition,
.TurnOff = IdleTransition,
.MakeStuck = IdleTransition
};
void IdleTransition(StateMachine *sm) { }
void BecomeOn(StateMachine *sm) { *sm = &StateOn; }
void BecomeOff(StateMachine *sm) { *sm = &StateOff; }
void BecomeStuck(StateMachine *sm) { *sm = &StateStuck; }
StateMachine sm = &StateOff;
It's still C but arguably much more concise than a switch, wouldn't you agree?This is but stupidly primitive representation / implementation of SM (yes it is useful in very simple cases). You can do way better in C. Somebody else already presented more advanced design.
On the other hand, regular expressions and things like lexers are largely based on finite-state machines so there is a ton of information related to those, but they often implement things more complex than finite-state machines in order to be more expressive.
In terms of manually implementing them yourself, I think the naive implementation based on the mathematical definition of a deterministic finite automata is pretty good for a small number of states for things like tracking program state. In particular, explicitly listing the total set of expected inputs/transition criteria, explicitly listing the states, and having a single function that takes the current state and current transition-relevant data and produces a new state.
This representation is nice because it makes the behavior inspectable in one location. This makes it easier to notice the edge cases and prevent the state representation and possible transitions from getting spread all over code. The transition function can be a switch statement or a table. Really any way of writing a two-input function with a finite number of inputs and outputs will work. Many people have also explored strongly-typed versions of this, which are worth a look as well.
# (state, event) => (next state, action)
FSM = {}
Add transitions (using enums): # Simple left down-[move*]-up sequences.
FSM[STATE.clear, EVENT.left_down ] = STATE.set_caret, 'set_insertion_point'
FSM[STATE.set_caret, EVENT.left_motion] = STATE.set_caret, 'set_insertion_point'
FSM[STATE.set_caret, EVENT.left_up ] = STATE.clear, 'nothing'
Then when an event happens you drive the FSM like so: new_state, action = FSM[state, event]
Check out https://git.sr.ht/~sforman/Xerblin/tree/trunk/item/xerblin/g... for an example.There is also a DOT file (and SVG image) of the resulting state graph (the code to generate the DOT file directly from the FSM dict is at the bottom of the mousebindings.py file.)
state = state.next(context, data);
Context just holds the global stuff of the process, data is the new input into the machine and state is the current state.A simple example is an SMTP server. State is the current command (or idle), context is email message being built, and data is the line read from the socket.
Context context = new Context();
sendSMTPBanner();
State state = new IdleState();
while (!state.isFinished) {
String data = readLine();
state = state.next(context, data);
}
Conceptually pretty simple. state_x:
... do some stuff ...
if(some_condition)
goto state_y;
else if(some_other_condition)
goto state_z;
else
goto state_n;
...
state_y:
...A long series of ifs/match/dictonary/whatever to match (current state + input → next state).
That's it.