CSS Prepare

optional · CSS study notes

II.3 Systematic Approach to Defining a Problem

⬆️ P-I.II. Programming Fundamentals · Question Bank · Chapter II (2016–2026)

🧠

Remember: Input + output + constraints = a testable problem.

Start here — what you should be able to do

  • Turn a vague request into a precise specification.
  • Distinguish requirements, constraints and design decisions.
  • Write normal, boundary and invalid-input tests before implementation.

Reading order: foundation checks → full notes and examples → exam traps → self-test without notes → past-paper answers. All sections below remain part of the topic; code examples are kept in full.

Foundation checks

Requirements versus choices

ItemExample
Required resultAverage and grade for N marks
Input constraintN > 0; each mark in 0–100
Assumption to stateGrade A starts at 80 in this example
Design choiceStream the marks into a running sum instead of storing all marks
Acceptance test80, 70, 90 → average 80

Boundary tests: N = 1, marks 0/100, values exactly at grade thresholds. Invalid tests: N = 0, negative marks, marks above 100, nonnumeric input. Refinement: real projects revisit requirements and design; this sequence is a planning guide, not a ban on iteration.

1. Steps to Define a Problem Systematically

  1. Problem statement — state clearly and precisely what needs to be solved
  2. Requirements analysis — identify what inputs are available and what outputs are expected
  3. Constraints identification — note any limits (time, memory, valid input ranges)
  4. Decomposition — break the problem into smaller, manageable sub-problems
  5. Identify inputs/outputs (I/O) explicitly — define data types and formats
  6. Design the solution — using algorithms, flowcharts, or pseudocode
  7. Verify against test cases — before implementation, mentally trace through sample inputs

2. Why This Step Matters

  • A poorly defined problem leads to a program that technically "works" but solves the wrong problem
  • Systematic definition reduces backtracking during coding and debugging
  • Forms the basis of the full Program Development Life Cycle: Define → Design → Code → Test → Debug → Maintain

3. Worked Example

Problem (as given): "Write a program to process student marks." This is too vague to code directly. Applying the steps:

  1. Problem statement: Compute the average of a student's marks in N subjects and assign a grade.
  2. Requirements analysis: Input = number of subjects N and N marks (0–100 each); Output = average marks and a letter grade.
  3. Constraints: N must be a positive integer; each mark must be in the range 0–100.
  4. Decomposition: Sub-problems = (a) read marks, (b) compute average, (c) map average to a grade, (d) display result.
  5. I/O definition: Input — integer N, then N floating-point marks; Output — one float (average) and one character/string (grade).
  6. Design: Draft pseudocode/flowchart for the four sub-problems above.
  7. Verify against test cases: Trace with N=3, marks={80,70,90} → average=80 → grade 'A' — check this by hand before coding.

Only after these seven steps does actual coding begin.

4. 💻 Code Examples: C & C++

Coding the worked example (average of N marks → grade) after the seven definition steps:

C:

#include <stdio.h>
int main(void) {
    int n;
    float sum = 0, mark, average;
    printf("Enter number of subjects: ");
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%f", &mark);
        sum += mark;
    }
    average = sum / n;
    char grade = (average >= 80) ? 'A' : (average >= 60) ? 'B' : 'C';
    printf("Average = %.2f, Grade = %c\n", average, grade);
    return 0;
}

C++:

#include <iostream>
using namespace std;
int main() {
    int n;
    float sum = 0, mark, average;
    cout << "Enter number of subjects: ";
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> mark;
        sum += mark;
    }
    average = sum / n;
    char grade = (average >= 80) ? 'A' : (average >= 60) ? 'B' : 'C';
    cout << "Average = " << average << ", Grade = " << grade << endl;
    return 0;
}
  • This is exactly the N=3, marks={80,70,90} trace from the worked example — running it gives average=80, grade='A', matching the by-hand verification done before coding

5. MCQ Traps

  • Defining the problem comes before algorithm design, not after
  • Decomposition ≠ coding — it's still at the planning/design stage
  • Constraints (time/memory/input range) are part of problem definition, not implementation

6. ✅ Self-Test Questions (with Answers)

Q1. List the steps to systematically define a problem before coding.

