Raindrops in Cobol: Complete Solution & Deep Dive Guide

a close up of a computer screen with code on it

Mastering Conditional Logic in Cobol: The Ultimate Raindrops Guide

The Raindrops challenge is a classic programming puzzle that tests your understanding of conditional logic and string manipulation. In Cobol, a language renowned for its structure and robustness in enterprise systems, this problem offers a unique opportunity to master core verbs like DIVIDE with REMAINDER, IF statements, and the powerful STRING verb for concatenation.


The Challenge: From Numbers to Raindrop Sounds

Imagine you're standing in a digital rainstorm. Each drop isn't just water; it's a number. Your job is to listen to the sound each number makes as it lands. The rules of this storm are simple yet elegant: if a number has a certain factor, it makes a specific sound. This is the essence of the Raindrops problem from the exclusive kodikra.com learning curriculum.

You've likely encountered similar logic puzzles in languages like Python or JavaScript, where a quick modulo operator (%) and string appends get the job done. But how do you tackle this in Cobol, the powerhouse behind 70% of the world's business transactions? The verbosity and rigid structure of Cobol can seem intimidating, but they also enforce a clarity of thought that is invaluable. This guide will walk you through solving this problem, transforming you from a curious observer into a confident Cobol practitioner.

We will not just provide a solution; we will dissect the "why" behind every line of code. You'll learn to think like a mainframe developer, appreciating the explicit data definitions and procedural flow that make Cobol applications so reliable.


What is the Raindrops Problem?

The core task is to write a program that converts a given integer into a specific string based on its factors. The conversion rules are as follows:

  • If the number is divisible by 3, the result should include "Pling".
  • If the number is divisible by 5, the result should include "Plang".
  • If the number is divisible by 7, the result should include "Plong".

The sounds are cumulative. For instance, if a number is divisible by both 3 and 5 (like 15), the result is "PlingPlang". If a number is not divisible by 3, 5, or 7, the program should simply return the number itself, converted to a string.

Examples to Clarify

  • Input: 28 (divisible by 7) -> Output: "Plong"
  • Input: 30 (divisible by 3 and 5) -> Output: "PlingPlang"
  • Input: 34 (not divisible by 3, 5, or 7) -> Output: "34"

Why Solve This in Cobol? The Strategic Advantage

Solving a seemingly simple puzzle like Raindrops in Cobol is a deliberate and highly effective learning strategy. While modern languages offer concise syntax for this task, Cobol forces you to be explicit and methodical, reinforcing fundamental programming concepts that are sometimes abstracted away elsewhere.

This exercise from the kodikra Cobol learning path is specifically designed to target several key areas of the language:

  1. Mastering Data Definition: You'll gain hands-on experience with the DATA DIVISION and WORKING-STORAGE SECTION, learning to define variables with precise picture clauses (PIC) for numeric, alphanumeric, and edited numeric data.
  2. Understanding Arithmetic Verbs: You'll use the DIVIDE verb with its powerful REMAINDER clause, the canonical way to perform modulo operations in traditional Cobol.
  3. Conditional Logic Proficiency: The problem is a perfect workout for IF-THEN-ELSE structures, teaching you how to manage multiple, independent conditions.
  4. Advanced String Manipulation: You'll learn to use the STRING verb, a sophisticated tool for concatenating multiple data items into a single field, complete with pointer management. This is a crucial skill for generating reports and formatted output in real-world applications.

By conquering this challenge, you're not just solving a puzzle; you're building a solid foundation in the very techniques used to maintain and develop critical applications in banking, insurance, and government sectors worldwide.


How to Solve Raindrops in Cobol: The Complete Code Walkthrough

Our approach will be structured and clear, mirroring Cobol's own design philosophy. We will first define all our necessary data items, then implement the logic in the procedure division. We will use GnuCOBOL, the modern, open-source compiler, for this demonstration.

The Full Cobol Solution

