The Plurality Election Program in C

The same election problem in a language with no dictionary, no string equality operator and no bounds checking: arrays of structs, reading candidates from argv, comparing with strcmp, writing a search that returns a boolean, printing winners in two passes so ties need no special case, and reading a segmentation fault back to its cause.

Subject: IT Support & Networking · 62 slides · code lesson

Open the interactive version of this deck · Homework for this lesson

What this lesson covers

The lesson, slide by slide

1. The Plurality Election Program, in C

Title

IT · Programming

The same problem, in a language with no dictionary and no safety net

2. What you will be able to do

Objectives

You have solved this problem before in a language that gave you a dictionary, string comparison and bounds checking. C gives you none of the three, and that is the whole content of this session.

CS50x — Harvard's introduction to computer science, Problem Set 3 (plurality) Problem Set 3 — the assignment this deck is built around

3. What C Does Not Give You

Section

Section 1

4. Start from what you already built

Warm-up

Two minutes, from memory.

Discussion prompt

In the Python version, three language features did most of the work. Name them, and say what each one was for.

Hint: How did you look a candidate up, compare a name, and stay inside the list?

Answer:

A dictionary, which mapped a name to a count in one lookup.

The equality operator, which compared two strings by their contents.

Bounds checking, which turned a bad index into an error instead of a wrong answer.

C has none of them. Everything in this deck is you supplying one of the three by hand.

5. The three things you now do yourself

Picture it

Shorter bar means the language did it for you.

Figure (svg): Six bars comparing Python's built-in dictionary, string equality and bounds checking against the manual equivalents in C

The last row is the dangerous one: C does not report the mistake at all.

None of this makes C worse. It makes C explicit, and the assignment is graded on whether you were explicit correctly.

6. There is no dictionary, so you build one out of an array

Concept

A struct groups a name and a count into one value. An array of those structs is your tally.

typedef struct
{
    string name;
    int votes;
}
candidate;

candidate candidates[MAX];
int candidate_count;
PythonCwhy the difference
tally = {}candidate candidates[MAX]C needs the size at compile time
tally['Alice']candidates[i].nameno lookup by key; you search
len(tally)candidate_countthe array does not know how full it is
tally['Dave'] -> KeyErrorno error at allC does not check

The last row is why the vote function has to return a value saying whether the name was found.

Kernighan and Ritchie, The C Programming Language, 2nd edition, Ch. 5 and 6 Ch. 6 — structures

7. The array, in memory

Picture it

MAX slots exist from the start. Only the first candidate_count of them mean anything.

Figure (svg): Four array slots, three holding candidate names and vote counts and the fourth greyed out as unused

The array has a fixed size; a separate integer says how much of it is real.

Forgetting that second fact is the source of most bugs in this program.

8. What is in candidates[3]?

Prediction

MAX is 9, three candidates were given on the command line.

Predict first

What does candidates[3].votes hold?

  • 0, because globals are zero-initialised
  • Garbage
  • It raises an error
  • It does not exist

Correct: 0, because globals are zero-initialised.

Why: A global array in C is zero-initialised before main runs, so the unused slots really are zero. That is a fact about globals, not about arrays: the same array declared inside a function would hold whatever was on the stack. Either way the slot is meaningless, and candidate_count is what tells you to ignore it.

9. Reading the Command Line

Section

Section 2

10. main takes the words you typed

Concept

The two parameters of main are the number of words on the command line and the words themselves.

int main(int argc, string argv[])
{
    if (argc < 2)
    {
        printf("Usage: plurality [candidate ...]\n");
        return 1;
    }
    candidate_count = argc - 1;
}
what you typeargcargv[0]argv[1]candidate_count
./plurality1./plurality-usage error
./plurality Alice2./pluralityAlice1
./plurality Alice Bob Charlie4./pluralityAlice3

CS50 Manual Pages — get_int, get_string and the cs50 library — the cs50 string type is a typedef for char pointer

11. argv, laid out

Picture it

Four words on the command line, three of them candidates.

Figure (svg): The four elements of argv with argv zero marked as the program name and the remaining three as candidate names

