CS50 Problem Set 1 - Role Play Dialogue Script

教授与学生对话式讲解(优化用于 Text-to-Speech)


🎭 使用说明

适用于 Gemini TTS 或其他语音合成工具

设置建议:

  • 教授声音: 男声(如 en-US-Neural2-D)- 较低沉、权威
  • 学生声音: 女声(如 en-US-Neural2-F)- 较年轻、好奇

练习方法:

  1. 先听完整对话
  2. 模仿教授的讲解风格
  3. 模仿学生的提问方式
  4. 自己扮演两个角色练习

🎬 Scene 1: Introduction - 开场

👨‍🏫 Professor

Good morning, everyone! Welcome to Problem Set One review session. I’m Professor Chen, and I’m here to help you understand these four programming challenges. Today, we’re going to solve Mario, Cash, and Credit together. Are you ready?

👩‍🎓 Student

Good morning, Professor! Yes, I’m ready. I’ve completed the assignments, but I want to make sure I truly understand the concepts behind them.

👨‍🏫 Professor

Excellent! That’s exactly the right attitude. Let’s start with a question: Have you ever played Super Mario Brothers?

👩‍🎓 Student

Yes! I love that game. Is that related to our first problem?

👨‍🏫 Professor

Exactly! In fact, our first problem asks us to recreate Mario’s pyramid blocks using hash symbols. Let me show you what I mean.


🎬 Scene 2: Problem 1 - Mario (Less Comfortable)

👨‍🏫 Professor

So, imagine we want to print a pyramid like this. For a height of four, we need:

  • Row one: three spaces, then one hash
  • Row two: two spaces, then two hashes
  • Row three: one space, then three hashes
  • Row four: zero spaces, then four hashes

Can you see a pattern here?

👩‍🎓 Student

Hmm, let me think. As we go down each row, the number of spaces decreases, and the number of hashes increases. Is that right?

👨‍🏫 Professor

Perfect! You’re absolutely right. Now, can you express that mathematically? If we call the row number “i”, starting from zero, what’s the formula?

👩‍🎓 Student

Let me see. For row i… the number of spaces would be… height minus i minus one?

👨‍🏫 Professor

Excellent! And the number of hashes?

👩‍🎓 Student

That would be i plus one!

👨‍🏫 Professor

Exactly right! Now let’s translate that into code. We need nested loops. The outer loop controls which row we’re on, and the inner loops print the spaces and hashes. Here’s how it looks.

First, we get the height from the user:

int height;
do
{
    height = get_int("Height: ");
}
while (height < 1 || height > 8);

Why do we use a do-while loop here?

👩‍🎓 Student

Because we want to keep asking until the user enters a valid number between one and eight?

👨‍🏫 Professor

Precisely! The do-while loop guarantees we ask at least once. Now, let’s print the pyramid:

for (int i = 0; i < height; i++)
{
    for (int j = 0; j < height - i - 1; j++)
    {
        printf(" ");
    }
    for (int j = 0; j < i + 1; j++)
    {
        printf("#");
    }
    printf("\n");
}

Let’s walk through this. Can you tell me what happens when i equals zero?

👩‍🎓 Student

When i is zero, the first inner loop runs… height minus zero minus one times, so three times if height is four. That prints three spaces.

Then the second inner loop runs zero plus one times, so once. That prints one hash.

Finally, we print a newline.

👨‍🏫 Professor

Perfect! You’ve got it! This is exactly how we translate patterns into code. The key is identifying the mathematical relationship first.


🎬 Scene 3: Problem 2 - Mario (More Comfortable)

👨‍🏫 Professor

Now, let’s level up. The more comfortable version asks for a double pyramid with a gap, like this:

   #  #
  ##  ##
 ###  ###
####  ####

How would you approach this?

👩‍🎓 Student

Well, it looks like two pyramids with a gap in the middle. Can we just… print two pyramids?

👨‍🏫 Professor

