Computer Sciencefor Theatre & Technical Direction

Class 3 of 6 Unit 2 · Working like an engineer 4 hours · 2 at the keyboard

Data, Structure and Keeping the Work

Choosing the shape, the growth curve that ambushes you, messy real exports, and version control as an undo you can trust.

Teach mode Working rules

A production is a data problem wearing a costume. The fixtures, the cues, the frames, the crew hours and the truck are all lists of things with relationships between them, and every hour lost to a spreadsheet is an hour lost to a badly chosen shape.

Logic gym#

Fifteen minutes, on paper, in pairs. Same rule. Out loud, in a sentence, before you check.

1. Pick the shape, and say the cost#

For each of these, name the shape and say what the expensive operation would then be:

  • Every fixture in the rig, in patch order, printed on a plot.
  • "Is address 214 already used?", asked five thousand times.
  • Every cue in the show, looked up by cue number.
  • The list of departments that have signed off, where asking twice is meaningless.

The second answer is the interesting one. Say how long the list version takes on 5 000 fixtures against the set version, in orders of magnitude rather than in seconds.

2. Fifty becomes five thousand#

A check compares every fixture with every other one. On 50 fixtures it is instant.

  • How many comparisons on 50?
  • On 500?
  • On 5 000?

Say the three numbers, then say the shape of the curve in one word. Then say the thing that actually happens in a building when a check gets slow, which is not "people wait".

3. Name the invariant#

"Every fixture in this rig has a unique address range, and every range fits inside one universe."

That is an invariant: a thing that must be true before and after any change. Write two more for a show file you have worked on. Then, for each, say what breaks first when it stops being true, and whether anybody would notice.

4. Where is the fault#

Forty commits between "it worked" and "it does not". You may ask "was it good at commit N?" and you get a yes or no.

Name the commit you would ask about first, and say how many questions you need in the worst case. Then do the same for a thousand commits. If your second answer is not about ten, you are walking the list.

5. The row you would have dropped#

A cast list export has: a blank name, a duplicate name, a name with a trailing space, and a role field containing a comma.

For each, say keep, reject, or keep and report, and say who is harmed by the wrong choice. One of the four has a different right answer from the others and it is the one that costs somebody their entrance.

What the gym is for#

Class 1 held behaviour, Class 2 held values. This one holds shape and scale, which is the pair a technical director is asked about in a meeting: will it still work when there are ten times as many, and what happens to the row we cannot read.

Choosing the shape of your data#

Most bad show tooling is not badly written. It is written on the wrong shape, and every operation afterwards has to fight it.

Three shapes cover nearly everything you will do.

A list is ordered and you get at things by position. A cue stack is a list: cue 3 comes after cue 2, and that order is the meaning. Finding a specific item means looking through it from the start.

A dictionary (a map, an object, a hash) is unordered and you get at things by key. The patch is a dictionary: address 137 gives you the fixture at 137, immediately, without looking at the other 511.

A set holds unique things and answers one question fast: is this in it? Use it for "which addresses are already taken", where the only question is membership.

The cost table is the whole reason to care:

OperationListDictionarySet
Get item by positionInstantn/an/a
Find by value or keyLook through all of itInstantInstant
Add to the endInstantInstantInstant
Remove from the middleShift everything after itInstantInstant
Keep in orderYes, that is the pointInsertion order onlyNo

The mistake that appears in almost every first tool: keeping the patch as a list of fixtures and searching it every time you need one. With 40 fixtures it is instant. With 4000 fixtures, and a check that runs once per fixture, you are doing 16 million comparisons and wondering why the script takes a minute.

# The shape that fights you
for f in fixtures:                 # 4000
    for g in fixtures:             # times 4000
        if f.address == g.address and f is not g:
            print("clash", f.address)

# The shape that does not
seen = {}
for f in fixtures:                 # 4000, once
    if f.address in seen:
        print("clash", f.address, "between", seen[f.address].name, "and", f.name)
    seen[f.address] = f

Same answer. The second is roughly four thousand times less work, and it is shorter. Choosing the right shape is usually the same edit as making it simpler.


Growth, and why 50 becomes 5000#