argv[0] is the program itself, which is why the candidates start at index 1.

12. Worked example: copy the names into the array

Worked example

Take the command-line words and set up the tally.

Reject a run with no candidates

Why: argc of 1 means only the program name was given. Returning a non-zero value tells the shell it failed.

Guard against too many candidates

Why: The array is only MAX long. Writing past it is not an error in C, so this check is the only thing standing between you and memory corruption.

Copy, offsetting the index by one

Why: Candidate i comes from argv[i + 1], because argv[0] is the program.

candidate_count = argc - 1;
if (candidate_count > MAX)
{
    printf("Maximum number of candidates is %i\n", MAX);
    return 2;
}
for (int i = 0; i < candidate_count; i++)
{
    candidates[i].name = argv[i + 1];
    candidates[i].votes = 0;
}
iargv[i + 1]candidates[i].namecandidates[i].votes
0AliceAlice0
1BobBob0
2CharlieCharlie0

Verify: that the loop stops before candidate_count

Why: Three candidates means indices 0, 1 and 2. The condition uses a strict less-than, so index 3 is never touched.

13. Why the strict less-than matters

Picture it

Three candidates, four boxes reached if the condition is wrong.

Figure (svg): Four array slots with the first three valid and the fourth marked in red as outside the array

C will happily read and write slot 3. Nothing stops it.

In Python this is an IndexError and you find it immediately. In C it is a wrong answer today and a crash next Tuesday.

14. Trap: the off-by-one in the loop condition

Trap

The trap

A loop that means to cover every candidate.

Annotate

  • With candidate_count of 3 this runs for i equal to 0, 1, 2 and 3.
  • Slot 3 is outside the meaningful part of the array, and argv[4] is past the end of argv.
  • The fix is a strict less-than. A count of n means indices 0 through n minus 1.

Get a program that works until it does not

Why: Small inputs often survive. The grader's larger input does not.

The fix

Strict less-than, every time.

for (int i = 0; i < candidate_count; i++)
{
    candidates[i].name = argv[i + 1];
    candidates[i].votes = 0;
}
candidate_counti values with <i values with <=valid indices
100, 10
30, 1, 20, 1, 2, 30, 1, 2
90 to 80 to 90 to 8

Read the condition out loud as 'while i is a valid index'

Why: That phrasing makes the strict less-than obviously right, and it works for every array in every language.

15. Pattern: setting up from the command line

Pattern

Four steps, always in this order, in every C program that takes arguments.

  1. Check argc first, before touching argv at all. Reading an argument that was not supplied is undefined behaviour.
  2. Derive the count as argc minus one, because the program name occupies slot zero.
  3. Check the count against the array size before writing anything, since C will not check for you.
  4. Copy with the offset, taking argv at i plus one into slot i.
stepwhat it prevents
argc checkreading an argument that does not exist
MAX checkwriting past the end of the array
the plus onethe program name being entered as a candidate
the strict less-thanone write past the last valid slot

Kernighan and Ritchie, The C Programming Language, 2nd edition, Ch. 5 and 6 Ch. 5 — command-line arguments

16. Check: how many candidates?

Check

Solve it on paper before you click.

Check your understanding

The command is ./plurality Ada Bob. What is candidate_count?

  • A. 2 (correct)
  • B. 3
  • C. 1
  • D. 0

Answer: A

Why: argc counts every word including the program name, so argc is 3 and candidate_count is argc minus 1, which is 2. The candidates are argv[1] and argv[2].

Why B tempts people
That is argc, which includes the program name. The program is not standing for election.
Why C tempts people
That would be argc minus 2, which subtracts one candidate too many.
Why D tempts people
Zero candidates would mean the usage error fired, which happens only when argc is less than 2.

17. Comparing Strings

Section

Section 3

18. The equality operator compares addresses, not text

Concept

A string in C is a pointer to the first character. Comparing two pointers with the equality operator asks whether they point at the same place in memory, not whether the characters match.

Two identical names typed at different times live at different addresses, so the comparison is false even though the text is the same.

Kernighan and Ritchie, The C Programming Language, 2nd edition, Ch. 5 and 6 Ch. 5 — pointers and arrays