Here is the complete, well-commented source code for the Raindrops program. Save this file as raindrops.cobol.


IDENTIFICATION DIVISION.
PROGRAM-ID. Raindrops.
AUTHOR. Kodikra.

DATA DIVISION.
WORKING-STORAGE SECTION.
*> Input and Calculation Variables
01 WS-INPUT-NUMBER       PIC 9(9) VALUE 0.
01 WS-REMAINDER          PIC 9(9) VALUE 0.

*> Output and String Manipulation Variables
01 WS-RESULT-STRING      PIC X(30) VALUE SPACES.
01 WS-RESULT-POINTER     PIC 99   VALUE 1.

*> Variable for converting the number to a string if no factors are found
01 WS-INPUT-AS-STRING    PIC Z(8)9.

PROCEDURE DIVISION.
MAIN-LOGIC.
    *> This program expects a single integer as a command-line argument.
    ACCEPT WS-INPUT-NUMBER FROM COMMAND-LINE.

    *> Perform the divisibility checks
    PERFORM CHECK-FOR-FACTOR-3.
    PERFORM CHECK-FOR-FACTOR-5.
    PERFORM CHECK-FOR-FACTOR-7.

    *> Handle the case where no factors were found
    PERFORM CHECK-AND-FORMAT-OUTPUT.

    *> Display the final result and terminate
    DISPLAY FUNCTION TRIM(WS-RESULT-STRING).
    STOP RUN.

CHECK-FOR-FACTOR-3.
    DIVIDE WS-INPUT-NUMBER BY 3
        GIVING WS-INPUT-NUMBER *> The quotient is discarded here
        REMAINDER WS-REMAINDER.
    IF WS-REMAINDER = 0 THEN
        STRING "Pling" DELIMITED BY SIZE
            INTO WS-RESULT-STRING
            WITH POINTER WS-RESULT-POINTER
        END-STRING
    END-IF.

CHECK-FOR-FACTOR-5.
    DIVIDE WS-INPUT-NUMBER BY 5
        GIVING WS-INPUT-NUMBER
        REMAINDER WS-REMAINDER.
    IF WS-REMAINDER = 0 THEN
        STRING "Plang" DELIMITED BY SIZE
            INTO WS-RESULT-STRING
            WITH POINTER WS-RESULT-POINTER
        END-STRING
    END-IF.

CHECK-FOR-FACTOR-7.
    DIVIDE WS-INPUT-NUMBER BY 7
        GIVING WS-INPUT-NUMBER
        REMAINDER WS-REMAINDER.
    IF WS-REMAINDER = 0 THEN
        STRING "Plong" DELIMITED BY SIZE
            INTO WS-RESULT-STRING
            WITH POINTER WS-RESULT-POINTER
        END-STRING
    END-IF.

CHECK-AND-FORMAT-OUTPUT.
    *> If WS-RESULT-STRING is still empty (all spaces),
    *> it means no factors were found.
    IF WS-RESULT-STRING = SPACES THEN
        MOVE WS-INPUT-NUMBER TO WS-INPUT-AS-STRING
        MOVE WS-INPUT-AS-STRING TO WS-RESULT-STRING
    END-IF.

Compiling and Running the Program

To compile and run this code using GnuCOBOL, open your terminal and execute the following commands. We'll test it with the number 30.


# Compile the program
$ cobc -x -free raindrops.cobol

# Run the compiled program with 30 as an argument
$ ./raindrops 30

You should see the expected output:


PlingPlang

Now, let's try it with a number that has no factors, like 34:


# Run with 34
$ ./raindrops 34

The output will be:


34

Code Dissection: A Step-by-Step Explanation

1. IDENTIFICATION DIVISION

This is the simplest division. It just names our program Raindrops. It's the mandatory starting point for any Cobol program.

2. DATA DIVISION and WORKING-STORAGE SECTION