You do not need complexity theory. You need four curves and the instinct to ask which one you are on.

ShapeName100 items10 000 items
nLinear: touch each item once10010 000
n log nSorting~700~130 000
n²Every item against every other10 000100 000 000
2ⁿTry every combinationBeyond astronomicalNever

The practical rule: anything with a loop inside a loop over the same data is n², and it is the one that ambushes you. It is invisible in testing, because you test on the small file. It appears on the real rig, on the get-in, on the day.

The other practical rule: computers are fast enough that n² is often fine. Four thousand fixtures squared is sixteen million simple operations, which is about a fifth of a second. Twenty thousand LED pixels squared is four hundred million, which is thirty seconds and a phone call. Know which side of that line you are on before you find out on the day.


Do it now: the clash check that still works at five thousand#

Write it any way you like. The tests only check the answer, which is deliberate: get it right first, then make it cheap. The obvious version compares every fixture with every other one, which on fifty fixtures is 1 225 comparisons and instant, and on five thousand is twelve million and slow enough that people stop running the check.

Then open the growth calculator and put your version's cost against a real festival patch. The third hint gives you the shape that grows with the number of fixtures rather than with its square, and the difference between those two sentences is most of what this section is for.


Real data is messy, and that is the job#

Here is a genuine line from an exported fixture list:

"Mac Aura XB",1,"137",FOH Bar 2, 4.5,"Colour, Zoom",,TRUE

Count what a program has to survive:

  • Quotes around some fields and not others, because the exporter quotes anything containing a comma
  • A comma inside a quoted field, which naive splitting on commas destroys
  • A number stored as text ("137")
  • Leading whitespace before 4.5
  • An empty field, meaning either "no value" or "nobody filled it in", which are different things
  • TRUE as text, not a boolean
  • No indication of what any column is unless the header row is right, which it often is not

Rules that hold up:

  1. Never split a CSV on commas yourself. Use the library. Python has csv built in. The library knows about quoting, escaping and line breaks inside fields, and you do not.
  2. Convert types at the boundary. The moment data enters your program, turn text into numbers and booleans, and complain loudly about anything that will not convert. A wrong value discovered at row 3 on the way in costs a minute. Discovered at row 2000 on the way out, it costs the day.
  3. Decide what an empty field means, in writing. Zero, missing, or an error. All three are reasonable and picking silently is what makes two departments' numbers disagree.
  4. Never modify the original. Read it, write a new one. Your script will have a bug on the first run. Every script does.

Do it now: the export nobody cleaned#

Five cases. The fourth is the whole exercise: a duplicate name is usable and must be reported. A tool that silently drops it produces a document that looks correct and is not, and the person who finds out is the understudy standing outside a locked door.

Rejecting and reporting are different actions. Most hurried code does one and calls it both.


Formats, and picking one on purpose#

FormatGood atBad atUse it for
CSVTables, universal, opens in ExcelAnything nested, no types, no standardFixture lists, schedules, anything the office also needs
JSONNesting, types, every language reads itHuman editing, no commentsConfiguration, network messages, saved state
YAMLHumans writing it, comments allowedIndentation errors, surprising type rulesConfiguration a human maintains by hand
XMLOld, strict, schema-checkableVerbose, painful by handWhatever the industry standard already uses
SQLiteQuerying, many records, safe concurrent readsNot readable in a text editorAnything past a few thousand rows you query repeatedly

The one rule worth memorising: if a human will maintain it by hand, choose YAML or CSV. If a program will write it and another program will read it, choose JSON. If you are querying it, put it in SQLite and stop pretending a spreadsheet is a database.

That last one deserves a sentence. A spreadsheet is a wonderful place to look at data and a terrible place to keep it: there is no rule stopping somebody typing text in a number column, no record of who changed what, and the file locks when one person opens it. Every production office in the world is running on one anyway. Knowing exactly why that hurts is what lets you propose something better without sounding like you are proposing extra work.


Version control: the thing that would have saved your file#

Your file disaster story from the preparation has the same shape as everybody else's. Version control is the industrial answer, and it is worth about four commands.