19. Two different questions

Picture it

The left panel asks about addresses. The right panel asks about characters.

Figure (svg): Two panels contrasting comparing two string pointers with the equality operator against comparing their characters with strcmp

Only one of these is the question you meant to ask.

20. strcmp answers a three-way question

Concept

strcmp does not return true or false. It returns a negative number, zero, or a positive number, telling you the alphabetical order of the two strings.

Equality is the zero case. Since zero is also what C treats as false, the correct test reads oddly the first hundred times you write it.

C reference — strcmp — the exact return-value contract

21. What strcmp actually returns

Picture it

Three comparisons, three kinds of answer.

Figure (svg): Three boxes showing strcmp returning a negative value, zero, and a positive value for three pairs of names

Zero means equal, which is the opposite of how truth values usually read.

This is also why strcmp is what you use to sort names, not just to compare them.

22. Worked example: the vote function

Worked example

Search the array for the name. If you find it, add a vote and report success. If you reach the end, report failure.

Return a boolean so the caller can react

Why: The caller prints the rejection message. Keeping the printing out of vote makes the function testable and matches the spec's separation.

Compare with strcmp and test against zero

Why: Not against true. Zero is the equal case.

Return immediately on a match

Why: One vote can only go to one candidate, and returning early also stops the loop.

Return false after the loop

Why: Reaching this line means every candidate was checked and none matched.

bool vote(string name)
{
    for (int i = 0; i < candidate_count; i++)
    {
        if (strcmp(candidates[i].name, name) == 0)
        {
            candidates[i].votes++;
            return true;
        }
    }
    return false;
}
votei = 0 Alicei = 1 Bobi = 2 Charliereturns
Alicematch--true
Bobnomatch-true
Davenononofalse
alicenononofalse

Verify: that the last row is what you want

Why: strcmp is case sensitive, so a lower-case name is rejected. If the spec wants case-insensitive matching, that is a different function and it should be a deliberate choice.

23. Step through the search for an unknown name

Invariant

Three candidates, one vote for Dave. Watch where control goes.

Step through it

Which line proves the array was not modified, and where would the increment have happened if the name had matched?

  1. First candidate compared; the difference is non-zero, so no match.
  2. Second candidate, same result.
  3. Third candidate, same again. The loop condition now fails.
  4. Control reaches the return after the loop, which can only mean nothing matched.

The return after the loop is not a fallback. It is a proof: the only way to reach it is to have compared against every candidate and failed.

24. Trap: testing strcmp for truth

Trap

The trap

The comparison written the way it reads in English.

Annotate

  • This is true whenever strcmp returns anything non-zero, which means whenever the strings are different.
  • So the program credits a vote to the first candidate whose name does not match.
  • The condition is exactly backwards, and it compiles without a single warning.

Watch every vote go to Alice

Why: Unless the voter typed Alice, in which case it goes to Bob.

The fix

Test against zero explicitly.

if (strcmp(candidates[i].name, name) == 0)
{
    candidates[i].votes++;
    return true;
}
namesstrcmp returnsbare condition== 0 condition
Alice vs Alice0falsetrue
Alice vs Bobnegativetruefalse
Charlie vs Bobpositivetruefalse

Read the comparison out loud as 'the difference is zero'

Why: strcmp measures a difference. Zero difference is a match, and saying it that way makes the test obvious.

25. Which comparison is right?

Elimination

Comparing a submitted vote against a candidate name.

Eliminate the wrong options

Which line belongs in vote?

  • A. if (strcmp(candidates[i].name, name) == 0)
  • B. if (candidates[i].name == name)
  • C. if (strcmp(candidates[i].name, name))
  • D. if (strcmp(candidates[i].name, name) == 1)

Survives elimination: A

Why: Zero means the strings are identical. The other three either compare the wrong thing, invert the test, or rely on a value the standard does not promise.

26. Match each Python idiom to its C equivalent

Matching

Same operation, different amount of machinery.

