Avatar of ajborla

ajborla's solution

to Diamond in the C Track

Published at Aug 10 2019 · 0 comments
Instructions
Test suite
Solution

The diamond kata takes as its input a letter, and outputs it in a diamond shape. Given a letter, it prints a diamond starting with 'A', with the supplied letter at the widest point.

Requirements

  • The first row contains one 'A'.
  • The last row contains one 'A'.
  • All rows, except the first and last, have exactly two identical letters.
  • All rows have as many trailing spaces as leading spaces. (This might be 0).
  • The diamond is horizontally symmetric.
  • The diamond is vertically symmetric.
  • The diamond has a square shape (width equals height).
  • The letters form a diamond shape.
  • The top half has the letters in ascending order.
  • The bottom half has the letters in descending order.
  • The four corners (containing the spaces) are triangles.

Examples

In the following examples, spaces are indicated by · characters.

Diamond for letter 'A':

A

Diamond for letter 'C':

··A··
·B·B·
C···C
·B·B·
··A··

Diamond for letter 'E':

····A····
···B·B···
··C···C··
·D·····D·
E·······E
·D·····D·
··C···C··
···B·B···
····A····

Getting Started

Make sure you have read the "Guides" section of the C track on the Exercism site. This covers the basic information on setting up the development environment expected by the exercises.

Passing the Tests

Get the first test compiling, linking and passing by following the three rules of test-driven development.

The included makefile can be used to create and run the tests using the test task.

make test

Create just the functions you need to satisfy any compiler errors and get the test to fail. Then write just enough code to get the test to pass. Once you've done that, move onto the next test.

As you progress through the tests, take the time to refactor your implementation for readability and expressiveness and then go on to the next test.

Try to use standard C99 facilities in preference to writing your own low-level algorithms or facilities by hand.

Source

Seb Rose http://claysnow.co.uk/recycling-tests-in-tdd/

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

test_diamond.c

#include "vendor/unity.h"
#include "../src/diamond.h"
#include <stdlib.h>

#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))

void setUp(void)
{
}

void tearDown(void)
{
}

static void free_all(char **diamond)
{
   free(diamond[0]);
   free(diamond);
}

static void test_rows_degenerate_case_with_a_single_a_row(void)
{
   const char letter = 'A';
   const char *expected[] = {
      "A"
   };
   char **diamond = make_diamond(letter);
   TEST_ASSERT_EQUAL_STRING_ARRAY(expected, diamond, ARRAY_SIZE(expected));
   free_all(diamond);
}

static void
test_rows_degenerate_case_with_no_row_with_3_distinct_groups_of_spaces(void)
{
   TEST_IGNORE();               // delete this line to run test
   const char letter = 'B';
   const char *expected[] = {
      " A ",
      "B B",
      " A "
   };
   char **diamond = make_diamond(letter);
   TEST_ASSERT_EQUAL_STRING_ARRAY(expected, diamond, ARRAY_SIZE(expected));
   free_all(diamond);
}

static void
test_rows_smallest_non_degenerate_case_with_odd_diamond_side_length(void)
{
   TEST_IGNORE();
   const char letter = 'C';
   const char *expected[] = {
      "  A  ",
      " B B ",
      "C   C",
      " B B ",
      "  A  "
   };
   char **diamond = make_diamond(letter);
   TEST_ASSERT_EQUAL_STRING_ARRAY(expected, diamond, ARRAY_SIZE(expected));
   free_all(diamond);
}

static void
test_rows_smallest_non_degenerate_case_with_even_diamond_side_length(void)
{
   TEST_IGNORE();
   const char letter = 'D';
   const char *expected[] = {
      "   A   ",
      "  B B  ",
      " C   C ",
      "D     D",
      " C   C ",
      "  B B  ",
      "   A   "
   };
   char **diamond = make_diamond(letter);
   TEST_ASSERT_EQUAL_STRING_ARRAY(expected, diamond, ARRAY_SIZE(expected));
   free_all(diamond);
}

