-
-
Notifications
You must be signed in to change notification settings - Fork 176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement exercise 'say' #275
Conversation
exercises/say/src/example.c
Outdated
"", "", "twenty", "thirty", "forty", | ||
"fifty", "sixty", "seventy", "eighty", "ninety" | ||
}; | ||
const int below_100_len[] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These arrays may be a good candidate for introducing a typedef'd struct that holds both the number and the word. What do you think?
It may even be a good opportunity for you to learn what X-macros are and what they are sometimes used for, if you haven't come across them yet. Not suggesting that X-macros should be introduced to the C track here though as they would likely be an exercise (or several) in themselves 😃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, the arrays could have file-scope, rather than being part of the function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
X macros info if interested:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is indeed a lot of clutter like this :/
I never thought about struct on this one, which is obviously a better, more integrated choice.
Also never heard of the 'X-macros'. It looks interesting - thanks for the links! Always glad to learn new stuff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this exercise I decided to go the 'struct'-route.
As you said maybe would be nice to have exercises with X-macros. I personally will gladly experiment more with them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use X-macros a lot at work. They add some cognitive overhead where they are used, but if you use them at the right time they are incredibly powerful.
exercises/say/src/example.c
Outdated
/* digits count */ | ||
int digits = floor(log10(input)) + 1; | ||
|
||
/* take care of spaces/hyphens, thousends and termination */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: thousends
-> thousands
, note the a
exercises/say/makefile
Outdated
|
||
tests.out: test/test_say.c src/say.c src/say.h | ||
@echo Compiling $@ | ||
@cc $(CFLAGS) src/say.c test/vendor/unity.c test/test_say.c -o tests.out -lm |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-lm
should probably be added by the variable LDFLAGS
(doesn't currently exist). See https://stackoverflow.com/questions/13249610/how-to-use-ldflags-in-makefile for an example of LDFLAGS
- update config.json where 'unlocked_by' was missing; - use 'LDFLAGS' in the 'Makefile'; - switch to structs for the names/sizes; - fix typo in example.c; - remove redundant 'TEST_ASSERT_NULL' in the tests.
Thanks @wolf99 for the review and the suggestions. |
exercises/say/src/example.h
Outdated
@@ -1,6 +1,12 @@ | |||
#ifndef SAY_H | |||
#define SAY_H | |||
#include <stdint.h> | |||
#include <stddef.h> | |||
|
|||
typedef struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the typedef can go inside example.c
, right? No need for it to be exposed to the caller if map
is only used internally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I think we've been following the convention of giving type names a suffix of _t
, e.g. map_t
.
This is common in non-POSIX environments to make it clear what is a type name and what is a variable name (because a lot of editors don't do syntax highlighting for custom types).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Completely agree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, the reason I mention non-POSIX environment is that names ending with _t
are reserved by POISX so code that uses POSIX will generally not follow this. Everywhere else it is a common idiom though.
exercises/say/test/test_say.c
Outdated
@@ -15,7 +15,6 @@ void test_zero(void) | |||
char *ans = NULL; | |||
int res = say(0, &ans); | |||
TEST_ASSERT_EQUAL_INT(0, res); | |||
TEST_ASSERT_NOT_NULL(ans); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why remove the tests for NULL
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TEST_ASSERT_EQUAL_STRING
checks already for NULL
(see unity.c:1014).
This was first brought to my attention by @patricksjackson in this PR
- rename the typedef to 'map_t'; - move the typedef to internal declaration.
exercises/say/src/example.c
Outdated
if (tmp / 100) { | ||
int i = tmp / 100; | ||
strcpy(head, below_10[i].name); | ||
head += below_10[i].size; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of .size
, how about using strlen
? This will be less fragile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean renaming 'size' to 'strlen' or really using 'strlen' to calculate the size?
My point of using the string-length pairs was to spare the size calculation iterations over predefined const char*
's. I know there are small number of strings, but it hurts somehow to calculate their length over and over again. Also they are not supposed to be changed, as long as the names in English for numbers remain the same :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was suggesting that you use strlen
to calculate on-demand instead of counting characters by hand. But I agree with your reasoning for using a length known at compile time.
exercises/say/src/example.c
Outdated
|
||
char *head = *ans; | ||
while (input) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this line left intentionally blank? I have the same question about line 72.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well that was me experimenting with formatting in my editor.
|
||
/* 'zero' is a corner case */ | ||
if (input == 0) { | ||
*ans = malloc(5); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about *ans = "zero";
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will require you to change to const char **ans
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can I then build the result if it is const
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a pointer to const data and the compiler will replace a string literal with a const void *
that points to the constant string data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I understand now what you're saying: the client needs to be able to free
this memory so we can't declare it as const
. I agree with you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
My question was how to use strcpy
when the destination string is a const
? Build the result in a modifiable char and then cast the return to const char
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, in this case I was suggesting that you not use strcpy
but instead just set *ans
to a string literal. This would work except that it would not be possible to free the string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it - thanks for the clarification!
exercises/say/src/example.c
Outdated
}; | ||
|
||
/* below_20's are like two times below_10's so this will work */ | ||
static const int widest = 8; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"seventeen"
has length 9, so should this be 9 instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
17 is in the order of 10^1, meaning it occupies two digits. But 17 also gets represented by a single word. So it is shorter or equal than everything that is not '-teen' on the same order of magnitude. One of the longest 10^1 order digit is for example 77 which makes together length of 12 ('seventy' + 'seven'), occupying 2*widest = 16 bytes, leaving 4 free.
The value of 8 is actually the length of 'thousand' which would be the longest, non-complex word.
exercises/say/src/example.c
Outdated
int digits = floor(log10(input)) + 1; | ||
|
||
/* take care of spaces/hyphens, thousands and termination */ | ||
*ans = malloc((widest + 1) * (digits + (digits + 1) / 3) + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit lost on what some of these terms mean. Could you extract some enumerations, #define
s, or constants to make it clear what these mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try to improve this.
exercises/say/src/example.c
Outdated
} else if (tmp < 20) { | ||
tmp -= 10; | ||
strcpy(head, below_20[tmp].name); | ||
head += below_20[tmp].size; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two lines (or something very similar) are used quite often. How about extracting a helper function to reduce the duplication and make the code easier to read?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course. A very good suggestion.
Looks pretty good. This is a tough exercise. I think the most important improvements that can be made are ones that will make this a bit easier to read and understand since it is a dense algorithm. |
Thanks @ryanplusplus for the code review. |
👍 |
No description provided.