A repository is a folder whose entire history is kept. A commit is a snapshot of every file at one moment, with a message saying why. A branch is a line of commits you can start and abandon without touching the main one. Merging brings a branch back.

The mental model that makes it click: git does not store differences, it stores snapshots. Every commit is the whole state of the folder. You can go back to any of them, exactly, forever.

git init                      # this folder now has a history
git add .                     # stage everything as it currently is
git commit -m "Patch as built, pre-tech"   # snapshot it, with a reason
git log --oneline             # what happened, most recent first
git checkout a3f9c21          # go and look at that snapshot
git checkout main             # come back

Five commands cover 90 per cent of solo use. The whole field card has the rest.

What actually goes wrong, and why it is survivable#

"I broke everything." git checkout . throws away uncommitted changes and returns to the last commit. The work since your last commit is gone; everything before it is safe. This is the argument for committing often.

"I need the version from Tuesday." git log to find it, git checkout to look, copy out what you need. Nothing is ever overwritten.

"Two of us changed the same file." That is a merge conflict, and git will mark both versions in the file and refuse to guess. It is not a failure, it is git declining to make an editorial decision that is yours.

The commit message is the part that matters#

git commit -m "changes" is worthless. In six weeks it tells you nothing, and in six weeks is exactly when you need it.

Write the message for the person who will read it during a technical rehearsal, at speed, looking for the change that broke it. That person is you.

"Fix DMX offset: fixtures were one channel low because the patch is 1-based and the array is 0-based" is a message that saves an hour later. It costs eight seconds now.

What version control does not do#

It is not a backup. A repository on one laptop that falls in the dock is one laptop's worth of loss. Push it somewhere else, or it is history you kept in exactly one place.


Debugging as a method, not a mood#

Electronics for Theatre, which I also teach, does fault-finding on a bench: establish the boundary, change one thing, prove the fix by putting the fault back. Code is the same discipline with faster instruments and no smell of hot plastic to warn you.

1. Reproduce it, exactly. A fault you cannot reproduce is a fault you cannot prove you fixed. Write down the exact input and the exact steps. Half of all "intermittent" bugs turn out to be perfectly reliable once somebody writes down what they were actually doing.

2. Establish the boundary. What works, what does not, and where the line is. Print the value before it goes wrong and after. Bisect: put a print statement halfway through, learn which half contains the fault, repeat. Ten halvings gets you through a thousand lines.

3. Read the error message. All of it, from the bottom. A Python traceback names the file, the line and the type of error. Most people scroll past it because it looks like noise. It is the answer, written down, for free.

4. Change one thing. Two changes at once and you have learned nothing about either.

5. Prove it by reintroducing the fault. Put the bug back. If the symptom returns, you found the cause. If it does not, you changed something else and got lucky, and it will come back on the day.

That last step is the one everybody skips and the one that separates a fix from a coincidence.

The three questions that solve most of them#

  • Is it running the code I think it is? The wrong file, the wrong machine, an unsaved buffer, a cached build. Embarrassingly often. Put a deliberate print at the top and see it appear.
  • Is the data what I think it is? Print it. Not its length, not a summary. The actual value and its type. Most bugs are text where you expected a number.
  • Did it ever work? If yes, what changed? Git answers that one exactly, which is one more reason for the previous section.

Do it now: halve it, do not hunt it#

Forty commits, six questions. The starter walks the list, which is correct and far too slow, and the tests will fail it on the budget rows for exactly that reason.

This is git bisect, written out. Once you have built it once, the command stops being magic and becomes something you reach for at 23:40 when a show file worked on Tuesday and does not now. A thousand commits in ten questions is why it is a command rather than advice.


Keeping the work: files, names and the crew change#

Everything above is undone by a folder nobody else can navigate. The discipline is dull and it is what makes the difference between a system that survives you and one that does not.

Name files so they sort correctly. 2026-03-14 lx plot v3.pdf sorts by date automatically. FINAL_lx_plot_v3_REALLY_FINAL.pdf sorts by despair. Dates as YYYY-MM-DD, always. This is ISO 8601 and it exists precisely so that alphabetical order and chronological order are the same order.

Never put the version in the filename if you have version control. Pick one system. Two systems is no system.