Match the pairs

  • l1. if a == b: (strings)
  • l2. tally[name] += 1
  • l3. len(candidates)
  • l4. if name in tally:
  • r1. if (strcmp(a, b) == 0)
  • r2. candidates[i].votes++ after finding i
  • r3. candidate_count, tracked by hand
  • r4. a loop that returns true on a match

Why: Every row on the right is a loop or a function call where the left is an operator. That is not C being awkward; it is C declining to hide the work, which is also why it is fast.

27. Pattern: searching an array of structs

Pattern

This loop shows up in every C assignment you will be set. Learn it as a shape.

  1. Loop over the valid indices only, using a strict less-than against the count.
  2. Compare with the right function for the type: strcmp for strings, plain equality for numbers.
  3. Act and return on the first match, which also ends the loop.
  4. Return the not-found value after the loop, because reaching that line proves nothing matched.
  5. Never print from inside the search. Return a value and let the caller decide what to say.

Kernighan and Ritchie, The C Programming Language, 2nd edition, Ch. 5 and 6 Ch. 6

28. Check: what does vote return for an unknown name?

Check

Solve it on paper before you click.

Check your understanding

The candidates are Alice, Bob and Charlie. What does vote("Dave") return, and what changes in the array?

  • A. false, and nothing in the array changes (correct)
  • B. false, but Dave is added to the array
  • C. true, with a vote credited to Alice
  • D. The program crashes

Answer: A

Why: The loop compares Dave against all three names, none matches, and control reaches the return after the loop. No increment happened, so the array is untouched.

Why B tempts people
Nothing in the function writes a new name. C arrays do not grow, and candidate_count is fixed once the command line is read.
Why C tempts people
That is what happens if the strcmp test is written without the comparison against zero, which is the trap two slides back.
Why D tempts people
There is no out-of-bounds access here. The loop stays inside the array and simply finds nothing.

29. Printing the Winner

Section

Section 4

30. Two loops, because a tie is not special

Concept

Find the highest vote count first. Then print every candidate who has it. Two passes, and ties fall out with no extra code.

CS50x — Harvard's introduction to computer science, Problem Set 3 (plurality) Problem Set 3

31. Why it has to be two passes

Picture it

You cannot print the winners until you know the maximum, and you do not know the maximum until you have seen everyone.

Figure (svg): A four step flow: first loop finds the maximum, nothing printed yet, second loop prints everyone equal to it, and a tie prints two names

One pass cannot do it, because the last candidate might be the highest.

32. Worked example: print_winner

Worked example

Two loops over the same array, doing two different jobs.

Start the maximum at zero

Why: Vote counts are never negative, so zero is a safe floor. If a candidate could have a negative score you would start from the first element instead.

First loop: keep the largest count seen

Why: Only the number is remembered, not who had it, which is what makes the tie case work.

Second loop: print everyone matching

Why: Equality, not greater-than. Every candidate on the maximum is a winner.

void print_winner(void)
{
    int max = 0;
    for (int i = 0; i < candidate_count; i++)
    {
        if (candidates[i].votes > max)
        {
            max = candidates[i].votes;
        }
    }
    for (int i = 0; i < candidate_count; i++)
    {
        if (candidates[i].votes == max)
        {
            printf("%s\n", candidates[i].name);
        }
    }
}
candidatevotesafter loop 1printed in loop 2?
Alice3max = 3yes
Bob3max still 3yes
Charlie1max still 3no

Verify: against a clear win

Why: With Alice on 3, Bob on 2 and Charlie on 1, max is 3 and only Alice prints. The same code handles both cases with no branch anywhere.

33. Watch max as the first loop runs

Invariant

Four candidates, and the maximum only ever goes up.

Step through it

What would break if the first loop used greater-than-or-equal instead of greater-than?

  1. max starts at zero, below any real count.
  2. Alice's 3 is greater, so max becomes 3.
  3. Bob also has 3, but 3 is not greater than 3, so max is unchanged. This is what preserves the tie.
  4. Charlie is lower and changes nothing. max is final.

Nothing, for the value of max. It would still end at 3. Using greater-than is a habit worth keeping anyway, because in the version that also records who is winning, the two differ.

34. Trap: printing inside the first loop

Trap

The trap

A single-pass attempt at finding and printing the winner.