Answer — key points

  • Defining a problem systematically converts a vague request into a precise, codable specification.
  • The standard seven steps are:
  1. Problem statement — state clearly and precisely what must be solved, removing every ambiguity
  2. Requirements analysis — identify what data is available and what results are expected
  3. Constraints identification — record the limits: valid input ranges, memory, time, platform
  4. Decomposition — break the problem into smaller, independently solvable sub-problems
  5. Explicit I/O definition — fix the exact data types and formats of every input and output
  6. Design the solution — express the logic as an algorithm, pseudocode or flowchart
  7. Verify against test cases — dry-run sample inputs by hand and confirm the expected outputs
  • Only when all seven are complete does coding begin.
  • Applied to "process student marks", step 1 sharpens it to "compute the average of N subject marks and assign a grade", step 3 adds N > 0 and 0 <= mark <= 100, and step 7 fixes the check N=3, {80,70,90} → average 80 → grade 'A'.
// The specification above, translated only AFTER definition is complete
int n;  double sum = 0, mark;
cin >> n;
if (n <= 0) { cout << "Invalid subject count"; return 1; }   // constraint from step 3
for (int i = 0; i < n; i++) {
    cin >> mark;
    if (mark < 0 || mark > 100) { cout << "Invalid mark"; return 1; }
    sum += mark;
}
double avg = sum / n;
cout << "Average = " << avg
     << ", Grade = " << (avg >= 80 ? 'A' : avg >= 60 ? 'B' : 'C');
// Input: 3  80 70 90   ->   Average = 80, Grade = A

MCQ trap: decomposition is not coding — it belongs to the planning stage. Likewise constraints (time, memory, input range) are part of problem definition, not of implementation, and verification by dry-running happens before the program is written, not only after.

Q2. Why should input/output be defined before writing an algorithm?

Answer — key points

  • Because the I/O specification is what the algorithm is obliged to deliver — without it there is no way to know when the algorithm is finished or whether it is correct.
  • Fixing the inputs and outputs first determines the data types and storage structures (a single int or a whole array?), dictates the control structures needed (a fixed count suggests a for loop, a sentinel-terminated stream suggests a while), supplies the validation rules for rejecting bad data, and — crucially — provides the test cases used to verify the design before a single line is coded.
  • Skipping this step means the logic is built on assumptions that surface only after coding, forcing expensive rewrites.
  • Determines data types — marks as int or float changes the arithmetic (and the truncation risk)
  • Determines structure — one value needs a variable; N values need an array or a loop accumulator
  • Determines the loop form — known count → for; unknown count ending in a sentinel → while
  • Determines validation — knowing marks lie in 0–100 tells you exactly what to reject
  • Enables verification — expected output for a known input lets you dry-run the algorithm on paper
// I/O DEFINED: input = N then N floats; output = one float average
int n;  double sum = 0, mark;
cin >> n;                            // count known -> for loop is correct
for (int i = 0; i < n; i++) { cin >> mark; sum += mark; }
cout << sum / n;                     // sum is double -> real division, no truncation
// Input: 3  80 70 90   ->   80

MCQ trap: had the I/O definition said "marks are whole numbers", declaring int sum would make sum / n perform integer division — 250/3 would print 83, not 83.33. The I/O definition is therefore not paperwork; it directly decides whether the arithmetic is right.

Q3. Where does 'problem definition' fit within the overall Program Development Life Cycle?

Answer — key points

  • Problem definition is the very first stage of the Program Development Life Cycle — it precedes design, coding, testing and maintenance.
  • The full cycle is Define → Design → Code → Test → Debug → Maintain (often written as Analysis → Design → Implementation → Testing → Maintenance).
  • Its output is a clear specification of inputs, outputs, constraints and success criteria, which becomes the input to the design stage, where algorithms, pseudocode and flowcharts are produced.
  • Nothing is written in a programming language until the definition and design stages are complete.
#StageMain ActivityDeliverable
1Define (Analysis)State the problem, fix I/O and constraintsRequirement specification
2DesignDevise the logicAlgorithm, pseudocode, flowchart
3Code (Implementation)Translate the design into a languageSource program
4TestRun against planned test casesTest report
5DebugLocate and remove defectsCorrected program
6MaintainEnhance and adapt after releaseUpdated versions
  • Maintenance consumes the most time and cost over a program's life — a favourite one-mark question
  • An error introduced at the Define stage but caught at the Test stage is the most expensive kind to fix, because design and code must both be reworked

