C two-buckets, 1st running version

This commit is contained in:
2021-09-07 21:46:23 +02:00
parent 07eb95fb58
commit 7b92456167
8 changed files with 699 additions and 0 deletions

56
c/two-bucket/GNUmakefile Normal file
View File

@@ -0,0 +1,56 @@
# The original 'makefile' has a flaw:
# 1) it overrides CFLAGS
# 2) it does not pass extra "FLAGS" to $(CC) that could come from environment
#
# It means :
# - we need to edit 'makefile' for different builds (DEBUG, etc...), which is
# not practical at all.
# - Also, it does not allow to run all tests without editing the test source
# code.
#
# To use this makefile (GNU make only):
# 1) copy it into exercise directory
# 2) add ex.h to exercise include file
# 3) add ex.c to exercise source code, and create a suitable main function
# 4) use make with one of the following targets :
# all: compile and run all predefined tests.
# nowarn: compile with no -Werror, and run all predefined tests
# debug: compile with -DDEBUG and run all predefined tests
# mem: perform memcheck with all tests enabled
# unit: build standalone (unit) bimary
# unitnowarn: build standalone (unit) binary with -Werror disabled
# unitdebug: build standalone binary with -DDEBUG
#
# Original 'makefile' targets can be used (test, memcheck, clean, ...)
.PHONY: default all nowarn debug mem unit unitnowarn unitdebug standalone
default: all
ALLSOURCES:=$(wildcard ./*.c)
TESTSOURCES:=$(wildcard ./test_*.c)
SRC:=$(filter-out $(TESTSOURCES),$(ALLSOURCES))
include makefile
all: CFLAGS+=-DTESTALL
all: clean test
nowarn: CFLAGS:=$(filter-out -Werror,$(CFLAGS))
nowarn: clean all
debug: CFLAGS+=-DDEBUG
debug: all
mem: CFLAGS+=-DTESTALL
mem: clean memcheck
unitnowarn: CFLAGS:=$(filter-out -Werror,$(CFLAGS))
unitnowarn: clean unit
unitdebug: CFLAGS+=-DDEBUG
unitdebug: clean unit
unit: CFLAGS+=-DUNIT_TEST
unit: *.c *.h
$(CC) $(CFLAGS) $(SRC) -o tests.out $(LIBS)

63
c/two-bucket/HELP.md Normal file
View File

@@ -0,0 +1,63 @@
# Help
## Running the tests
Get the first test compiling, linking and passing by following the [three rules of test-driven development][3-tdd-rules].
The included makefile can be used to create and run the tests using the `test` task.
```console
$ 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.
[3-tdd-rules]: http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd
## Submitting your solution
You can submit your solution using the `exercism submit two_bucket.c two_bucket.h` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [C track's documentation](https://exercism.org/docs/tracks/c)
- [Exercism's support channel on gitter](https://gitter.im/exercism/support)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
Make sure you have read the "Guides" section of the [C track][c-track] on the Exercism site.
This covers the basic information on setting up the development environment expected by the exercises.
## Submitting Incomplete Solutions
If you are struggling with a particular exercise, it is possible to submit an incomplete solution so you can see how others have completed the exercise.
## Resources
To get help if having trouble, you can use the following resources:
- [StackOverflow][] can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.
- [CPPReference][] can be used to look up information on C concepts, operators, types, standard library functions and more.
- [TutorialsPoint][] has similar content as CPPReference in its C programming section.
- [The C Programming][K&R] book by K&R is the original source of the language and is still useful today.
[c-track]: https://exercism.io/my/tracks/c
[stackoverflow]: http://stackoverflow.com/questions/tagged/c
[cppreference]: https://en.cppreference.com/w/c
[tutorialspoint]: https://www.tutorialspoint.com/cprogramming/
[K&R]: https://www.amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/0131103628/

49
c/two-bucket/README.md Normal file
View File

@@ -0,0 +1,49 @@
# Two Bucket
Welcome to Two Bucket on Exercism's C Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
Given two buckets of different size, demonstrate how to measure an exact number of liters by strategically transferring liters of fluid between the buckets.
Since this mathematical problem is fairly subject to interpretation / individual approach, the tests have been written specifically to expect one overarching solution.
To help, the tests provide you with which bucket to fill first. That means, when starting with the larger bucket full, you are NOT allowed at any point to have the smaller bucket full and the larger bucket empty (aka, the opposite starting point); that would defeat the purpose of comparing both approaches!
Your program will take as input:
- the size of bucket one
- the size of bucket two
- the desired number of liters to reach
- which bucket to fill first, either bucket one or bucket two
Your program should determine:
- the total number of "moves" it should take to reach the desired number of liters, including the first fill
- which bucket should end up with the desired number of liters (let's say this is bucket A) - either bucket one or bucket two
- how many liters are left in the other bucket (bucket B)
Note: any time a change is made to either or both buckets counts as one (1) move.
Example:
Bucket one can hold up to 7 liters, and bucket two can hold up to 11 liters. Let's say bucket one, at a given step, is holding 7 liters, and bucket two is holding 8 liters (7,8). If you empty bucket one and make no change to bucket two, leaving you with 0 liters and 8 liters respectively (0,8), that counts as one "move". Instead, if you had poured from bucket one into bucket two until bucket two was full, leaving you with 4 liters in bucket one and 11 liters in bucket two (4,11), that would count as only one "move" as well.
To conclude, the only valid moves are:
- pouring from either bucket to another
- emptying either bucket and doing nothing to the other
- filling either bucket and doing nothing to the other
Written with <3 at [Fullstack Academy](http://www.fullstackacademy.com/) by Lindsay Levine.
## Source
### Created by
- @ryanplusplus
### Contributed to by
- @wolf99
### Based on
Water Pouring Problem - http://demonstrations.wolfram.com/WaterPouringProblem/

45
c/two-bucket/br-common.h Normal file
View File

@@ -0,0 +1,45 @@
#ifndef __BR_COMMON_H
#define __BR_COMMON_H
/* ${LINUX_SRC}/include/linux/compiler_attributes.h, around line 30
*
* __has_attribute is supported on gcc >= 5, clang >= 2.9 and icc >= 17.
* For other compilers, simple implementation (for __falltrough__ only)
*/
#ifndef __has_attribute
# define __has_attribute(x) __GCC4_has_attribute_##x
# define __GCC4_has_attribute___assume_aligned__ (__GNUC_MINOR__ >= 9)
# define __GCC4_has_attribute___copy__ 0
# define __GCC4_has_attribute___designated_init__ 0
# define __GCC4_has_attribute___externally_visible__ 1
# define __GCC4_has_attribute___no_caller_saved_registers__ 0
# define __GCC4_has_attribute___noclone__ 1
# define __GCC4_has_attribute___nonstring__ 0
# define __GCC4_has_attribute___no_sanitize_address__ (__GNUC_MINOR__ >= 8)
# define __GCC4_has_attribute___no_sanitize_undefined__ (__GNUC_MINOR__ >= 9)
# define __GCC4_has_attribute___fallthrough__ 0
# define __GCC4_has_attribute___fallthrough__ 0
#endif
/* ${LINUX_SRC}/include/linux/compiler_attributes.h, around line 200
*/
#if __has_attribute(__fallthrough__)
# define fallthrough __attribute__((__fallthrough__))
#else
# define fallthrough do {} while (0); /* fallthrough */
#endif
/* See GNUmakefile below for explanation
* https://github.com/braoult/exercism/blob/master/c/templates/GNUmakefile
*/
#if defined UNIT_TEST || defined DEBUG
# include <stdio.h>
# include <stdlib.h>
#endif
#ifdef TESTALL
# undef TEST_IGNORE
# define TEST_IGNORE() {}
#endif
#endif /* __BR_COMMON_H */

37
c/two-bucket/makefile Normal file
View File

@@ -0,0 +1,37 @@
### If you wish to use extra libraries (math.h for instance),
### add their flags here (-lm in our case) in the "LIBS" variable.
LIBS = -lm
###
CFLAGS = -std=c99
CFLAGS += -g
CFLAGS += -Wall
CFLAGS += -Wextra
CFLAGS += -pedantic
CFLAGS += -Werror
CFLAGS += -Wmissing-declarations
CFLAGS += -DUNITY_SUPPORT_64
ASANFLAGS = -fsanitize=address
ASANFLAGS += -fno-common
ASANFLAGS += -fno-omit-frame-pointer
.PHONY: test
test: tests.out
@./tests.out
.PHONY: memcheck
memcheck: ./*.c ./*.h
@echo Compiling $@
@$(CC) $(ASANFLAGS) $(CFLAGS) test-framework/unity.c ./*.c -o memcheck.out $(LIBS)
@./memcheck.out
@echo "Memory check passed"
.PHONY: clean
clean:
rm -rf *.o *.out *.out.dSYM
tests.out: ./*.c ./*.h
@echo Compiling $@
@$(CC) $(CFLAGS) test-framework/unity.c ./*.c -o tests.out $(LIBS)

View File

@@ -0,0 +1,139 @@
#include "test-framework/unity.h"
#include "two_bucket.h"
void setUp(void)
{
}
void tearDown(void)
{
}
static void assert_results_match(bucket_result_t expected,
bucket_result_t actual)
{
TEST_ASSERT_EQUAL(expected.possible, actual.possible);
if (expected.possible) {
TEST_ASSERT_EQUAL(expected.move_count, actual.move_count);
TEST_ASSERT_EQUAL(expected.goal_bucket, actual.goal_bucket);
TEST_ASSERT_EQUAL(expected.other_bucket_liters,
actual.other_bucket_liters);
}
}
static void
test_measure_using_bucket_one_of_size_3_and_bucket_two_of_size_5_start_with_bucket_one
(void) {
bucket_result_t expected = {.possible = true,.move_count = 4,.goal_bucket =
BUCKET_ID_1,.other_bucket_liters = 5
};
bucket_result_t actual = measure(3, 5, 1, BUCKET_ID_1);
assert_results_match(expected, actual);
}
static void
test_measure_using_bucket_one_of_size_3_and_bucket_two_of_size_5_start_with_bucket_two
(void) {
TEST_IGNORE();
bucket_result_t expected = {.possible = true,.move_count = 8,.goal_bucket =
BUCKET_ID_2,.other_bucket_liters = 3
};
bucket_result_t actual = measure(3, 5, 1, BUCKET_ID_2);
assert_results_match(expected, actual);
}
static void
test_measure_using_bucket_one_of_size_7_and_bucket_two_of_size_11_start_with_bucket_one
(void) {
TEST_IGNORE();
bucket_result_t expected = {.possible = true,.move_count = 14,.goal_bucket =
BUCKET_ID_1,.other_bucket_liters = 11
};
bucket_result_t actual = measure(7, 11, 2, BUCKET_ID_1);
assert_results_match(expected, actual);
}
static void
test_measure_using_bucket_one_of_size_7_and_bucket_two_of_size_11_start_with_bucket_two
(void) {
TEST_IGNORE();
bucket_result_t expected = {.possible = true,.move_count = 18,.goal_bucket =
BUCKET_ID_2,.other_bucket_liters = 7
};
bucket_result_t actual = measure(7, 11, 2, BUCKET_ID_2);
assert_results_match(expected, actual);
}
static void
test_measure_one_step_using_bucket_one_of_size_1_and_bucket_two_of_size_3_start_with_bucket_two
(void) {
TEST_IGNORE();
bucket_result_t expected = {.possible = true,.move_count = 1,.goal_bucket =
BUCKET_ID_2,.other_bucket_liters = 0
};
bucket_result_t actual = measure(1, 3, 3, BUCKET_ID_2);
assert_results_match(expected, actual);
}
static void
test_measure_using_bucket_one_of_size_2_and_bucket_two_of_size_3_start_with_bucket_one_and_end_with_bucket_two
(void) {
TEST_IGNORE();
bucket_result_t expected = {.possible = true,.move_count = 2,.goal_bucket =
BUCKET_ID_2,.other_bucket_liters = 2
};
bucket_result_t actual = measure(2, 3, 3, BUCKET_ID_1);
assert_results_match(expected, actual);
}
static void test_not_possible_to_reach_the_goal(void)
{
TEST_IGNORE();
bucket_result_t expected = {.possible = false };
bucket_result_t actual = measure(6, 15, 5, BUCKET_ID_1);
assert_results_match(expected, actual);
}
static void
test_with_the_same_buckets_but_a_different_goal_then_it_is_possible(void)
{
TEST_IGNORE();
bucket_result_t expected = {.possible = true,.move_count = 10,.goal_bucket =
BUCKET_ID_2,.other_bucket_liters = 0
};
bucket_result_t actual = measure(6, 15, 9, BUCKET_ID_1);
assert_results_match(expected, actual);
}
static void test_goal_larger_than_both_buckets_is_impossible(void)
{
TEST_IGNORE();
bucket_result_t expected = {.possible = false };
bucket_result_t actual = measure(5, 7, 8, BUCKET_ID_1);
assert_results_match(expected, actual);
}
int main(void)
{
UnityBegin("test_two_bucket.c");
RUN_TEST
(test_measure_using_bucket_one_of_size_3_and_bucket_two_of_size_5_start_with_bucket_one);
RUN_TEST
(test_measure_using_bucket_one_of_size_3_and_bucket_two_of_size_5_start_with_bucket_two);
RUN_TEST
(test_measure_using_bucket_one_of_size_7_and_bucket_two_of_size_11_start_with_bucket_one);
RUN_TEST
(test_measure_using_bucket_one_of_size_7_and_bucket_two_of_size_11_start_with_bucket_two);
RUN_TEST
(test_measure_one_step_using_bucket_one_of_size_1_and_bucket_two_of_size_3_start_with_bucket_two);
RUN_TEST
(test_measure_using_bucket_one_of_size_2_and_bucket_two_of_size_3_start_with_bucket_one_and_end_with_bucket_two);
RUN_TEST(test_not_possible_to_reach_the_goal);
RUN_TEST
(test_with_the_same_buckets_but_a_different_goal_then_it_is_possible);
RUN_TEST(test_goal_larger_than_both_buckets_is_impossible);
return UnityEnd();
}

283
c/two-bucket/two_bucket.c Normal file
View File

@@ -0,0 +1,283 @@
#include "two_bucket.h"
#include <stdio.h>
#include <stdlib.h>
/* We will choose to use a board of (X,Y) squares, with the sizes of the 2
* buckets, where we move the current point at each step.
* Current square (x,y) is enough to describe the status of the
* 2 buckets: b1 has x liters, b2 has y liters.
* At square (x, y), a maximum of 6 moves are possible :
* - (0, y) = empty b1
* - (X, y) = fill b1
* - (x, 0) = empty be
* - (x, Y) = fill b2
* - (x - d, y + d) = b1 -> b2, with d = min(Y-y, x)
* - (x + d, y - d) = b2 -> b1, with d = min(X-x, y)
* The board keeps tracks of visited squares. We cannot pass twice the
* same one.
* We will use BFS algorithm from initial square, which is (0, Y) or (X, 0).
* We stop when x or y are equal to goal, or when no moves are possible.
*
*/
/* The board contains a positive integer for its current move, otherwise the
* following values
*/
#define AVAILABLE 0
#define FORBIDDEN (-1)
#define GOAL (-2)
//#define CURRENT (-4)
#define SQUARE(x, y, X) ((y) * (X) + (x))
#define COL(p, X) ((p) % (X))
#define ROW(p, X) ((p) / (X))
/* danger zone !! - this macro is BAD™ in general, dangerous double
* evaluation, but ok here. OK, I promise, only here ;-)
* If unsure why this is very bad™, let me a comment on exercism.
*/
#define MAX(x, y) ((x) >= (y)? (x): (y))
#define MIN(x, y) ((x) <= (y)? (x): (y))
static void board_print(int *board, int x, int y)
{
printf("board(%d, %d)\n", x, y);
for (int row = y-1; row >= 0; --row) {
printf("%03d ", row);
for (int col = 0; col < x; ++col) {
//printf("C: i=%d j=%d cell %d\n", i, j, i*x+j);
//printf(" %d ", board[i*x+j]);
printf(" %2d ", board[SQUARE(col, row, x)]);
//printf("row=%d col=%d x=%d square=%d\n", row, col, x, SQUARE(col, row, x));
}
putchar('\n');
}
putchar('\n');
}
static inline void board_init(int x, int y, int goal, int *board)
{
int i;
printf("board_init(%d, %d, goal=%d)\n", x, y, goal);
//board_print(board, x, y);
for (i=0; i < x*y; ++i) {
//printf("setting square %d (%d, %d)\n", i, COL(i, x), ROW(i, x));
*(board+i) = AVAILABLE;
}
board_print(board, x, y);
/* set target on goal row */
if (x >= goal) {
for (i = 0; i < y; ++i) {
printf("A: x=%d i=%d goal=%d setting %d\n", x, i, goal, SQUARE(goal, i, x));
board[SQUARE(goal, i, x)] = GOAL;
//board_print(board, x, y);
}
}
/* set target on goal col */
board_print(board, x, y);
if (y >= goal) {
for (i = 0; i < x; ++i) {
printf("B: x=%d i=%d goal=%d setting %d\n", x, i, goal, SQUARE(i, goal, x));
board[SQUARE(i, goal, x)] = GOAL;
}
board_print(board, x, y);
}
}
struct stack {
int first;
int last;
int stack[];
};
static inline void push(struct stack *s, int v)
{
printf("pushing v=%d\n", v);
//if (s->stack[s->last] == AVAILABLE)
s->stack[s->last++] = v;
}
static inline int pop(struct stack *s)
{
int res;
printf("pop: first=%d last=%d val=%d\n", s->first, s->last, s->stack[s->first]);
res = s->first < s->last ? s->stack[s->first++] : -1;
printf("popping v=%d\n", res);
return res;
}
static int move_maybe(int *board, struct stack *stack, int col, int row, int level ,int X, char *text)
{
printf("trying to [%s] on (%d, %d) - level=%d curval=%d : ",
text, col, row, level, board[SQUARE(col, row, X)]);
if (board[SQUARE(col, row, X)] == AVAILABLE) {
printf("OK\n");
board[SQUARE(col, row, X)] = level;
push (stack, SQUARE(col, row, X));
} else if (board[SQUARE(col, row, X)] == GOAL) {
printf("**************** GOAL\n");
return 1;
} else {
printf("Impossible\n");
}
return 0;
}
static bucket_result_t board_bfs(int *board, struct stack *stack, int start,
const int X, const int Y, const int goal)
{
int level = 0;
int cur_square;
int min, row, col, col1, row1;
bucket_result_t res = { .possible = false, .move_count = 0 };
/* initialize 1st square used */
printf("bfs(start=%d, X=%d, Y=%d)\n", start, X, Y);
if (start == BUCKET_ID_1) {
push(stack, SQUARE(X-1, 0, X));
board[SQUARE(X-1, 0, X)] = 1;
board[SQUARE(0, Y-1, X)] = FORBIDDEN;
} else {
push(stack, SQUARE(0, Y-1, X));
board[SQUARE(0, Y-1, X)] = 1;
board[SQUARE(X-1, 0, X)] = FORBIDDEN;
}
board_print(board, X, Y);
/* now we consume as we can... */
while ((cur_square = pop(stack)) >= 0) {
col=COL(cur_square, X);
row=ROW(cur_square, X);
level=board[cur_square];
board_print(board, X, Y);
printf("move[pop=%d](col=%d, row=%d)=%d level=%d\n",
cur_square, col, row, board[cur_square], level);
switch (level) {
case GOAL: /* we win */
printf("*** SHOULD NOT BE HERE\n");
goto found;
case AVAILABLE: /* should not happen */
goto end;
default:
if (col > 0) {
col1 = 0;
row1 = row;
if (move_maybe(board, stack, col1, row1, level+1, X, "empty b1"))
goto found;
}
if (row > 0) {
col1 = col;
row1 = 0;
if (move_maybe(board, stack, col1, row1, level+1, X, "empty b2"))
goto found;
}
if (col < X-1) {
col1 = X-1;
row1 = row;
if (move_maybe(board, stack, col1, row1, level+1, X, "fill b1"))
goto found;
}
if (row < Y-1) {
col1 = col;
row1 = Y-1;
if (move_maybe(board, stack, col1, row1, level+1, X, "fill b2"))
goto found;
}
if (col > 0 && row < Y-1) {
min = MIN(col, Y-row-1);
col1 = col-min;
row1 = row+min;
if (move_maybe(board, stack, col1, row1, level+1, X,
"pour b1 in b2"))
goto found;
}
if (row > 0 && col < X-1) {
min = MIN(row, X-col-1);
col1 = col+min;
row1 = row-min;
if (move_maybe(board, stack, col1, row1, level+1, X,
"pour b2 in b1"))
goto found;
}
}
}
goto end;
found:
res.possible = true;
res.move_count = level+1;//board[SQUARE(col1, row1, X)]+1;
if (col1 == goal) {
res.goal_bucket=BUCKET_ID_1;
res.other_bucket_liters=row1;
} else {
res.goal_bucket=BUCKET_ID_2;
res.other_bucket_liters=col1;
}
end:
printf("END: possible=%d\n", res.possible);
printf(" move-count=%d\n", res.move_count);
printf(" goal-bucket=%d\n", res.goal_bucket);
printf(" other-bucket=%d\n", res.other_bucket_liters);
return res;
}
bucket_result_t measure(const bucket_liters_t b1,
const bucket_liters_t b2,
const bucket_liters_t goal,
const bucket_id_t start)
{
bucket_result_t res = {
.possible = false,
.move_count = 0,
.other_bucket_liters = 0
};
int board[(b1+1)*(b2+1)];
printf("measure(b1=%d, b2=%d, goal=%d, start=%d)\n", b1, b2, goal, start);
if (goal > b1 && goal > b2)
return res;
if ((start == BUCKET_ID_1 && goal == b1) ||
(start == BUCKET_ID_2 && goal == b2)) {
printf("done in 1\n");
res.possible = true;
res.move_count = 1;
res.goal_bucket = start;
return res;
}
struct stack *stack = malloc(sizeof(*stack) +
sizeof(*stack->stack)*(b1+1)*(b1+1));
if (!stack)
return res;
stack->first = 0;
stack->last = 0;
board_init(b1+1, b2+1, goal, board);
res = board_bfs(board, stack, start, b1+1, b2+1, goal);
return res;
}
/* See GNUmakefile below for explanation
* https://github.com/braoult/exercism/blob/master/c/templates/GNUmakefile
*/
#ifdef UNIT_TEST
int main(int ac, char **av)
{
int arg=1;
int b1, b2, goal, start=0;
bucket_result_t res;;
for (; arg<ac-2; arg+=3) {
b1 = atoi(av[arg]);
b2 = atoi(av[arg+1]);
goal = atoi(av[arg+2]);
printf("b1=%d, b2=%d, goal=%d, start=%d\n", b1, b2, goal, start);
res = measure(b1, b2, goal, start);
printf(" pos=%d count=%d goal=%d liters=%d\n",
res.possible, res.move_count, res.goal_bucket,
res.other_bucket_liters);
}
}
#endif

27
c/two-bucket/two_bucket.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef TWO_BUCKET_H
#define TWO_BUCKET_H
#include <stdbool.h>
typedef enum {
BUCKET_ID_1,
BUCKET_ID_2
} bucket_id_t;
typedef unsigned int bucket_liters_t;
typedef struct {
bool possible;
int move_count;
bucket_id_t goal_bucket;
bucket_liters_t other_bucket_liters;
} bucket_result_t;
bucket_result_t measure(bucket_liters_t bucket_1_size,
bucket_liters_t bucket_2_size,
bucket_liters_t goal_volume,
bucket_id_t start_bucket);
#include "br-common.h"
#endif /* TWO_BUCKETS_H */