Annotate

  • This prints every candidate who was ever in the lead, not the candidate who ended in the lead.
  • With Alice on 1 and Bob on 3, both print, because Alice led briefly.
  • And a genuine tie prints only the first of the tied candidates, because the second never exceeds the maximum.

Get output that is wrong in two opposite directions

Why: Too many names in one case and too few in the other.

The fix

Separate finding from printing.

int max = 0;
for (int i = 0; i < candidate_count; i++)
    if (candidates[i].votes > max)
        max = candidates[i].votes;

for (int i = 0; i < candidate_count; i++)
    if (candidates[i].votes == max)
        printf("%s\n", candidates[i].name);
tallyone-pass outputtwo-pass output
Alice 1, Bob 3Alice, BobBob
Alice 3, Bob 3AliceAlice, Bob
Alice 3, Bob 1AliceAlice

Notice the third row agrees

Why: Which is exactly why the bug survives testing: the obvious case works.

35. Which cases would a single test miss?

Prediction

You test with Alice 3, Bob 1, and the one-pass version prints Alice. Correct.

Predict first

Which input would expose the bug?

  • Any input at all
  • One where the eventual winner is not first in the array
  • One with more than nine candidates
  • One with no votes

Correct: One where the eventual winner is not first in the array.

Why: The one-pass version prints every candidate who was ever ahead. When the winner is listed first, nobody else ever leads, so the output happens to be correct. Ordering the candidates so the winner comes last is the test that exposes it.

36. Compiling and Debugging

Section

Section 5

37. The whole program

Concept

Under sixty lines, and every one of them is something this deck has covered.

#include <cs50.h>
#include <stdio.h>
#include <string.h>

#define MAX 9

typedef struct
{
    string name;
    int votes;
}
candidate;

candidate candidates[MAX];
int candidate_count;

bool vote(string name);
void print_winner(void);

int main(int argc, string argv[])
{
    if (argc < 2)
    {
        printf("Usage: plurality [candidate ...]\n");
        return 1;
    }
    candidate_count = argc - 1;
    if (candidate_count > MAX)
    {
        printf("Maximum number of candidates is %i\n", MAX);
        return 2;
    }
    for (int i = 0; i < candidate_count; i++)
    {
        candidates[i].name = argv[i + 1];
        candidates[i].votes = 0;
    }

    int voter_count = get_int("Number of voters: ");
    for (int i = 0; i < voter_count; i++)
    {
        string name = get_string("Vote: ");
        if (!vote(name))
        {
            printf("Invalid vote.\n");
        }
    }
    print_winner();
}
section of mainwhat it doesfailure it guards against
the argc checkrequires at least one candidatea run with no ballot
the MAX checkrefuses too many candidateswriting past the array
the copy loopfills the array from argv-
the voting loopreads and counts each vote-
the vote checkprints the rejectionan unknown name being counted

The two function prototypes above main are what let main call functions defined below it. C reads the file top to bottom and will not use a name it has not seen.

CS50x — Harvard's introduction to computer science, Problem Set 3 (plurality) Problem Set 3

38. Compiling and running it

Picture it

Four votes, one of them invalid.

Figure (svg): A terminal compiling the program with make and running it with three candidate names, rejecting one vote and printing the winner

The candidates come from the command line; the votes come from the prompts.

Note that make prints nothing on success. Silence is the good outcome.

39. Two symptoms worth recognising instantly

Concept

A segmentation fault means the program touched memory it does not own. In a program this size it is nearly always an index outside an array, or a string pointer that was never set.

A wrong answer with no error at all is the other symptom, and in C it usually means the same thing: you read a slot that was never written.

$ ./plurality Alice Bob
Segmentation fault (core dumped)

$ debug50 ./plurality Alice Bob
# or:
$ valgrind ./plurality Alice Bob
symptomusual cause heretool that finds it
segmentation faultan index past the end of an arraydebug50 or gdb, then read the line number
silently wrong countreading an uninitialised slotvalgrind, which reports the uninitialised read
every vote goes to one candidatethe strcmp test written without == 0reading the condition out loud
compiles but does nothingthe program was rebuilt without being re-runlook at the shell history