Exactly! Think of it as:

  1. Print leading spaces
  2. Print left pyramid hashes
  3. Print a two-space gap
  4. Print right pyramid hashes
  5. Print newline

The beauty is, we already know how to do most of this! We just extend our previous solution.

👩‍🎓 Student

Oh, I see! So we add another loop for the right pyramid?

👨‍🏫 Professor

Precisely! Here’s the code:

for (int i = 0; i < height; i++)
{
    // Leading spaces
    for (int j = 0; j < height - i - 1; j++)
    {
        printf(" ");
    }
    // Left pyramid
    for (int j = 0; j < i + 1; j++)
    {
        printf("#");
    }
    // Gap
    printf("  ");
    // Right pyramid
    for (int j = 0; j < i + 1; j++)
    {
        printf("#");
    }
    printf("\n");
}

Notice how we’re building on what we already know? That’s incremental problem-solving.

👩‍🎓 Student

That makes sense! It’s not as complicated as I thought. We just break it down into steps.

👨‍🏫 Professor

Exactly! That’s the key to programming: break big problems into smaller, manageable pieces.


🎬 Scene 4: Problem 3 - Cash (Greedy Algorithm)

👨‍🏫 Professor

Alright, let’s move on to something different. Imagine you’re working at a cash register. A customer is owed forty-one cents in change. What’s the minimum number of coins you should give them?

👩‍🎓 Student

Um, I could give them forty-one pennies, but that seems like a lot…

👨‍🏫 Professor

Right! That would be forty-one coins. Can you do better?

👩‍🎓 Student

Oh! I could give them one quarter, that’s twenty-five cents. Then… one dime, that’s ten more cents. Then one nickel, that’s five cents. And one penny. So… four coins total?

👨‍🏫 Professor

Excellent! You just implemented a greedy algorithm! You naturally chose the largest coin possible at each step. Let me ask you: why does this work?

👩‍🎓 Student

Because… using bigger coins first means we need fewer coins overall?

👨‍🏫 Professor

Exactly! And here’s an interesting fact: this greedy approach doesn’t always work for every coin system, but it does work for U.S. coins. Can you guess why?

👩‍🎓 Student

Hmm, I’m not sure. Does it have to do with how the coins are related to each other?

👨‍🏫 Professor

Precisely! Each larger coin is a multiple of the smaller coins. Twenty-five is five times five, ten is two times five. This special relationship ensures greedy gives us the optimal solution.

Now, let’s implement this in code. Here’s our algorithm:

int cents = round(dollars * 100);
int coins = 0;

coins += cents / 25;
cents %= 25;

coins += cents / 10;
cents %= 10;

coins += cents / 5;
cents %= 5;

coins += cents;

Let’s trace through an example. If we owe ninety-two cents, what happens?

👩‍🎓 Student

Okay, ninety-two divided by twenty-five is… three? So we use three quarters, and the remainder is…

👨‍🏫 Professor

Use the modulo operator. Ninety-two mod twenty-five.

👩‍🎓 Student

That’s seventeen! So we have seventeen cents left.

👨‍🏫 Professor

Good! Continue.

👩‍🎓 Student

Seventeen divided by ten is one dime, with seven cents remaining.

Seven divided by five is one nickel, with two cents remaining.

And two pennies.

So that’s three plus one plus one plus two, which equals seven coins!

👨‍🏫 Professor

Perfect! You’ve got it. Now, let me ask you something important: why do we convert dollars to cents first?

👩‍🎓 Student

Is it because of floating-point precision issues?

👨‍🏫 Professor

Exactly! Floating-point numbers can have tiny errors. By converting to cents, we work with integers, which are exact. Always remember: when dealing with money, use integers to represent the smallest unit.


🎬 Scene 5: Problem 4 - Credit (Introduction)

👨‍🏫 Professor

Now we come to our most challenging problem: Credit. Have you heard of Luhn’s Algorithm?

👩‍🎓 Student