static void test_rows_largest_possible_diamond(void)
{
   TEST_IGNORE();
   const char letter = 'Z';
   const char *expected[] = {
      "                         A                         ",
      "                        B B                        ",
      "                       C   C                       ",
      "                      D     D                      ",
      "                     E       E                     ",
      "                    F         F                    ",
      "                   G           G                   ",
      "                  H             H                  ",
      "                 I               I                 ",
      "                J                 J                ",
      "               K                   K               ",
      "              L                     L              ",
      "             M                       M             ",
      "            N                         N            ",
      "           O                           O           ",
      "          P                             P          ",
      "         Q                               Q         ",
      "        R                                 R        ",
      "       S                                   S       ",
      "      T                                     T      ",
      "     U                                       U     ",
      "    V                                         V    ",
      "   W                                           W   ",
      "  X                                             X  ",
      " Y                                               Y ",
      "Z                                                 Z",
      " Y                                               Y ",
      "  X                                             X  ",
      "   W                                           W   ",
      "    V                                         V    ",
      "     U                                       U     ",
      "      T                                     T      ",
      "       S                                   S       ",
      "        R                                 R        ",
      "         Q                               Q         ",
      "          P                             P          ",
      "           O                           O           ",
      "            N                         N            ",
      "             M                       M             ",
      "              L                     L              ",
      "               K                   K               ",
      "                J                 J                ",
      "                 I               I                 ",
      "                  H             H                  ",
      "                   G           G                   ",
      "                    F         F                    ",
      "                     E       E                     ",
      "                      D     D                      ",
      "                       C   C                       ",
      "                        B B                        ",
      "                         A                         "
   };
   char **diamond = make_diamond(letter);
   TEST_ASSERT_EQUAL_STRING_ARRAY(expected, diamond, ARRAY_SIZE(expected));
   free_all(diamond);
}

int main(void)
{
   UnityBegin("test/test_diamond.c");

   RUN_TEST(test_rows_degenerate_case_with_a_single_a_row);
   RUN_TEST
       (test_rows_degenerate_case_with_no_row_with_3_distinct_groups_of_spaces);
   RUN_TEST
       (test_rows_smallest_non_degenerate_case_with_odd_diamond_side_length);
   RUN_TEST
       (test_rows_smallest_non_degenerate_case_with_even_diamond_side_length);
   RUN_TEST(test_rows_largest_possible_diamond);

   return UnityEnd();
}

src/diamond.c

/* ------------------------------------------------------------------------- */
/* exercism.io                                                               */
/* C Track Exercise: diamond                                                 */
/* Contributed: Anthony J. Borla (ajborla@bigpond.com)                       */
/* ------------------------------------------------------------------------- */

#include "diamond.h"

#include <stdlib.h>
#include <ctype.h>
#include <inttypes.h>

// Invariant values (computed once at startup) for building diamond
typedef struct diamond_invariants
{
  char character;
  int8_t code;

  int8_t num_cols_total;
  int8_t num_rows_top;
  int8_t num_rows_bottom;

} diamond_invariants_t;

// Some helpful constants
static const char CH_SPACE = ' ';
static const char CH_NUL = '\0';
static const char CH_UPPER_A = 'A';

/* ------------------------------------------------------------------------- */
/* Given a character, 'character', returns a structure of invariant values   */
/* needed in building a diamond. Note, a -1-filled structure is returned if  */
/* character is not an uppercase letter                                      */
/* ------------------------------------------------------------------------- */
static diamond_invariants_t make_diamond_invariants(char character)
{
    diamond_invariants_t diamond_invariants = { character, -1, -1, -1, -1 };

    // Fill structure only if 'character' range is 'A' through 'Z'
    if (isalpha(character))
    {
        // Character
        diamond_invariants.character = toupper(character);

        // Character as number ('A' -> 0, 'B' -> 1, ... 'Z' -> 25)
        diamond_invariants.code = diamond_invariants.character - CH_UPPER_A;

        // Number of columns in (or length of) row
        diamond_invariants.num_cols_total = 2 * diamond_invariants.code + 1;

        // Special case 'A' - always a single row
        if (diamond_invariants.character == CH_UPPER_A)
            diamond_invariants.num_rows_top = 0;
        else
            diamond_invariants.num_rows_top =
                (diamond_invariants.num_cols_total - 1) / 2;

        // Same number of top and bottom rows
        diamond_invariants.num_rows_bottom = diamond_invariants.num_rows_top;
    }

    return diamond_invariants;
}