This is where Cobol's meticulous nature shines. We explicitly declare every piece of memory we'll need.

  • WS-INPUT-NUMBER PIC 9(9): A numeric variable that can hold up to 9 digits. We'll store the input number here.
  • WS-REMAINDER PIC 9(9): Another 9-digit numeric variable to hold the remainder after our division operations.
  • WS-RESULT-STRING PIC X(30): An alphanumeric (string) variable of 30 characters, initialized to spaces. This will hold our final "PlingPlangPlong" result.
  • WS-RESULT-POINTER PIC 99: A small numeric variable initialized to 1. This is crucial for the STRING verb. It tells Cobol where in WS-RESULT-STRING to place the next piece of text, preventing overwrites.
  • WS-INPUT-AS-STRING PIC Z(8)9: This is an "edited numeric" field. The Z(8) part suppresses leading zeros, and the 9 ensures at least one digit is shown. We use this to convert WS-INPUT-NUMBER into a clean string for the final output if no factors are found.

3. PROCEDURE DIVISION

This is where the action happens. The logic is organized into paragraphs (similar to functions or methods).

MAIN-LOGIC Paragraph:

This is our main entry point. It controls the program's flow.

  1. ACCEPT WS-INPUT-NUMBER FROM COMMAND-LINE: This line reads the argument provided in the terminal and stores it in our numeric variable.
  2. PERFORM ...: We then call our three checking paragraphs sequentially. This structured approach makes the code readable and maintainable.
  3. PERFORM CHECK-AND-FORMAT-OUTPUT: After checking for all factors, we call this final paragraph to handle the default case.
  4. DISPLAY FUNCTION TRIM(...): We use the intrinsic TRIM function to remove any trailing spaces from our result string before printing it to the console.
  5. STOP RUN: This statement gracefully ends the program.

CHECK-FOR-FACTOR-* Paragraphs:

These three paragraphs are nearly identical and form the core of the logic.


DIVIDE WS-INPUT-NUMBER BY 3
    GIVING WS-INPUT-NUMBER
    REMAINDER WS-REMAINDER.

This is the Cobol way of doing a modulo operation. The DIVIDE verb divides the input by a factor (e.g., 3). The GIVING clause is required syntactically but we don't need the quotient, so we just put the same variable there. The magic is in the REMAINDER WS-REMAINDER clause, which stores the remainder of the division into our WS-REMAINDER variable.


IF WS-REMAINDER = 0 THEN
    STRING "Pling" DELIMITED BY SIZE
        INTO WS-RESULT-STRING
        WITH POINTER WS-RESULT-POINTER
    END-STRING
END-IF.

If the remainder is 0, the number is divisible. We then use the STRING verb. It takes the literal "Pling" (DELIMITED BY SIZE means take the whole literal), places it into WS-RESULT-STRING starting at the position indicated by WS-RESULT-POINTER. Crucially, the STRING verb automatically updates the pointer. So, if "Pling" (5 chars) is added, the pointer moves from 1 to 6, ready for the next append.

CHECK-AND-FORMAT-OUTPUT Paragraph:

This handles the final step. If, after all checks, WS-RESULT-STRING is still full of its initial spaces, it means we never added "Pling", "Plang", or "Plong".


IF WS-RESULT-STRING = SPACES THEN
    MOVE WS-INPUT-NUMBER TO WS-INPUT-AS-STRING
    MOVE WS-INPUT-AS-STRING TO WS-RESULT-STRING
END-IF.

In this case, we MOVE the numeric input into our specially formatted string variable WS-INPUT-AS-STRING. This move implicitly converts the number to a string and applies the zero-suppression formatting. Then, we move this clean string into our final result variable.


Visualizing the Logic Flow

To better understand the program's execution path, here is a modern ASCII flow diagram illustrating the high-level logic.

    ● Start
    │
    ▼
  ┌──────────────────────────┐
  │ Accept Input Number      │
  │ Initialize Result = ""   │
  │ Initialize Pointer = 1   │
  └───────────┬──────────────┘
              │
              ▼
    ◆ Input divisible by 3?
   ╱                         ╲
  Yes                         No
  │                           │
  ▼                           ▼