Valgrind Quick Start Guide — the quick start guide is genuinely ten minutes

40. Match the symptom to its cause

Matching

Five things that go wrong in this program, and what each one means.

Match the pairs

  • l1. Segmentation fault
  • l2. Every vote credited to the first candidate
  • l3. A tie prints only one name
  • l4. implicit declaration of function vote
  • l5. The program ignores the candidate names entirely
  • r1. an index outside the array, often from <= in a loop
  • r2. strcmp tested for truth rather than against zero
  • r3. printing inside the first loop instead of a second pass
  • r4. the prototype above main is missing
  • r5. the copy loop used argv[i] instead of argv[i + 1]

Why: Four of the five are things the compiler cannot detect, which is the real difference between C and a language with runtime checks. The fifth is a warning that people learn to ignore, and it should never be ignored.

41. Why does C let you read past the end of an array?

Socratic

Worth two minutes, because the answer explains a lot of C.

Discussion prompt

Python raises IndexError. C returns whatever bytes are there. Why would a language be designed that way?

Hint: What would a check cost, and on what kind of machine was C designed to run?

Answer:

Because checking every index costs a comparison and a branch on every array access, and C was designed for systems where that cost was not acceptable.

The bargain is explicit: the language will not slow you down, and in exchange you guarantee your own indices are valid.

Which means a bounds bug in C is not the language failing. It is you not holding up your end, and the tools that catch it are external ones like valgrind.

Kernighan and Ritchie, The C Programming Language, 2nd edition, Ch. 5 and 6 Ch. 5

42. The same program, two languages

Trade off

Fill in what each column costs you.

Comparison matrix

PythonC
lines of codeabout 25about 55
a bad indexIndexError, immediatelysilent corruption, or a crash much later
string comparison==strcmp, tested against zero
what you gainspeed of writingspeed of running, and control over memory

Neither is the better language. They are different bargains, and the assignment is asking you to demonstrate that you understand which one you are in.

43. Order the debugging routine

Ranking

When a C program misbehaves, in this order.

Put in order

  1. Read every compiler warning, not just the errors
  2. Check every loop condition for a strict less-than
  3. Check every strcmp for the comparison against zero
  4. Run it under the debugger and read the line number
  5. Run it under valgrind for uninitialised and out-of-bounds reads

Why: Warnings first because they are free and they name real bugs. Then the two mistakes this specific program invites. The tools come last, not because they are weak, but because in a fifty-line program the first three steps usually find it before you have finished typing the debugger command.

44. Edge Cases the Grader Will Use

Section

Section 6

45. Five inputs, and your own testing probably uses none of them

Concept

Graders do not test the case you had in mind. They test the boundaries, because that is where programs break.

CS50x — Harvard's introduction to computer science, Problem Set 3 (plurality) — check50 runs exactly this kind of input

46. The five inputs to try before submitting

Picture it

Run every one of these by hand. It takes about three minutes.

Figure (svg): Five boxes listing the edge cases: no candidates, too many candidates, zero voters, all votes invalid, and the winner listed last

Two of these test your guards; three test your logic.

The last one is the input that catches the one-pass print_winner bug, and it is the one nobody thinks of.

47. Worked example: trace zero voters

Worked example

The command line has three candidates and the user answers 0 at the prompt.

The setup loop runs normally

Why: Three candidates go into the array, each with zero votes.

The voting loop body never runs

Why: The condition is i less than zero, which is false immediately, so the loop is skipped entirely rather than running once.

print_winner still runs

Why: max starts at zero, no candidate exceeds it, so max stays zero.

Every candidate equals the maximum

Why: All three have zero votes, so all three print.

candidatevotesequals max of 0?printed
Alice0yesyes
Bob0yesyes
Charlie0yesyes

Verify: that this is defensible rather than a crash

Why: With no votes cast, every candidate genuinely is tied for the lead. The program does not crash and its output is arguably correct. If the spec wants something else it has to say so.

48. Zero voters, drawn

Picture it

Three candidates, no votes, and a maximum of zero that every one of them reaches.