/* ------------------------------------------------------------------------- */
/* Given a character, 'character', a structure of invariant values, and a    */
/* buffer area, builds a row and returns the buffer address                  */
/* ------------------------------------------------------------------------- */
static char* gen_diamond_row(char character,
                             diamond_invariants_t* ptr_diamond_invariants,
                             char* buffer)
{
    // Ensure current character is uppercased
    char current_character = toupper(character);

    // Check for buffer allocation
    if (!buffer) return NULL;

    // "A" (special case - single letter in row, no additional spacing)
    if (ptr_diamond_invariants->character == CH_UPPER_A)
    {
        buffer[0] = CH_UPPER_A; buffer[1] = CH_NUL;
    }

    // "C...C" Reference row, marks widest point of diamond. Supplied character
    // starts and ends the row, only spaces are intervening ones
    else if (ptr_diamond_invariants->character == current_character)
    {
        // Bounding (first, last) characters
        buffer[0] =
            buffer[ptr_diamond_invariants->num_cols_total - 1] = current_character;

        // Padding (middle) spaces
        int8_t i = 1, last_col_idx = ptr_diamond_invariants->num_cols_total - 1;
        while (i < last_col_idx) buffer[i++] = CH_SPACE;

        // Zero terminator
        buffer[ptr_diamond_invariants->num_cols_total] = CH_NUL;
    }

    // "..C....C.." Non-reference rows, containing leading, intervening, and
    // trailing spaces
    else
    {
        // Zero terminator
        buffer[ptr_diamond_invariants->num_cols_total] = CH_NUL;

        // Fill row with spaces
        int8_t i = 0, last_col_idx = ptr_diamond_invariants->num_cols_total;
        while (i < last_col_idx) buffer[i++] = CH_SPACE;

        // Compute indexes, place the current character in those locations
        int8_t num_pad_spaces =
            ptr_diamond_invariants->code - (current_character - CH_UPPER_A);

        buffer[num_pad_spaces] = current_character;

        buffer[ptr_diamond_invariants->num_cols_total - num_pad_spaces - 1] =
            current_character;
    }

    return buffer;
}

// See header description
char** make_diamond(char letter)
{
    // Compute and package runtime values
    diamond_invariants_t diamond_invariants = make_diamond_invariants(letter);

    // Ensure we have an uppercase caharacter
    char character = toupper(letter);

    // Compute required rows then allocate and validate memory allocation
    int8_t rows =
        diamond_invariants.num_rows_bottom + diamond_invariants.num_rows_top + 1;

    char** tbl = NULL;

    if ((tbl = malloc(rows * sizeof(char*))) != NULL)
    {
        // Generate top part of diamond
        for (int8_t c = CH_UPPER_A; c < character; ++c)
            gen_diamond_row(c, &diamond_invariants,
                (tbl[--rows] = malloc(diamond_invariants.num_cols_total * sizeof(char))));

        // Generate bottom part of diamond
        for (int8_t c = character; c >= CH_UPPER_A; --c)
            gen_diamond_row(c, &diamond_invariants,
                (tbl[--rows] = malloc(diamond_invariants.num_cols_total * sizeof(char))));
    }

    // Return diamond table - caller will free memory
    return tbl;
}

src/diamond.h

/* ------------------------------------------------------------------------- */
/* exercism.io                                                               */
/* C Track Exercise: diamond                                                 */
/* Contributed: Anthony J. Borla (ajborla@bigpond.com)                       */
/* ------------------------------------------------------------------------- */

#ifndef DIAMOND_H
#define DIAMOND_H

/* ------------------------------------------------------------------------- */
/* Given a letter, 'letter', returns a table of strings which comprise the   */
/* rows of a 'diamond'. The supplied letter is used to form the reference    */
/* row; it is the longest row, and appears in the middle of the shape. The   */
/* other rows (an equal number above and below the reference row) are formed */
/* with letters of the alphabet counting down from the supplied letter until */
/* the letter 'A'. Examples:                                                 */
/*                                                                           */
/*        A                                                                  */
/*       B B           A                                                     */
/*      C   C         B B         A                                          */
/*     D     D       C   C       B B    A                                    */
/*      C   C         B B         A                                          */
/*       B B           A                                                     */
/*        A                                                                  */
/* ------------------------------------------------------------------------- */
char** make_diamond(char letter);

#endif

Community comments

Find this solution interesting? Ask the author a question to learn more.

ajborla's Reflection

More challenging (but certainly more fun) than I had expected.