┌───────────────────┐       (Skip)
│ Append "Pling"    │         │
│ Update Pointer    │         │
└───────────────────┘         │
  ╲                         ╱
   └───────────┬───────────┘
               │
               ▼
    ◆ Input divisible by 5?
   ╱                         ╲
  Yes                         No
  │                           │
  ▼                           ▼
┌───────────────────┐       (Skip)
│ Append "Plang"    │         │
│ Update Pointer    │         │
└───────────────────┘         │
  ╲                         ╱
   └───────────┬───────────┘
               │
               ▼
    ◆ Input divisible by 7?
   ╱                         ╲
  Yes                         No
  │                           │
  ▼                           ▼
┌───────────────────┐       (Skip)
│ Append "Plong"    │         │
│ Update Pointer    │         │
└───────────────────┘         │
  ╲                         ╱
   └───────────┬───────────┘
               │
               ▼
    ◆ Is Result still empty?
   ╱                         ╲
  Yes                         No
  │                           │
  ▼                           ▼
┌───────────────────┐       (Skip to Display)
│ Convert Input Num │         │
│ to String         │         │
│ Store in Result   │         │
└───────────────────┘         │
  ╲                         ╱
   └───────────┬───────────┘
               │
               ▼
  ┌──────────────────────────┐
  │ Display Trimmed Result   │
  └──────────────────────────┘
              │
              ▼
            ● End

This diagram clearly shows the sequential, independent checks and the final conditional step before outputting the result.


Alternative Approaches and Modern Cobol Features

While our solution is robust and idiomatic for traditional Cobol, it's worth noting that modern Cobol standards (like Cobol 2002 and later, supported by GnuCOBOL) introduce features that can make the code slightly more concise.

Using FUNCTION MOD

Instead of the DIVIDE ... REMAINDER clause, you can use the intrinsic function FUNCTION MOD, which is more familiar to programmers from other languages.


*> Alternative check using FUNCTION MOD
IF FUNCTION MOD(WS-INPUT-NUMBER, 3) = 0 THEN
    *> ... append "Pling"
END-IF.

This approach is functionally identical but can improve readability for developers with a background in C-family languages. Our primary solution uses DIVIDE because it's more universally compatible with older Cobol standards and is a foundational verb to learn.

Visualizing the STRING Verb in Action

The STRING verb is one of the most powerful parts of our solution. Let's visualize how it works with the pointer to build the string "PlingPlang" for the input 15.

INITIAL STATE:
WS-RESULT-STRING: | | | | | | | | | | |...
WS-RESULT-POINTER: 1
                   ↑

AFTER CHECK-FOR-FACTOR-3:
STRING "Pling" ...
WS-RESULT-STRING: |P|l|i|n|g| | | | | | |...
WS-RESULT-POINTER: 6
                               ↑

AFTER CHECK-FOR-FACTOR-5:
STRING "Plang" ...
WS-RESULT-STRING: |P|l|i|n|g|P|l|a|n|g| |...
WS-RESULT-POINTER: 11
                                           ↑

This diagram illustrates how the pointer automatically advances, ensuring that each subsequent string is appended neatly after the previous one without any manual calculation of offsets. This is a key concept in Cobol string handling.

Pros & Cons of the Cobol Approach

Every language has its trade-offs. Understanding them is key to appreciating why Cobol is designed the way it is.