No, I haven’t. What is it?

👨‍🏫 Professor

It’s a checksum algorithm used to validate credit card numbers. It was invented by Hans Peter Luhn, an IBM scientist, back in nineteen fifty-four. It’s still used today by every credit card company!

👩‍🎓 Student

Wow! So credit cards have built-in error detection?

👨‍🏫 Professor

Exactly! This way, if you mistype a digit, the system can often detect it. Let me show you how it works.


🎬 Scene 6: Credit - Understanding Luhn’s Algorithm

👨‍🏫 Professor

Let’s use a Visa number as an example: four zero zero three six zero zero zero zero zero zero zero zero zero one four.

Step one: Starting from the second-to-last digit, moving left, we double every other digit.

So we double the one, the zero, the zero, the zero, the zero, the zero, the zero, and the three.

👩‍🎓 Student

Wait, why the second-to-last digit, not the last?

👨‍🏫 Professor

Great question! That’s just how Luhn designed the algorithm. The last digit is actually the check digit itself.

Now, when we double these digits, we get: two, zero, zero, zero, zero, zero, zero, and six.

👩‍🎓 Student

Okay, I’m following.

👨‍🏫 Professor

Step two: If any doubled digit is ten or more, we add its digits together. For example, if we double eight, we get sixteen. Then we calculate one plus six, which equals seven.

In our example, all our doubled digits are less than ten, so we don’t need to do anything.

Step three: Add all the digits together - both the doubled ones and the un-doubled ones.

👩‍🎓 Student

So that’s… let me calculate… four plus zero plus six plus zero plus zero plus zero plus zero plus zero… this is tedious!

👨‍🏫 Professor

Welcome to programming! This is exactly why we write code - to automate tedious tasks. The computer can do this instantly.

Finally, step four: If the total modulo ten equals zero, the number is valid.

👩‍🎓 Student

So we’re checking if the sum is divisible by ten?

👨‍🏫 Professor

Precisely! If the last digit of the sum is zero, the card number passes the Luhn check.


🎬 Scene 7: Credit - Implementation Strategy

👨‍🏫 Professor

Now, how do we implement this in C? We need to:

  1. Extract each digit from the card number
  2. Determine if it should be doubled
  3. If doubled, add its digits if necessary
  4. Keep a running sum
  5. Check if the sum modulo ten equals zero

Let’s think about how to extract digits. If we have a number like one two three four, how do we get each digit?

👩‍🎓 Student

Um, we could… use modulo ten to get the last digit?

👨‍🏫 Professor

Excellent! One thousand two hundred thirty-four mod ten equals four. Then what?

👩‍🎓 Student

Divide by ten to remove that digit?

👨‍🏫 Professor

Exactly! Integer division. One thousand two hundred thirty-four divided by ten equals one hundred twenty-three. We repeat this process until the number becomes zero.

Here’s a skeleton:

long temp = number;
int position = 1;
int sum1 = 0;  // Doubled digits
int sum2 = 0;  // Un-doubled digits

while (temp > 0)
{
    int digit = temp % 10;
    
    if (position % 2 == 0)
    {
        // This is a digit we need to double
    }
    else
    {
        // This is a digit we don't double
    }
    
    temp /= 10;
    position++;
}

Can you see how this works?

👩‍🎓 Student

Yes! We’re processing digits from right to left. The position variable tells us which digit we’re on.

👨‍🏫 Professor

Exactly! Now, when we double a digit, we need to handle two-digit results. If the product is ten or more, we add its digits. How can we do that?

👩‍🎓 Student

We could… divide by ten to get the first digit, and mod ten to get the second digit?

👨‍🏫 Professor

Perfect! If the product is sixteen, then sixteen divided by ten is one, and sixteen mod ten is six. Add them together: one plus six equals seven.

if (position % 2 == 0)
{
    int product = digit * 2;
    if (product >= 10)
    {
        sum1 += product / 10 + product % 10;
    }
    else
    {
        sum1 += product;
    }
}
else
{
    sum2 += digit;
}