Figure (svg): Three bars all at zero votes, each marked as equal to the maximum and therefore printed

A three-way tie on zero is the honest answer, not a bug.

The program neither crashes nor invents a winner, which is exactly the behaviour to aim for when the spec is silent.

49. What has the spec not told you?

Missing information

The spec says: read the candidates, read the votes, print the winner.

Discussion prompt

Name three behaviours the grader will exercise that the spec never states.

Hint: All three are about inputs a careless user would produce by accident.

Answer:

What to print when two candidates tie. Almost every spec omits it and almost every grader tests it.

Whether the comparison is case sensitive, and therefore whether a lower-case name is a valid vote.

What to do with zero voters, where every candidate ties on zero.

When the spec is silent, choose the behaviour that cannot crash and leave a comment saying which choice you made.

50. Which claim about the guards holds?

Two truths and a lie

The argc check and the MAX check at the top of main.

Eliminate the wrong options

Which is true?

  • A. The MAX check must come before the copy loop, because C will not stop the loop writing past the array.
  • B. The argc check is unnecessary, since the copy loop would simply not run.
  • C. Returning 1 and returning 2 are interchangeable.
  • D. A too-long candidate list would raise an error at runtime.

Survives elimination: A

Why: The check is not a formality; it is the only thing between the input and a buffer overrun. Placing it after the loop would be like locking the door on the way out.

51. How sure are you?

Commit first

Answer, then rate your confidence honestly.

Predict first

./plurality Alice Bob Charlie, four voters, all four vote for Charlie. What prints?

  • Charlie
  • Alice
  • Charlie four times
  • Nothing

Correct: Charlie.

Why: max becomes 4 after the first loop, and only Charlie equals it in the second, so one name prints once. The second loop prints each candidate at most once because it iterates the candidate array, not the votes.

52. What Runoff Adds

Section

Section 7

53. A ranked ballot is a two-dimensional array

Concept

Plurality stores one name per voter. Runoff stores an ordered list of preferences per voter, which in C means a rectangular array indexed by voter and by rank.

int preferences[MAX_VOTERS][MAX_CANDIDATES];

// voter j's rank-k choice, stored as a candidate index
preferences[j][k] = i;
storedpluralityrunoff
per voterone namean ordered list of candidate indices
array shapeone dimension over candidatestwo dimensions, voters by ranks
passes over the dataoneone per elimination round
what is stored in a cellnot applicablean index into candidates, not a name

Storing the index rather than the name is the trick that makes elimination cheap: marking a candidate eliminated is one boolean, and no ballot has to be rewritten.

CS50x — Harvard's introduction to computer science, Problem Set 3 (plurality) Problem Set 3 — runoff follows plurality in the same problem set

54. One row per voter, one column per rank

Picture it

Plurality only ever reads the first column.

Figure (svg): A table of three voters and three ranked preferences each, with the first-choice column highlighted

Runoff walks rightwards along a row when the candidate in the current column is eliminated.

55. What carries over, and what is new

Comparison

Fill in what runoff has to add.

Comparison matrix

piecepluralityrunoff
candidate structname and votesname, votes, and an eliminated flag
the tally loopruns onceruns once per round, over non-eliminated candidates
winning conditionhighest countmore than half of the remaining votes
what happens on no winnernot applicableeliminate the lowest and count again

Note how much carries over unchanged. Writing vote and print_winner as separate functions now is what makes runoff an extension rather than a rewrite.

56. Explain why the ballots store indices, not names

Explain it

Two sentences, out loud.

Discussion prompt

Why does runoff store a candidate index in each cell rather than the candidate's name?

Hint: How would you check whether a ballot's current choice has been eliminated?

Answer:

Because an index is a number you can use to look the candidate up directly, including their eliminated flag, without any string comparison at all.

Storing names would mean running strcmp on every cell of the table in every round, which is both slower and one more place to get the comparison wrong.

Kernighan and Ritchie, The C Programming Language, 2nd edition, Ch. 5 and 6 Ch. 5

57. Check: what does runoff reuse?

Check

Solve it on paper before you click.

Check your understanding