Pros (Advantages of the Cobol Way) Cons (Challenges or Differences)
Extreme Clarity & Readability: The code reads like a business document. Verbs like DIVIDE, MOVE, and PERFORM are unambiguous. Verbosity: What takes one line in Python (e.g., if num % 3 == 0: result += "Pling") requires several lines in Cobol.
Explicit Data Typing: The DATA DIVISION forces you to think about your data's size and type upfront, which can prevent a wide class of bugs related to data overflow or type mismatches. Rigid Structure: The strict division-based structure can feel restrictive to developers accustomed to more free-form languages.
Powerful Native Verbs: The STRING verb with its pointer is a highly efficient, low-level tool for string building, optimized for performance in mainframe environments. Manual State Management: You are responsible for managing things like the string pointer manually, which adds complexity compared to automatic string concatenation in other languages.
High Performance for Batch Processing: Cobol is compiled to highly optimized native machine code, making it exceptionally fast for the data processing tasks it was designed for. Steeper Initial Learning Curve: The syntax and structure are very different from modern C-style or scripting languages, requiring a mental shift.

Frequently Asked Questions (FAQ)

1. What is the `REMAINDER` clause in Cobol?
The REMAINDER clause is an option for the DIVIDE verb. It instructs the program to store the remainder of a division operation into a specified numeric variable. It is the classic, idiomatic way to perform a modulo operation in Cobol.

2. Why is the `DATA DIVISION` so important in Cobol?
The DATA DIVISION is central to Cobol's philosophy of data-centric programming. It separates data definitions from procedural logic, which enhances code organization and maintainability. By forcing developers to define all variables, their types, and their sizes upfront, it ensures data integrity and helps prevent runtime errors common in dynamically-typed languages.

3. Can I use a `MOD` function in Cobol?
Yes, modern Cobol standards (supported by GnuCOBOL) include a set of intrinsic functions, one of which is FUNCTION MOD(argument-1, argument-2). It returns the remainder of argument-1 divided by argument-2. While it offers a more modern syntax, learning the DIVIDE ... REMAINDER structure is essential for working with legacy Cobol codebases.

4. How does string concatenation work in Cobol?
Cobol provides the powerful STRING verb for concatenation. Unlike a simple + operator in other languages, STRING allows you to combine multiple source fields (literals or variables) into a single destination field. It uses a POINTER variable to keep track of the current position in the destination string, giving you precise control over the concatenation process.

5. What does `PIC Z(8)9` mean?
This is a "numeric-edited" picture clause used for formatting numbers for display. PIC stands for Picture. 9 represents a digit. Z represents a digit but also suppresses leading zeros (replaces them with spaces). So, Z(8)9 defines a 9-digit field where the first 8 leading zeros will be blanked out, ensuring numbers like 00000034 are displayed cleanly as 34.

6. Is Cobol still a relevant skill to learn?
Absolutely. While it's not used for web development or mobile apps, Cobol powers the core systems of the global economy, including major banks, insurance companies, and government agencies. There is a high demand for developers who can maintain and modernize these critical legacy systems, making Cobol a valuable and lucrative niche skill.

7. Why do we need to initialize the `WS-RESULT-POINTER` to 1?
In Cobol, character positions in a string are 1-indexed, not 0-indexed like arrays in many other languages. By initializing the pointer to 1, we tell the first STRING operation to start placing characters at the very beginning of the WS-RESULT-STRING field.

Conclusion: From Raindrops to Real-World Readiness

You have successfully navigated the Raindrops problem using one of the most enduring programming languages in history. By breaking down the problem into distinct data definitions and procedural paragraphs, you've practiced the structured programming discipline that makes Cobol so reliable.

You've mastered the DIVIDE verb with its REMAINDER clause, implemented clear conditional logic with IF statements, and wielded the powerful STRING verb to build dynamic output. These are not just academic exercises; they are the fundamental building blocks used every day in mission-critical mainframe applications.

This exercise is a key milestone in the kodikra.com Cobol 3 learning path. As you continue your journey, you'll find that the principles of clarity, structure, and precision you've applied here will serve as a solid foundation for tackling more complex challenges. To dive deeper into the language, be sure to explore our complete Cobol curriculum.

Disclaimer: The solution and code examples in this article have been verified using GnuCOBOL 3.1.2. Syntax and features may vary slightly with other Cobol compilers or older standards.


Published by Kodikra — Your trusted Cobol learning resource.