MCQ trap: problem definition comes before algorithm design, never after; and decomposition is still planning, not coding. Do not confuse the Program Development Life Cycle with the broader SDLC (Software Development Life Cycle), which adds feasibility study, deployment and formal documentation phases.

Q4. Using the seven-step approach, define the problem "sort a list of names alphabetically."

Answer — key points

  • Applying the seven systematic steps turns this one-line request into a specification precise enough to code:
  1. Problem statement — Read N names and display them arranged in ascending lexicographic (dictionary) order.
  2. Requirements analysis — Input: the count N, then N name strings. Output: the same N names, reordered A–Z, one per line.
  3. Constraints — 1 <= N <= 100; each name at most 50 characters; letters and spaces only; comparison is case-insensitive (so "ahmed" sorts beside "Ahmed"); duplicates are retained, not discarded.
  4. Decomposition — (a) read the names, (b) compare two names, (c) swap/reorder, (d) display the sorted list.
  5. Explicit I/O definition — Input: one int, then an array string names[100]. Output: N lines of text.
  6. Design the solution — use Bubble Sort (or Selection Sort) with string comparison rather than numeric comparison.
  7. Verify against test cases — Input {Zahra, Ali, Bilal} must give {Ali, Bilal, Zahra}; also test N=1, already-sorted input, and reverse-sorted input.
#include <iostream>
#include <string>
using namespace std;

int main() {
    int n;  cin >> n;
    string names[100];
    for (int i = 0; i < n; i++) cin >> names[i];

    for (int i = 0; i < n - 1; i++)              // Bubble Sort
        for (int j = 0; j < n - 1 - i; j++)
            if (names[j] > names[j + 1])          // lexicographic compare
                swap(names[j], names[j + 1]);

    for (int i = 0; i < n; i++) cout << names[i] << endl;
    return 0;
}
// Input: 3 Zahra Ali Bilal   ->   Ali / Bilal / Zahra
  • MCQ trap: in C-style strings you cannot compare with == or > — those compare pointer addresses, not contents.
  • You must use strcmp(a, b), which returns a negative value, 0 or positive, not a boolean.
  • Note also that ASCII puts all uppercase letters before all lowercase ('Z'=90 < 'a'=97), so a naive sort places "Zahra" before "ali" — hence the case-insensitivity constraint fixed in step 3.

Q5. Why can two programmers, given the same vague problem statement, end up building two very different (and both 'technically correct') programs?

Answer — key points

  • Because a vague statement fails the definiteness requirement: it leaves the inputs, outputs, constraints and success criteria unspecified, so each programmer silently fills those gaps with their own assumptions.
  • Both then implement their own interpretation faithfully — each program is internally consistent, compiles and runs — yet the two solve different problems, and at most one matches what the client actually wanted.
  • This is precisely why problem definition precedes design and coding: the cost of a misunderstood requirement rises sharply at every later stage of the life cycle, and a defect introduced at the definition stage is the most expensive of all to repair.

Take the statement "write a program to process student marks". The unstated decisions include:

  • Input source and count — fixed 3 subjects, or a user-supplied N? Keyboard or file?
  • Output required — total, average, percentage, grade, pass/fail, or a ranked list?
  • Grading scale — is 80 an 'A' or a 'B'? Is 40 or 50 the pass mark?
  • Validation — are negative marks or marks above 100 rejected, or accepted silently?
  • Data type — integer marks or fractional (int vs float)?
// Programmer A assumed "process" = average only, marks are integers
cout << sum / n;            // 250/3 -> 83   (integer division truncates!)

// Programmer B assumed "process" = average + grade, marks are real
double avg = sum / (double)n;   // 83.33
cout << avg << (avg >= 80 ? " Grade A" : " Grade B");

MCQ trap: note the second-order bug hiding in Programmer A's version — sum / n with two int operands performs integer division and silently truncates 83.33 to 83. Ambiguity at the definition stage often breeds logic errors at the coding stage, and neither is caught by the compiler.

Recall check and coverage

  • Explain the three learning outcomes at the top without looking.
  • Reproduce one example or trace and test a boundary case.
  • Answer each self-test, then compare with the key.
  • Attempt each recorded past-paper task on this page.

Recorded written references: No direct written record in this topic's current notes. These concepts support the chapter's programming questions.

Evidence limit: this page preserves the question records found in your Notion notes. Disputed labels and questions not checked against original PDFs remain flagged in the Question Bank; this is not a claim of exhaustive original-paper verification.