Which part of the plurality program carries over to runoff essentially unchanged?

  • A. The command-line setup that fills the candidate array from argv (correct)
  • B. print_winner, which needs no changes at all
  • C. The vote function, which needs no changes at all
  • D. Nothing; runoff is a separate program

Answer: A

Why: Reading candidates off the command line and guarding against too many of them is identical in both. That is why it is worth writing carefully once.

Why B tempts people
The winning condition changes from highest count to a majority of the remaining votes, so the comparison has to change.
Why C tempts people
vote now has to record a ranked preference for a given voter and rank rather than incrementing a count, so its signature changes.
Why D tempts people
The struct, the argv handling and the general shape all carry over. Treating it as a fresh start is what makes runoff feel much harder than it is.

58. Pattern: the whole assignment on one card

Pattern

Six rules, and the second and third between them account for most lost marks.

do thisinstead ofbecause
candidate_count = argc - 1argcargv[0] is the program name
i < candidate_counti <= candidate_countn items means indices 0 to n minus 1
strcmp(a, b) == 0a == b, or bare strcmp(a, b)one compares addresses, the other inverts the test
return a bool from voteprinting inside votethe caller owns the message
two loops in print_winnerone loop that prints as it goesthe maximum is not known until the end
check candidate_count against MAXtrusting the inputC will let you write past the array
  1. Compile with warnings on and read all of them.
  2. Trace the array by hand for one small input before running anything.
  3. Test with the winner listed last, which is the input that exposes the one-pass bug.

CS50x — Harvard's introduction to computer science, Problem Set 3 (plurality) Problem Set 3

59. Check: the two-loop requirement

Check

Solve it on paper before you click.

Check your understanding

Why must print_winner use two loops rather than one?

  • A. Because the maximum is not known until every candidate has been examined (correct)
  • B. Because C loops cannot contain printf
  • C. Because the array has to be sorted first
  • D. Because ties are impossible in a single loop

Answer: A

Why: Any candidate could turn out to have the highest count, including the last one. Printing during the first pass means printing before you know what the maximum is.

Why B tempts people
printf inside a loop is perfectly normal. The problem is what is known at the moment it runs, not where it sits.
Why C tempts people
Sorting would work but is more code and changes the array. Two linear passes are simpler and leave the data alone.
Why D tempts people
Ties are possible either way. The one-pass version handles them badly, which is the argument for two passes, not against.

60. Exit ticket

Exit ticket

One honest answer.

Predict first

Which of these would you least want to write from scratch right now?

  • Setting up the array from argv
  • The vote function with strcmp
  • print_winner with the two loops
  • Reading a segmentation fault back to its cause

Correct: Whichever you named is where the next session starts.

Why: The first three are each about fifteen minutes of drill. The fourth is a different kind of skill and worth a session of its own, because it transfers to every C program you will ever write rather than just this one.

61. Write it from the card, not from the slides

Connect it up

Blank file, thirty minutes.

Draw it

From the pattern card alone, write main, vote and print_winner. Compile it. Then run it with the winner listed last on the command line, and with two candidates tied.

Those two inputs are the ones the grader will use. Anything that fails them is the agenda for next session.

62. What you can do now

Recap

Five sections, and the three C-specific traps in the middle are where the marks actually go.

inputexpected output
./plurality with no candidatesusage message, exit code 1
ten candidate namesmaximum message, exit code 2
Alice 3, Bob 2, Charlie 1Alice
Alice 3, Bob 3, Charlie 1Alice then Bob
a vote for DaveInvalid vote.

CS50x — Harvard's introduction to computer science, Problem Set 3 (plurality) — the specification, the test cases, and the style checker

Sources

  1. CS50x — Harvard's introduction to computer science, Problem Set 3 (plurality)
  2. CS50 Manual Pages — get_int, get_string and the cs50 library
  3. C reference — strcmp
  4. Kernighan and Ritchie, The C Programming Language, 2nd edition, Ch. 5 and 6 — Prentice Hall, 1988
  5. GDB: The GNU Project Debugger — user manual
  6. Valgrind Quick Start Guide

Want this taught 1-on-1? Alexander tutors IT Support & Networking — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108