Elements of Computational Thinking
Thinking abstractly, ahead, procedurally, logically and concurrently, applied to real problems rather than defined in isolation.
Abstractly, ahead and procedurally
2.1.1 Thinking abstractly
Abstraction is removing detail that is not relevant to the problem.
- Representational abstraction. Remove detail until only what is needed remains, as a Tube map removes real distances and everything above ground.
- Abstraction by generalisation. Group things by shared characteristics so one solution serves many cases, as a Vehicle class serves cars, lorries and vans.
Distinguish the reality from the model. A model is always simpler than what it represents, which is exactly why it is useful and exactly why it can mislead. A traffic simulation that assumes every driver behaves rationally will not predict real congestion.
2.1.2 Thinking ahead
Identifying in advance what will be needed.
- Inputs and outputs determined before design begins
- Preconditions: what must be true before a subroutine may be called, such as an array being sorted before a binary search
- Caching: storing results likely to be needed again, trading memory for speed
- Reusable components: designing so that parts serve future needs as well as present ones
2.1.3 Thinking procedurally
Decomposition into sub problems, deciding the order in which they must be solved, and identifying which can become reusable subroutines.
A top down approach starts with the whole problem and repeatedly divides it. A bottom up approach builds small reliable components and assembles them. Real projects use both.
Step 1 of 3 Decomposition: break the problem into parts small enough to solve one at a time.
- The real thing is full of detail: most of it is no use for the job in hand.
- The abstraction is a simple model that keeps only what the job needs.
- What stays: the stations, what connects to what, and the lines.
- What goes: the detail that does not help, such as houses, exact distances and street names.
- Start with the whole problem at the top.
- Split it into a few main parts.
- Keep splitting until every part is small enough to solve.
- Why bother: parts can be built in parallel, tested separately and reused.
Logically and concurrently
2.1.4 Thinking logically
Identifying the points where a decision is made, the conditions that determine each outcome, and every path through the program.
The discipline is completeness: for every decision, ask what happens in each case, including the boundary and the case nobody expected. Most defects live in the path that was never considered.
Nested conditions, compound conditions using AND and OR, and the correct ordering of conditions all belong here. Boolean algebra and truth tables are the formal tools.
2.1.5 Thinking concurrently
Identifying which parts of a problem can be carried out at the same time, and which cannot because they depend on earlier results.
Genuine concurrency requires multiple processors executing simultaneously. Apparent concurrency is a single processor switching between tasks so rapidly that they appear simultaneous.
Benefits: the total time falls when independent parts run in parallel, the processor is used more fully, and a system stays responsive while long work continues in the background.
Costs:
- Only the parallelisable portion benefits, and Amdahl's law caps the gain
- Data dependencies force some steps to wait
- Race conditions occur when the result depends on the order in which threads happen to access shared data
- Deadlock occurs when threads each hold a resource the other needs
- Synchronisation and coordination add overhead, and concurrent code is substantially harder to write, test and debug, because faults may appear only under particular timings
When the work divides into genuinely independent parts, when the sequential fraction is small, and when the complexity cost is repaid by the time saved. Rendering separate video frames qualifies. Applying a running total does not.
- A dependency graph: Load first, then two cleaning tasks that do not depend on each other, then Merge.
- One at a time is 8 units. Running the two cleaning tasks side by side is 6 units.
- Concurrency needs independent tasks and brings coordination costs. The longest dependent chain limits the saving.
Key terms you must be able to define
- Abstraction
- Removing detail not relevant to the problem being solved.
- Representational abstraction
- Reducing detail until only what is needed to solve the problem remains.
- Abstraction by generalisation
- Grouping things by shared characteristics so one solution serves many cases.
- Precondition
- A condition that must hold before a subroutine may correctly be called.
- Caching
- Storing results likely to be needed again, trading memory for speed.
- Decomposition
- Breaking a problem into smaller sub problems that can be solved separately.
- Concurrency
- Carrying out parts of a problem at the same time, or appearing to.
- Race condition
- A fault where the outcome depends on the unpredictable order in which threads access shared data.
- Deadlock
- A state where threads each hold a resource another needs, so none can proceed.
Flashcards
Say the definition out loud before you flip the card. Retrieving it from memory is what builds recall, reading it again is not.
Reach the top grade on this topic
- Apply each element to the scenario in the question rather than defining it
- Distinguish representational abstraction from abstraction by generalisation with an example of each
- State a precondition precisely, such as the array must be sorted in ascending order
- Identify both what can be done concurrently and what cannot, with the dependency that prevents it
- Give at least three costs of concurrency, including race conditions
Mistakes that cost marks here
- Defining the terms rather than applying them to the scenario.
- Confusing abstraction with decomposition. Abstraction removes detail, decomposition splits the problem.
- Saying concurrency always makes a program faster.
- Omitting race conditions and deadlock when asked for the difficulties of concurrency.
- Failing to identify the dependency that prevents two tasks running in parallel.
Caching, precomputation and lookup tables are all the same idea: thinking ahead. Work out in advance what you will need, so that at the moment you need it the answer is already there.
Knowledge check
Exam-style questions
Five written questions in the style of the real paper. Write a full answer, then let the marker check it against the mark scheme.
- LRepresentational abstraction removes detail until only what is needed remains
- LGives an example such as a map or a model
- LAbstraction by generalisation groups things by shared characteristics
- LGives an example such as a superclass serving several subclasses
Representational abstraction is the process of removing detail from a situation until only the information needed to solve the problem remains. A map of the London Underground is the classic example: it discards real distances, geographic direction and everything above ground, keeping only the order of stations and where lines interchange, which is precisely what a passenger needs and nothing more. Abstraction by generalisation instead groups different things together according to characteristics they share, so that one solution can serve all of them. Defining a Vehicle class with attributes and methods common to cars, vans and lorries is an example, since code written to work with Vehicle then works for every kind of vehicle without being rewritten for each.
- LA condition that must be true before a subroutine is called for it to work correctly
- LGives a valid example such as a sorted array for binary search
- LExplains that if it is not met the result is incorrect or the program fails
A precondition is a statement that must be true before a subroutine is called if that subroutine is to work correctly. It documents an assumption the subroutine makes rather than checks. For example, a binary search has the precondition that the array passed to it is already sorted in ascending order, because the algorithm decides which half to discard on the assumption that everything to the left of the midpoint is smaller. If that precondition is not met the search will still run and return a result, but the result will simply be wrong, and no error will be raised to indicate that anything went astray, which is why preconditions must be stated explicitly and guaranteed by the calling code.
- LIndependent parts of a task can be executed at the same time
- LThe total execution time is reduced and processor resources are used more fully
- LA system can remain responsive while long work continues in the background
- LOnly the parallelisable portion benefits, and Amdahl's law limits the gain
- LRace conditions can occur where the result depends on the timing of access to shared data
- LDeadlock and synchronisation overhead make concurrent code harder to write and debug
The benefit of concurrent processing is that parts of a task which do not depend on one another can be executed at the same time rather than one after another, so the total elapsed time falls and the available processing resources are used far more fully. Rendering the separate frames of an animation is a clear case, since each frame can be computed entirely independently. Concurrency also allows a system to stay responsive, running a long calculation on one thread while the user interface continues to react on another. The difficulties are substantial. Only the portion of the work that can genuinely be divided benefits, and Amdahl's law states that whatever fraction must remain sequential sets a hard ceiling on the achievable speedup regardless of how many processors are added. Data dependencies limit division further, since a step requiring the result of an earlier one cannot begin until that step has finished. Where threads share data, race conditions become possible: if two threads read, modify and write the same value without proper synchronisation, the final result depends on the order in which their operations happen to interleave, and the fault may appear only occasionally and only under particular timings, which makes it exceptionally difficult to reproduce and diagnose. Preventing this with locks introduces the risk of deadlock, where two threads each hold a resource the other is waiting for so neither can continue. Finally, the synchronisation itself consumes time, and concurrent code is significantly harder to write, test and reason about than sequential code, so the complexity cost must be genuinely repaid by the time saved.
- LApplying the filter to each image is independent so images can be processed concurrently
- LNo image's result depends on any other image
- LProducing the summary report cannot begin until all images are processed
- LThe report is a data dependency, forcing that part to be sequential
Applying the filter to the images is an ideal candidate for concurrent execution, because each image is processed entirely independently of every other one. Nothing about filtering image five depends on the outcome of filtering image four, so the set can be divided between as many threads or processors as are available, and the total time for that stage falls roughly in proportion to the number of workers used. Producing the summary report is different. It aggregates information drawn from every image, so it cannot begin until every image has been processed, which makes it a data dependency that forces the program back into sequential execution at that point. In practice the report generation itself is also likely to be a single sequential operation, and it is precisely this kind of unavoidable sequential section that Amdahl's law identifies as the limit on the overall speedup achievable.
- LA model is necessarily a simplification of the reality
- LDetail judged irrelevant is removed, but it may turn out to matter
- LAssumptions made in the model may not hold in reality
A computational model is by definition a simplification of the system it represents, since a model containing every detail of reality would be as complex as reality itself and therefore useless. Building it requires choosing which details are relevant and discarding the rest, and the accuracy of the model depends entirely on whether those choices were right. Detail that was judged irrelevant may in fact influence the outcome, and factors omitted altogether cannot be predicted at all. Beyond omission, models rest on assumptions about how the system behaves, and those assumptions may simply not hold: a traffic model assuming every driver chooses the objectively fastest route will underestimate congestion, because real drivers act on habit and incomplete information. The model can therefore be internally correct and still produce predictions that do not match what actually happens.