Make sense?

👩‍🎓 Student

Yes! So sum1 accumulates the doubled digits, and sum2 accumulates the un-doubled digits. Then we check if their sum is divisible by ten.

👨‍🏫 Professor

Exactly right!


🎬 Scene 8: Credit - Card Type Identification

👨‍🏫 Professor

After validating with Luhn’s algorithm, we need to determine the card type. Do you know the differences?

👩‍🎓 Student

I remember from the problem description. American Express has fifteen digits and starts with thirty-four or thirty-seven. Visa has thirteen or sixteen digits and starts with four. And MasterCard has sixteen digits and starts with fifty-one through fifty-five.

👨‍🏫 Professor

Perfect recall! So we need two pieces of information: the length and the starting digits. How can we get the length?

👩‍🎓 Student

We could count digits using a similar loop?

👨‍🏫 Professor

Exactly! And for the starting digits?

👩‍🎓 Student

We could keep dividing by ten until we have just one or two digits left?

👨‍🏫 Professor

Perfect! Let’s create helper functions for these tasks:

int get_length(long number)
{
    int count = 0;
    while (number > 0)
    {
        number /= 10;
        count++;
    }
    return count;
}

int get_first_two_digits(long number)
{
    while (number >= 100)
    {
        number /= 10;
    }
    return number;
}

Then our card type logic becomes:

int length = get_length(card_number);
int first_two = get_first_two_digits(card_number);

if (length == 15 && (first_two == 34 || first_two == 37))
{
    printf("AMEX\n");
}
else if (length == 16 && first_two >= 51 && first_two <= 55)
{
    printf("MASTERCARD\n");
}
else if ((length == 13 || length == 16) && first_two / 10 == 4)
{
    printf("VISA\n");
}
else
{
    printf("INVALID\n");
}

Notice for Visa, we divide first_two by ten to check if it starts with four.

👩‍🎓 Student

Oh, clever! Because Visa can start with forty-something, we just check if the first digit is four.


🎬 Scene 9: Function Abstraction Discussion

👩‍🎓 Student

Professor, I have a question. In the credit problem, we created many helper functions. Is it always better to use more functions?

👨‍🏫 Professor

Ah, excellent question! This touches on a principle called function abstraction. The answer is: it depends.

Functions are great when they:

  • Make the code more readable
  • Are used multiple times
  • Encapsulate complex logic
  • Make testing easier

But creating too many tiny functions can sometimes make code harder to follow. It’s a balance.

👩‍🎓 Student

So how do we know when to create a function?

👨‍🏫 Professor

Here’s my rule of thumb: If you find yourself explaining “this block of code does X,” that’s a good candidate for a function called “do_X.”

For example, “this code calculates the length of a number” becomes a function called get_length.

But if it’s just one or two lines and only used once, it might be fine to keep inline.

👩‍🎓 Student

That makes sense. So it’s about clarity and reusability?

👨‍🏫 Professor

Exactly! And it comes with experience. As you write more code, you’ll develop intuition for when to abstract.


🎬 Scene 10: Common Mistakes and Debugging

👨‍🏫 Professor

Before we wrap up, let’s talk about common mistakes I see in these problems.

First: off-by-one errors. Can you think of an example in Mario?

👩‍🎓 Student

Um, maybe using “i less than or equal to height” instead of “i less than height”?

👨‍🏫 Professor

Exactly! If height is four and you use “less than or equal to,” you’ll print five rows instead of four. Always think carefully about your loop conditions.

Second: integer division. What’s five divided by two in C?

👩‍🎓 Student

Two! Because it truncates the decimal.

👨‍🏫 Professor

Right! This catches many beginners. If you want two point five, at least one operand must be a float.

Third: floating-point precision. Why did we convert dollars to cents in the cash problem?

👩‍🎓 Student