Write down where the source of truth is. For every artefact: which file, on which machine, is the one that is right? An hour spent writing that down is the cheapest insurance in production.

One README in every project folder. What this is, how to run it, what it assumes, who to ask. Ten lines. It is the difference between a tool and a liability.

A tool nobody else can run is a tool that exists only until you are unavailable, and you will be unavailable exactly when it matters.


Numbers from this class#

ThingValue
Finding an item in a list of nUp to n comparisons
Finding an item in a dictionaryOne lookup, regardless of size
A loop inside a loop over the same datan², the shape that ambushes you
4000 items, compared every pair16 million operations, about 0.2 s
20 000 items, compared every pair400 million operations, about 30 s
Sorting n itemsn log n
What a git commit storesA snapshot of every file, not a difference
Commands covering 90 per cent of solo gitinit, add, commit, log, checkout
Date format that sorts correctlyISO 8601: YYYY-MM-DD
Bisections to search 1000 linesAbout 10
Where to convert text to numbersAt the boundary, on the way in
What an empty CSV field meansWhatever you decided in writing. Otherwise, a future argument
Copies of a repository needed for it to be a backupAt least two, on different machines
Lines in a project READMEAbout ten, and never zero

Self test with model answers

Answer it yourself first, out loud or on paper, then open the answer. Reading the answer without attempting the question teaches you almost nothing.

Note. These are model answers, not the only correct ones. If yours differs and you can defend it with the arithmetic and a measurement, that is worth more than matching the wording.

The four hours, accounted for. This is the lecturer's view of the session, and it is here rather than hidden in a pack because a student who can see where the time goes can see what the class is actually for.

Run of the session #

240 minutes · 130 at the keyboard · 75 on the idea

2520403540
  1. 10min Open The file disaster stories, and what each one would have cost
  2. 25min The idea Choosing a shape: list, dictionary, set, and what each is fast at
  3. 15min Lab · gym Logic gym 3, on paper. Shape, growth, and the invariant
  4. 20min The idea Growth: why a method that works on 50 fixtures dies on 5000
  5. 15min Break —
  6. 40min Lab A Parse the messy CSV you brought, and count the ways it is messy
  7. 15min The idea Formats, and the rule that picks one
  8. 35min Lab B Version control on a real folder: commit, branch, break it, get it back
  9. 40min Lab C Bisect a fault in code you did not write, on a budget of six questions
  10. 15min The idea Naming and the show-file discipline that survives a crew change
  11. 10min Close The one command you will use tomorrow

Blocks are an order of work rather than a timetable: a class that runs long at the keyboard is a class that is going well. The minutes are here so you know what you are trading against when it does.

Four hours goes fast, and most of it is at the keyboard. What you do before the class, and in particular whether the software is installed and working, decides whether you spend those hours learning or catching up.

What you must already be able to do#

Write a function with parameters and a return value, and read somebody else's signature. Convert text to a number and know why you had to. Explain the difference between a value and a reference, and why it caused the shared-cue bug in Class 2.

Three things to do#

  1. Export something real. A fixture list, a cue list, a schedule, a stock list. Get it out of whatever software owns it, as CSV. Open it in a text editor, not a spreadsheet, and look at what is actually in the file. Bring it. We will parse the ones the class brings, and they will be messier than any example I could invent, which is the point.
  2. Install git and make one commit. git init in a folder, add a file, git commit. If it asks for a name and an email, give it one. That is the whole task. Bring the output of git log.
  3. Write down the worst thing that has happened to a file you needed. Overwritten, lost, three versions and nobody knew which was current, saved on a machine that went back to the rental company. One paragraph. Everybody has one and it is the argument for the second half of the class.

What to bring#

Laptop with Python and git. Your exported CSV. The file disaster story.

The calculations this class asks for, with the working shown. Use them to check your arithmetic, never to replace it: the assessment asks for the method.

Growth: what happens at scale

Storage and transfer time

Number bases, bits and ranges

Retrieval, not rereading. Everything here is self-graded, stored in this browser only, and reported to nobody.

Spot the myth

Read the code

Fault diagnosis simulator

Numbers drill