From 39c890b1e981197cb363603e6c79e81c25aa8e3d Mon Sep 17 00:00:00 2001 From: Danilo Piazzalunga Date: Mon, 29 Jan 2024 22:33:27 +0100 Subject: [PATCH] Raindrops approaches (#946) * Raindrops approach: introduction * Raindrops approaches: if statements approach * Raindrops approaches: sprintf function approach * Raindrops approaches: data-driven programming approach * Apply suggestions from code review --------- Co-authored-by: Ryan Hartlage --- .../raindrops/.approaches/config.json | 36 ++++ .../.approaches/data-driven/content.md | 68 ++++++++ .../.approaches/data-driven/snippet.txt | 5 + .../.approaches/if-statements/content.md | 49 ++++++ .../.approaches/if-statements/snippet.txt | 6 + .../raindrops/.approaches/introduction.md | 159 ++++++++++++++++++ .../raindrops/.approaches/sprintf/content.md | 50 ++++++ .../raindrops/.approaches/sprintf/snippet.txt | 2 + 8 files changed, 375 insertions(+) create mode 100644 exercises/practice/raindrops/.approaches/config.json create mode 100644 exercises/practice/raindrops/.approaches/data-driven/content.md create mode 100644 exercises/practice/raindrops/.approaches/data-driven/snippet.txt create mode 100644 exercises/practice/raindrops/.approaches/if-statements/content.md create mode 100644 exercises/practice/raindrops/.approaches/if-statements/snippet.txt create mode 100644 exercises/practice/raindrops/.approaches/introduction.md create mode 100644 exercises/practice/raindrops/.approaches/sprintf/content.md create mode 100644 exercises/practice/raindrops/.approaches/sprintf/snippet.txt diff --git a/exercises/practice/raindrops/.approaches/config.json b/exercises/practice/raindrops/.approaches/config.json new file mode 100644 index 00000000..249c6da5 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/config.json @@ -0,0 +1,36 @@ +{ + "introduction": { + "authors": [ + "danilopiazza" + ] + }, + "approaches": [ + { + "uuid": "830f6fb9-cd91-4c53-8b73-cbcb4585ad51", + "slug": "if-statements", + "title": "if Statements", + "blurb": "Use multiple if statements.", + "authors": [ + "danilopiazza" + ] + }, + { + "uuid": "cac9cf42-cd7b-4070-8f86-5a9753d17d8e", + "slug": "sprintf", + "title": "sprintf Function", + "blurb": "Use the sprintf function.", + "authors": [ + "danilopiazza" + ] + }, + { + "uuid": "ec393164-0222-4bed-9234-fc25755d8746", + "slug": "data-driven", + "title": "Data-Driven Programming", + "blurb": "Use data-driven programming.", + "authors": [ + "danilopiazza" + ] + } + ] +} diff --git a/exercises/practice/raindrops/.approaches/data-driven/content.md b/exercises/practice/raindrops/.approaches/data-driven/content.md new file mode 100644 index 00000000..69f2e0d5 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/data-driven/content.md @@ -0,0 +1,68 @@ +# Data-Driven Programming + +**raindrops.h** + +```c +#ifndef RAINDROPS_H +#define RAINDROPS_H + +char *convert(char result[], int drops); + +#endif +``` + +**raindrops.c** + +```c +#include "raindrops.h" + +#include +#include + +typedef struct { + int factor; + const char *sound; +} sound_t; + +static const sound_t SOUNDS[] = { + { 3, "Pling" }, + { 5, "Plang" }, + { 7, "Plong" }, +}; + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) + +char *convert(char result[], int drops) +{ + for (size_t i = 0; i < ARRAY_SIZE(SOUNDS); i++) { + if (drops % SOUNDS[i].factor == 0) { + strcat(result, SOUNDS[i].sound); + } + } + + if (strlen(result) == 0) { + sprintf(result, "%d", drops); + } + + return result; +} +``` + +First, the program defines a [structure][struct] [data type][typedef] to hold together two pieces of information: a factor and its corresponding sound. +Then, an array is created to hold all the necessary data, which is used to drive the logc of the program. + +The body of the function does not have any knowledge of the actual data, becoming simple and flexible: + +- For each element of the `SOUNDS` array +-- If the given number is a multiple of the current `factor` +--- Then concatenate the current `sound` to the result string using the [`strcat` function][strcat]. + +This approach allows for extensible code: for example, new sounds could be added (or removed) without modifying the `convert` function. + +Finally, the [`strlen` function] checks if `result` is empty: if so, the [`sprintf` function] formats `drops` as string into `result`. + +[struct]: https://www.geeksforgeeks.org/structures-c/ +[typedef]: https://www.geeksforgeeks.org/typedef-in-c/ +[strcat]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/strcat.html +[strlen]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/strlen.html +[sprintf]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sprintf.html diff --git a/exercises/practice/raindrops/.approaches/data-driven/snippet.txt b/exercises/practice/raindrops/.approaches/data-driven/snippet.txt new file mode 100644 index 00000000..7664b1a1 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/data-driven/snippet.txt @@ -0,0 +1,5 @@ + for (size_t i = 0; i < ARRAY_SIZE(SOUNDS); i++) { + if (drops % SOUNDS[i].factor == 0) { + strcat(result, SOUNDS[i].sound); + } + } diff --git a/exercises/practice/raindrops/.approaches/if-statements/content.md b/exercises/practice/raindrops/.approaches/if-statements/content.md new file mode 100644 index 00000000..6d0f6561 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/if-statements/content.md @@ -0,0 +1,49 @@ +# `if` Statements + +**raindrops.h** + +```c +#ifndef RAINDROPS_H +#define RAINDROPS_H + +char *convert(char result[], int drops); + +#endif +``` + +**raindrops.c** + +```c +#include "raindrops.h" + +#include +#include + +char *convert(char result[], int drops) +{ + if (drops % 3 == 0) + strcat(result, "Pling"); + if (drops % 5 == 0) + strcat(result, "Plang"); + if (drops % 7 == 0) + strcat(result, "Plong"); + + if (strlen(result) == 0) + sprintf(result, "%d", drops); + + return result; +} +``` + +- The first `if` statement checks if `drops` is a multiple of `3`. + If so, `"Pling"` is concatenated to `result` using the [`strcat` function][strcat]. +- The second `if` statement checks if `drops` is a multiple of `5`. + If so, `"Plang"` is concatenated to `result` using the [`strcat` function][strcat]. +- The thrd `if` statement checks if `drops` is a multiple of `7`. + If so, `"Plong"` is concatenated to `result` using the [`strcat` function][strcat]. + +Finally, the [`strlen` function] checks if `result` is empty: if so, the [`sprintf` function] formats `drops` as string into `result`. + +[strcat]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/strcat.html +[strlen]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/strlen.html +[sprintf]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sprintf.html diff --git a/exercises/practice/raindrops/.approaches/if-statements/snippet.txt b/exercises/practice/raindrops/.approaches/if-statements/snippet.txt new file mode 100644 index 00000000..9c385a4d --- /dev/null +++ b/exercises/practice/raindrops/.approaches/if-statements/snippet.txt @@ -0,0 +1,6 @@ + if (drops % 3 == 0) + strcat(result, "Pling"); + if (drops % 5 == 0) + strcat(result, "Plang"); + if (drops % 7 == 0) + strcat(result, "Plong"); diff --git a/exercises/practice/raindrops/.approaches/introduction.md b/exercises/practice/raindrops/.approaches/introduction.md new file mode 100644 index 00000000..a3f7f407 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/introduction.md @@ -0,0 +1,159 @@ +# Introduction + +There are various idioomatic ways to solve Raindrops. +A straightforward and approach is to use a series of `if` statements. +Another approach could look up both factors and raindrop sounds from an array, using data-driven programming to make the code as simple as possible. + +## General Guidance + +The key to solving Raindrops is to know if the input is evenly divisible by `3`, `5` and/or `7` using the [modulo operator][modulo-operator]. + +## Assumptions + +- `drops` is a non-negative, 32-bit integer (from `0` to `2,147,483,647`). +- `result` has enough space to hold the largest output string, meaning 16 bytes: + - 15 bytes for `"PlingPlangPlong"`, plus one for the null terminating character; + - 10 bytes for the largest possible value of `drops`, plus one for `'\0'`. +- `result` has been initialized as an empty string (that is, `result[0]` is `'\0'`). + +## Approach: `if` Statements + +**raindrops.h** + +```c +#ifndef RAINDROPS_H +#define RAINDROPS_H + +char *convert(char result[], int drops); + +#endif +``` + +**raindrops.c** + +```c +#include "raindrops.h" + +#include +#include + +char *convert(char result[], int drops) +{ + if (drops % 3 == 0) + strcat(result, "Pling"); + if (drops % 5 == 0) + strcat(result, "Plang"); + if (drops % 7 == 0) + strcat(result, "Plong"); + + if (strlen(result) == 0) + sprintf(result, "%d", drops); + + return result; +} +``` + +This approach uses a series of `if`-statements and string concatentation to build up the result string. +For more information, check the [`if` statements approach][approach-if-statements]. + +## Approach: `sprintf` Function + +**raindrops.h** + +```c +#ifndef RAINDROPS_H +#define RAINDROPS_H + +char *convert(char result[], int drops); + +#endif +``` + +**raindrops.c** + +```c +#include "raindrops.h" + +#include +#include + +#include "raindrops.h" + +#include +#include + +char *convert(char result[], int drops) +{ + sprintf(result, "%s%s%s", drops % 3 == 0 ? "Pling" : "", + drops % 5 == 0 ? "Plang" : "", drops % 7 == 0 ? "Plong" : ""); + + if (strlen(result) == 0) + sprintf(result, "%d", drops); + + return result; +} +``` + +This approach uses a single call to the [`sprintf` function][sprintf] to build the result string; +it contains a series of [ternary conditional operators][conditional-opeator]. +For more information, check the [`sprintf` functon approach][approach-sprintf]. + +## Approach: Data-Driven Programming + +**raindrops.h** + +```c +#ifndef RAINDROPS_H +#define RAINDROPS_H + +char *convert(char result[], int drops); + +#endif +``` + +**raindrops.c** + +```c +#include "raindrops.h" + +#include +#include + +typedef struct { + int factor; + const char *sound; +} sound_t; + +static const sound_t SOUNDS[] = { + { 3, "Pling" }, + { 5, "Plang" }, + { 7, "Plong" }, +}; + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) + +char *convert(char result[], int drops) +{ + for (size_t i = 0; i < ARRAY_SIZE(SOUNDS); i++) { + if (drops % SOUNDS[i].factor == 0) { + strcat(result, SOUNDS[i].sound); + } + } + + if (strlen(result) == 0) { + sprintf(result, "%d", drops); + } + + return result; +} +``` + +This approach puts some of the logic into data, simplifying the code. +For more information, check the [data-driven approach][approach-data-driven]. + +[modulo-operator]: https://www.geeksforgeeks.org/modulo-operator-in-c-cpp-with-examples/ +[conditional-operator]: https://www.geeksforgeeks.org/conditional-or-ternary-operator-in-c/ +[sprintf]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sprintf.html +[approach-if-statements]: https://exercism.org/tracks/c/exercises/raindrops/approaches/if-statements +[approach-sprintf]: https://exercism.org/tracks/c/exercises/raindrops/approaches/sprintf +[approach-data-driven]: https://exercism.org/tracks/c/exercises/raindrops/approaches/data-driven diff --git a/exercises/practice/raindrops/.approaches/sprintf/content.md b/exercises/practice/raindrops/.approaches/sprintf/content.md new file mode 100644 index 00000000..27df7bdf --- /dev/null +++ b/exercises/practice/raindrops/.approaches/sprintf/content.md @@ -0,0 +1,50 @@ +## `sprintf` Function + +**raindrops.h** + +```c +#ifndef RAINDROPS_H +#define RAINDROPS_H + +char *convert(char result[], int drops); + +#endif +``` + +**raindrops.c** + +```c +#include "raindrops.h" + +#include +#include + +#include "raindrops.h" + +#include +#include + +char *convert(char result[], int drops) +{ + sprintf(result, "%s%s%s", drops % 3 == 0 ? "Pling" : "", + drops % 5 == 0 ? "Plang" : "", drops % 7 == 0 ? "Plong" : ""); + + if (strlen(result) == 0) + sprintf(result, "%d", drops); + + return result; +} +``` + +This approach allows for very concise, if not a bit obfuscated, code. + +A series of [ternary conditional operators][conditional-operator] check if `drops` is a multiple of `3`, `5`, or `7` respectively. +If it is, the expression returns the appropriate raindrop sound (either `"Pling"`, `"Plang"` or `"Plong"`); otherwise, the result is the empty string (`""`). + +Then, in the same statement, a single call to the [`sprintf` function][sprintf] concatenates all the above three strings. + +Finally, the [`strlen` function][strlen] checks if `result` is empty: if so, `sprintf` is used again to format `drops` as string into `result`. + +[conditional-operator]: https://www.geeksforgeeks.org/conditional-or-ternary-operator-in-c/ +[sprintf]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sprintf.html +[strlen]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/strlen.html diff --git a/exercises/practice/raindrops/.approaches/sprintf/snippet.txt b/exercises/practice/raindrops/.approaches/sprintf/snippet.txt new file mode 100644 index 00000000..ee1362d2 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/sprintf/snippet.txt @@ -0,0 +1,2 @@ + sprintf(result, "%s%s%s", drops % 3 == 0 ? "Pling" : "", + drops % 5 == 0 ? "Plang" : "", drops % 7 == 0 ? "Plong" : "");