Because floating-point numbers aren’t exact! If we calculated with dollars, we might get rounding errors.

👨‍🏫 Professor

Perfect! Always be careful with floats, especially for money.

Fourth: logic errors in conditions. What’s the difference between “and” and “or”?

👩‍🎓 Student

“And” means both conditions must be true. “Or” means at least one must be true.

👨‍🏫 Professor

Exactly! I often see students mix these up. For example, checking if height is invalid:

if (height < 1 || height > 8)  // Correct
if (height < 1 && height > 8)  // Wrong! This is impossible.

Make sense?

👩‍🎓 Student

Yes! The second one would never be true because a number can’t be both less than one and greater than eight at the same time.


🎬 Scene 11: Testing and Edge Cases

👨‍🏫 Professor

Great! Now let’s talk about testing. When you finish a program, how do you know it works?

👩‍🎓 Student

I test it with the examples from the problem?

👨‍🏫 Professor

That’s a good start, but not enough. You need to think about edge cases - unusual or extreme inputs. For Mario, what are some edge cases?

👩‍🎓 Student

Hmm, maybe height equals one? Or height equals eight?

👨‍🏫 Professor

Excellent! Those are boundary cases - the minimum and maximum valid inputs. What about invalid inputs?

👩‍🎓 Student

Oh! Like zero, or negative numbers, or numbers greater than eight?

👨‍🏫 Professor

Exactly! And for cash?

👩‍🎓 Student

Zero cents? One cent? Maybe a large amount like ninety-nine dollars and ninety-nine cents?

👨‍🏫 Professor

Perfect! And for credit?

👩‍🎓 Student

Invalid numbers that fail the Luhn check? Numbers that are too short or too long? Numbers that don’t match any card type?

👨‍🏫 Professor

Excellent thinking! This is exactly how professional programmers test their code. They think: what could go wrong? What unusual inputs might break my program?


🎬 Scene 12: Big-Picture Lessons

👨‍🏫 Professor

Alright, we’re almost done. Let’s step back and think about the big picture. What did you learn from these four problems?

👩‍🎓 Student

Well, from Mario, I learned about nested loops and translating patterns into formulas.

From Cash, I learned about greedy algorithms and why we should use integers for money.

From Credit, I learned about implementing algorithms and organizing code with functions.

👨‍🏫 Professor

Excellent summary! But there’s something deeper. What’s the common thread through all these problems?

👩‍🎓 Student

Um… they all involve breaking down problems into smaller steps?

👨‍🏫 Professor

Exactly! That’s the essence of computational thinking. We take a complex problem and decompose it into manageable pieces.

In Mario, we broke it down to: print spaces, print hashes, repeat for each row.

In Cash, we broke it down to: check quarters, check dimes, check nickels, check pennies.

In Credit, we broke it down to: extract digits, apply Luhn’s algorithm, check card type.

This skill - problem decomposition - is perhaps the most important thing you can learn in computer science.

👩‍🎓 Student

So it’s not just about learning C syntax, it’s about learning how to think?

👨‍🏫 Professor

Exactly! The syntax is just a tool. The real skill is problem-solving. And that skill transfers to any programming language, any domain.


🎬 Scene 13: Looking Ahead

👩‍🎓 Student

Professor, what’s next? How do these problems prepare us for later in the course?

👨‍🏫 Professor

Great question! The concepts we covered here are foundational.

Nested loops from Mario will come back when we work with two-dimensional arrays and more complex data structures.

Greedy algorithms from Cash will reappear when we study algorithm design strategies.

The Luhn algorithm from Credit introduces you to the idea that algorithms are recipes - precise step-by-step procedures that someone invented to solve a problem.

And function abstraction? That’s something you’ll use every single day as a programmer.

👩‍🎓 Student

So even though these problems seem simple, they’re teaching us fundamental concepts?

👨‍🏫 Professor

Exactly! In fact, some of the most important ideas in computer science can be illustrated with simple problems. Master these basics, and you’ll have a solid foundation for everything that follows.


🎬 Scene 14: Study Tips

👩‍🎓 Student

Do you have any advice for studying and practicing?

👨‍🏫 Professor

Absolutely! Here are my top tips:

First: Don’t just read code - write it. Type out the examples yourself. Modify them. Break them and fix them.

Second: When you get stuck, use the rubber duck method. Explain your code line by line to an imaginary duck. Often, just articulating the problem helps you find the solution.

Third: Draw pictures. Visualize what’s happening in memory. For arrays, draw boxes. For loops, trace through iteration by iteration.

Fourth: Practice reading error messages. They’re trying to help you! Learn to decipher what they mean.

Fifth: Compare your solutions with others - but only after you’ve solved it yourself. See different approaches.

👩‍🎓 Student

The rubber duck method? That’s funny!

👨‍🏫 Professor

It sounds silly, but it really works! Explaining your logic forces you to think clearly. Many professional developers keep a rubber duck on their desk for exactly this purpose.


🎬 Scene 15: Conclusion

👨‍🏫 Professor

Well, I think that covers Problem Set One! Do you feel more confident now?

👩‍🎓 Student

Yes, much more! Understanding the “why” behind the code makes everything clearer. Thank you so much, Professor!

👨‍🏫 Professor

You’re very welcome! Remember: programming is a skill that improves with practice. Don’t get discouraged if something doesn’t click immediately. Keep working at it.

And one more thing: enjoy the process! These problems might seem like just homework, but you’re actually learning to think like a computer scientist. That’s a valuable skill that will serve you well, whether you become a software engineer or not.

👩‍🎓 Student

I’ll remember that. I’m excited to move on to Problem Set Two!

👨‍🏫 Professor

Excellent attitude! Problem Set Two introduces arrays and strings - some of my favorite topics. I think you’re going to enjoy it.

Good luck, and feel free to come by office hours if you have questions!

👩‍🎓 Student

I will. Thank you again, Professor Chen!

👨‍🏫 Professor

You’re welcome. Happy coding!


📝 Practice Guide for This Script

How to Use This Dialogue for Learning

Method 1: Listen and Repeat

  1. Use TTS to generate audio for Professor parts
  2. Listen carefully to pronunciation and intonation
  3. Pause and repeat each section
  4. Record yourself and compare

Method 2: Role Play (Solo)

  1. Read Professor parts out loud (teach mode)
  2. Read Student parts out loud (learn mode)
  3. Practice switching between roles
  4. Focus on natural speech patterns

Method 3: With a Partner

  1. One person plays Professor
  2. Other person plays Student
  3. Take turns switching roles
  4. Add your own questions and answers

Method 4: Shadow the TTS

  1. Play the TTS audio
  2. Speak simultaneously (shadowing technique)
  3. Match the rhythm and pronunciation
  4. Gradually increase speed

Key Phrases to Practice

Professor Phrases (Teaching)

  • “Let me show you what I mean.”
  • “Can you see a pattern here?”
  • “Exactly right!”
  • “That’s a great question!”
  • “Let’s walk through this.”
  • “Make sense?”

Student Phrases (Learning)

  • “Let me think about that.”
  • “Oh, I see!”
  • “Why does that work?”
  • “I’m not sure I understand.”
  • “So you’re saying…”
  • “Could you explain that again?”

Vocabulary Checklist

After studying this dialogue, you should be able to:

  • Explain nested loops
  • Describe greedy algorithms
  • Explain Luhn’s algorithm
  • Discuss function abstraction
  • Identify edge cases
  • Describe problem decomposition

🎯 Next Steps

After mastering this script:

  1. ✅ Review pronunciation guide for unfamiliar terms
  2. ✅ Practice with TTS (Gemini or other tools)
  3. ✅ Record yourself teaching the concepts
  4. ✅ Move on to Problem Set 2 dialogue script

Good luck with your practice! 🎓