The goal of this homework assignment is to allow you to explore building sets using a dynamic array, a linked list, and a bitset and then utilizing this abstract data type to solve the infamous palindromic permutation programming challenge.
In high-level languages such as Python, we have a built-in implementation of sets which support a variety of methods:
# Define a set
>>> s = set([5, 7, 4])
>>> s
{4, 5, 7}
# Add values to set
>>> s.add(0)
>>> s.add(1)
>>> s
{0, 1, 4, 5, 7}
# Check if set contains value
>>> 4 in s
True
>>> 0 in s
True
>>> 7 in s
True
>>> 9 in s
False
# Remove values from set
>>> s.remove(4)
>>> s.remove(7)
>>> s
{0, 1, 5}
Because C does not have any builtin implementations of a set, you are to implement three different versions:
ArraySet
: This uses a dynamic array and binary search internally to
implement the set.
ListSet
: This uses a linked list, linear search, and recursion
internally to implement the set.
BitSet
: This uses a bitset with bit operations internally to
implement the set.
For this assignment, you are to do your work in the homework05
folder of
your assignments GitHub repository and push your work by noon
Wednesday, September 27.
Before starting this homework assignment, you should first perform a git
pull
to retrieve any changes in your remote GitHub repository:
$ cd path/to/repository # Go to assignments repository
$ git switch master # Make sure we are in master branch
$ git pull --rebase # Get any remote changes not present locally
Next, create a new branch for this assignment:
$ git checkout -b homework05 # Create homework05 branch and check it out
To help you get started, the instructor has provided you with the following skeleton code:
# Go to assignments repository
$ cd path/to/assignments/repository
# -----------------------------------------------------
# MAKE SURE YOU ARE NOT INSIDE THE homework05 DIRECTORY
# -----------------------------------------------------
# MAKE SURE YOU ARE AT THE TOP-LEVEL DIRECTORY
# -----------------------------------------------------
# Download skeleton code tarball
$ curl -LO https://www3.nd.edu/~pbui/teaching/cse.20312.fa23/static/tar/homework05.tar.gz
# Extract skeleton code tarball
$ tar xzvf homework05.tar.gz
Once downloaded and extracted, you should see the following files in your
homework05
directory:
homework05
\_ Makefile # This is the Makefile for building all the project artifacts
\_ bin # This contains the project binary executables and test scripts
\_ benchmark.py # This is the Python script for benchmarking the palindromic program
\_ test_arrayset.sh # This is the shell script for testing the ArraySet library
\_ test_bitset.sh # This is the shell script for testing the BitSet library
\_ test_listset.sh # This is the shell script for testing the ListSet library
\_ test_palindromic.sh # This is the shell script for testing the palindromic program
\_ include # This contains the project header files
\_ ds # This contains the project data structure header files
\_ arrayset.h # This is the C header file for the ArraySet library
\_ bitset.h # This is the C header file for the BitSet library
\_ listset.h # This is the C header file for the ListSet library
\_ lib # This contains the project library files
\_ src # This contains the project source code
\_ arrayset.c # This is the C source code for the ArraySet library
\_ bitset.c # This is the C source code for the BitSet library
\_ listset.c # This is the C source code for the ListSet library
\_ palindromic.c # This is the C source code for the palindromic program
\_ tests # This contains the project unit tests
\_ unit_arrayset.c # This is the unit test for the ArraySet library
\_ unit_bitset.c # This is the unit test for the BitSet library
\_ unit_listset.c # This is the unit test for the ListSet library
Now that the files are extracted into the homework05
folder, you can
commit them to your git repository:
# Go into homework05 folder
$ cd homework05
# Add and commit initial skeleton files
$ git add Makefile # Mark changes for commit
$ git add bin/*.sh bin/*.py # Mark changes for commit
$ git add include/ds/*.h # Mark changes for commit
$ git add lib/.gitkeep # Mark changes for commit
$ git add src/*.c # Mark changes for commit
$ git add tests/*.c tests/*.input # Mark changes for commit
$ git commit -m "Homework 05: Initial Import" # Record changes
The details on what you need to implement are described in the following sections.
Makefile
¶The Makefile contains all the rules or recipes for building the project
artifacts (e.g. palindromic
, libds.a
, unit_arrayset
, unit_bitset
,
unit_listset
, etc.):
CC= gcc
CFLAGS= -Wall -std=gnu99 -g -Iinclude
AR= ar
ARFLAGS= rcs
LD= gcc
LDFLAGS= -Llib
all: bin/palindromic
test:
@$(MAKE) -sk test-all
test-all: test-arrayset test-listset test-bitset test-palindromic
# TODO: Pattern rule for object files
# TODO: Rule for lib/libds.a
# TODO: Rule for bin/palindromic
bin/unit_arrayset: tests/unit_arrayset.o lib/libds.a
$(LD) $(LDFLAGS) -o $@ $^
bin/unit_listset: tests/unit_listset.o lib/libds.a
$(LD) $(LDFLAGS) -o $@ $^
bin/unit_bitset: tests/unit_bitset.o lib/libds.a
$(LD) $(LDFLAGS) -o $@ $^
test-arrayset: bin/unit_arrayset
bin/test_arrayset.sh
test-listset: bin/unit_listset
bin/test_listset.sh
test-bitset: bin/unit_bitset
bin/test_bitset.sh
test-palindromic: bin/palindromic
bin/test_palindromic.sh
clean:
rm -f bin/unit_* bin/palindromic lib/*.a src/*.o tests/*.o
For this task, you will need to add rules for building the static library
lib/libds.a
and the program bin/palindromic
. Be sure to have a recipe
for any intermediate object files that libraries require as shown in the
DAG below:
You must use the CC
, CFLAGS
, LD
, LDFLAGS
, AR
, and ARFLAGS
variables when appropriate in your rules. You should also consider using
automatic variables such as $@
and $<
as well.
Once you have a working Makefile
, you should be able to run the following
commands:
# Build all TARGETS
$ make
gcc -Wall -std=gnu99 -g -Iinclude -c -o src/palindromic.o src/palindromic.c
gcc -Wall -std=gnu99 -g -Iinclude -c -o src/arrayset.o src/arrayset.c
gcc -Wall -std=gnu99 -g -Iinclude -c -o src/listset.o src/listset.c
gcc -Wall -std=gnu99 -g -Iinclude -c -o src/bitset.o src/bitset.c
ar rcs lib/libds.a src/arrayset.o src/listset.o src/bitset.o
gcc -Llib -o bin/palindromic src/palindromic.o lib/libds.a
# Run all tests
$ make test
Testing arrayset...
...
# Remove generated artifacts
$ make clean
rm -f bin/unit_* bin/palindromic lib/*.a src/*.o tests/*.o
Note: The tests will fail if you haven't implemented ArraySet
,
Bitset
, or ListSet
libraries, or the palindromic
program.
You must include the -Wall
flag in your CFLAGS
when you compile. This
also means that your code must compile without any warnings, otherwise
points will be deducted.
After completing the Makefile
and verifying that you can produce all the
specified artifacts, you may begin implementing the [data structure]s and
applications for this assignment.
For the first activity, you are to complete the ArraySet
library, which
implements a set using an ordered dynamic array. That is, to
maintain a unique collection of values, you will store values in the
internal dynamic array in ascending order. This will allow you to
perform binary search to check if a value is already in the ArraySet
.
As can be seen above, the ArraySet
is an ordered dynamic array. This
means that you will need to resize the internal data
array to make
room for new values if the size
reaches the internal capacity
and shift
the values in the internal data
array.
include/ds/arrayset.h
¶The include/ds/arrayset.h
file is the header file for the ArraySet
structure, which contains the following structs and function
prototypes:
/* arrayset.h: Set (Dynamic Array) */
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
/* Constants */
#define ARRAYSET_DEFAULT_CAPACITY (1<<2)
/* Structures */
typedef struct {
int64_t *data; // Internal array
size_t capacity; // Total number of elements
size_t size; // Number of valid elements
} ArraySet;
/* Functions */
ArraySet * arrayset_create();
void arrayset_delete(ArraySet *as);
bool arrayset_contains(ArraySet *as, int64_t value);
void arrayset_add(ArraySet *as, int64_t value);
void arrayset_remove(ArraySet *as, int64_t value);
Other programs will #include
this file in order to use the functions we
will be implementing in this library.
Note: For this task, you do not need to modify this file. Instead, you should review it and ensure you understand the provided code.
src/arrayset.c
¶The src/arrayset.c
file contains the C implementation for the ArraySet
structure. For this task, you will need to implement the following
functions:
ArraySet* arrayset_create()
This function allocates a new
ArraySet
struct and initializes its internal attributes:data
,capacity
, andsize
.
void arrayset_delete(ArraySet *as)
This function deallocates the given
ArraySet
struct along with its internaldata
array.
bool arrayset_contains(ArraySet *as, int64_t value)
This function returns
true
if thevalue
is in theArraySet
, otherwise it returnsfalse
.
value
.
void arrayset_add(ArraySet *as, int64_t value)
This function adds the
value
to theArraySet
while maintaining both uniqueness (ie. two values are the same) and ordering (ie. values are in ascending order).
Hint: Use arrayset_contains
, determine the index
where you want
to place the new value
, and then adapt array_insert
from Lecture 07.
void arrayset_remove(ArraySet *as, int64_t value)
This function removes the
value
from theArraySet
.
Hint: Use arrayset_contains
, determine the index
of the value
you want to remove, and then adapt array_pop
from Homework 03.
As you implement the functions in src/arrayset.c
, you should use
bin/test_arrayset.sh
to test each function:
# Build unit-test
$ make bin/unit_arrayset
gcc -Wall -std=gnu99 -g -Iinclude -c -o tests/unit_arrayset.o tests/unit_arrayset.c
gcc -Llib -o bin/unit_arrayset tests/unit_arrayset.o lib/libds.a
# Run test script manually
$ ./bin/test_arrayset.sh
Testing arrayset...
arrayset_create ... Success
arrayset_delete ... Success
arrayset_contains ... Success
arrayset_add ... Success
arrayset_remove ... Success
Score 2.00 / 2.00
Status Success
Alternatively, you can both build the artifacts and run the test script by doing the following:
# Build and run test scripts
$ make test-arrayset
...
If one of the functions fails, and you need to debug the unit tests, you
can run the bin/unit_arrayset
command directly:
# Display usage message
$ ./bin/unit_arrayset
Usage: ./bin/unit_arrayset NUMBER
Where NUMBER is right of the following:
0 Test arrayset_create
1 Test arrayset_delete
2 Test arrayset_contains
3 Test arrayset_add
4 Test arrayset_remove
# Run test for arrayset_create
$ ./bin/unit_arrayset 0
# Run test for arrayset_create using GDB
$ gdb ./bin/unit_arrayset
...
(gdb) run 0
# Run test for arrayset_create using Valgrind
$ valgrind --leak-check=full ./bin/unit_arrayset 0
Of course, you are free to create your own test programs to debug and test
your ArraySet
library.
You should practice iterative development. That is, rather than writing a bunch of code and then debugging it all at once, you should concentrate on one function at a time and then test that one thing at a time. The provided unit tests allow you to check on the correctness of the individual functions without implementing everything at once. Take advantage of this and build one thing at a time.
For the second activity, you are to complete the ListSet
library, which
implements a set using an ordered linked list. That is, to maintain
a unique collection of values, you will store values in the internal
linked list in ascending order. Unlike with the ArraySet
, you will
perform linear search to check if a value is already in the ListSet
.
As be seen above, the ListSet
struct is an ordered singly linked
list with a dummy node at the front of the sequence. That is, the
first ListSet
struct in the sequence is a dummy and not used to hold
any values. It is there to simplify the listset_add
and listset_remove
functions by ensuring there is always a previous ListSet
struct.
include/ds/listset.h
¶The include/ds/listset.h
file is the header file for the ListSet
structure, which contains the following structs and function
prototypes:
/* listset.h: Set (Linked List) */
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
/* Structures */
typedef struct ListSet ListSet;
struct ListSet {
int64_t data;
ListSet *next;
};
/* Functions */
ListSet * listset_create(int64_t data, ListSet *next);
void listset_delete(ListSet *ls);
size_t listset_size(ListSet *ls);
bool listset_contains(ListSet *ls, int64_t value);
void listset_add(ListSet *ls, int64_t value);
void listset_remove(ListSet *ls, int64_t value);
Note: For this task, you do not need to modify this file. Instead, you should review it and ensure you understand the provided code.
src/listset.c
¶The src/listset.c
file contains the C implementation for the ListSet
structure. For this task, you will need to implement the following functions:
ListSet* listset_create(int64_t data, ListSet *next)
This function allocates a new
ListSet
struct and initializes its internal attributes:data
andnext
.
void listset_delete(ListSet *ls)
This function recursively deallocates the given
ListSet
struct along with the rest of the sequence.
size_t listset_size(ListSet *ls)
This function recursively determines the number of values in the
ListSet
struct.
Hint: Use the internal listset_size_r
on the first valid ListSet
struct to recursively traverse through the sequence. The
listset_size_r
function should consider its base case and recursive
step.
bool listset_contains(ListSet *ls, int64_t value)
This function returns
true
if thevalue
is in theListSet
, otherwise it returnsfalse
.
Hint: Use linear search in the internal listset_contain_r
function to recursively locate the value
. The
listset_contains_r
function should consider its three base cases and
its recursive step.
void listset_add(ListSet *ls, int64_t value)
This function adds the
value
to theListSet
while maintaining both uniqueness (ie. two values are the same) and ordering (ie. values are in ascending order).
Hint: Use linear search in the internal listset_add_r
function to
recursively locate where to insert a new ListSet
struct with the
specified value
. The listset_add_r
function should consider its
three base cases and its recursive step.
void listset_remove(ListSet *ls, int64_t value)
This function removes the
value
from theListSet
.
Hint: Use linear search in the internal listset_remove_r
function
to recursively locate the ListSet
struct with the specified
value
. The listset_remove_r
function should consider its two base
cases and its recursive step.
Note: All the functions in the ListSet
, besides list_create
must be
recursive functions. For your convenience, we have provided a few
helper recursive functions such as listset_size_r
,
listset_contains_r
, listset_add_r
, listset_remove_r
. These are meant
to be called from their non-recursive functions on the first valid
ListSet
struct:
size_t listset_size(ListSet *ls) {
return listset_size_r(ls->next);
}
As you implement the functions in src/listset.c
, you should use
bin/unit_listset.sh
to test each function:
# Build unit-test
$ make bin/unit_listset
gcc -Wall -std=gnu99 -g -Iinclude -c -o tests/unit_listset.o tests/unit_listset.c
gcc -Llib -o bin/unit_listset tests/unit_listset.o lib/libds.a
# Run test script manually
$ ./bin/test_listset.sh
Testing listset...
listset_create ... Success
listset_delete ... Success
listset_size ... Success
listset_contains ... Success
listset_add ... Success
listset_remove ... Success
Score 4.00 / 4.00
Status Success
Alternatively, you can both build the artifacts and run the test script by doing the following:
# Build and run test scripts
$ make test
...
If one of the functions fails, and you need to debug the unit tests, you
can run the bin/unit_listset
command directly:
# Display usage message
$ ./bin/unit_listset
Usage: ./bin/unit_listset NUMBER
Where NUMBER is right of the following:
0 Test listset_create
1 Test listset_delete
2 Test listset_size
3 Test listset_contains
4 Test listset_add
5 Test listset_remove
# Run test for listset_create
$ ./bin/unit_listset 0
# Run test for listset_create using GDB
$ gdb ./bin/unit_listset
...
(gdb) run 0
# Run test for listset_create using Valgrind
$ valgrind --leak-check=full ./bin/unit_listset 0
Of course, you are free to create your own test programs to debug and test
your ListSet
library.
For the third activity, you are to complete the BitSet
library, which
implements a set using a bitset. That is, to maintain a unique
collection of values, you will mark individual bits in an unsigned long
integer (i.e. uint64_t
) that correspond to the values you are tracking.
include/ds/bitset.h
¶The include/ds/bitst.h
file is the header file for the BitSet
library,
which contains the following function prototypes:
/* bitset.h: Set (BitSet) */
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
/* Type Definitions */
typedef uint64_t BitSet;
/* Functions */
bool bitset_contains(BitSet *bs, int64_t value);
void bitset_add(BitSet *bs, int64_t value);
void bitset_remove(BitSet *bs, int64_t value);
size_t bitset_size(BitSet *bs);
Note: For this task, you do not need to modify this file. Instead, you should review it and ensure you understand the provided code.
src/bitset.c
¶The src/bitset.c
file contains the C implementation for the BitSet
library. For this task, you will need to implement the following functions:
bool bitset_contains(BitSet *bs, int64_t value)
This function returns
true
if thevalue
is in theBitSet
, otherwise it returnsfalse
.
void bitset_add(BitSet *bs, int64_t value)
This function adds the
value
to theBitSet
.
void bitset_remove(BitSet *bs, int64_t value)
This function removes the
value
from theListSet
.
size_t bitset_size(BitSet *bs)
This function returns the number of values in the
BitSet
.
Hint: Use bitset_contains
on each possible value.
As you implement the functions in src/bitset.c
, you should use
bin/unit_bitset.sh
to test each function:
# Build unit-test
$ make bin/unit_bitset
gcc -Wall -std=gnu99 -g -Iinclude -c -o tests/unit_bitset.o tests/unit_bitset.c
gcc -Llib -o bin/unit_bitset tests/unit_bitset.o lib/libds.a
# Run test script manually
$ ./bin/test_bitset.sh
Testing bitset...
bitset_contains ... Success
bitset_add ... Success
bitset_remove ... Success
bitset_size ... Success
Score 1.00 / 1.00
Status Success
Alternatively, you can both build the artifacts and run the test script by doing the following:
# Build and run test scripts
$ make test
...
If one of the functions fails, and you need to debug the unit tests, you
can run the bin/unit_bitset
command directly:
# Display usage message
$ ./bin/unit_bitset
Usage: ./bin/unit_bitset NUMBER
Where NUMBER is right of the following:
0 Test bitset_contains
1 Test bitset_add
2 Test bitset_remove
3 Test bitset_size
# Run test for bitset_contains
$ ./bin/unit_bitset 0
# Run test for bitset_contains using GDB
$ gdb ./bin/unit_bitset
...
(gdb) run 0
# Run test for bitset_contains using Valgrind
$ valgrind --leak-check=full ./bin/unit_bitset 0
Of course, you are free to create your own test programs to debug and test
your BitSet
library.
For this activity, you are use your three set implementations to solve the following programming challenge:
Given a word, you are to determine if any permutation of the word is a palindrome, which is a phrase that reads the same forwards and backwards (ignoring case). For instance, "tacocat" is an example of a palindrome.
To do this, you will complete the bin/palindromic
program, which reads in
one word at a time from stdin
and prints YEAH
if it is a palindromic
permutation, otherwise it prints out NOPE
:
# Check if words are palindromic permutations using ArraySet
$ ./bin/palindromic -a
tacocat
YEAH
burrito
NOPE
ttaacco
YEAH
# Check if words are palindromic permutations using ListSet
$ ./bin/palindromic -l
tacocat
YEAH
burrito
NOPE
ttaacco
YEAH
# Check if words are palindromic permutations using BitSet
$ ./bin/palindromic -b
tacocat
YEAH
burrito
NOPE
ttaacco
YEAH
In addition to meeting the functional requirements of the assignment (as described above), your program must not exhibit any memory leaks or invalid memory accesses as would be detected by Valgrind.
Be sure to free any memory that has been allocated on the heap and to initialize any allocate memory appropriately.
src/palindromic.c
¶The src/palindromic.c
file contains the C implementation of the
bin/palindromic
program described above. You will need to implement the
following functions:
bool is_palindromic_arrayset(const char *s)
This function returns
true
if the strings
is a palindromic permutation, otherwise it returnsfalse
.
bool is_palindromic_listset(const char *s)
This function returns
true
if the strings
is a palindromic permutation, otherwise it returnsfalse
.
bool is_palindromic_listset(const char *s)
This function returns
true
if the strings
is a palindromic permutation, otherwise it returnsfalse
.
Hint: For each of the three functions above, use the corresponding set
implementation: ArraySet
, ListSet
, BitSet
.
The functions should be largely the same except for changing the underlying set implementation.
To solve the problem, consider when you would want to add to the set as you process each letter and what you would want to remove. Moreover, consider what the final size of the set should be when you finish processing all the letters.
To ignore case, consider using tolower.
Note: The main
function will parse the command line options, and then
read each line of stdin
, process it with one of the is_palindromic
functions above, and finally output the result.
This function is provided to you and you do not need to modify it.
As you implement src/palindromic.c
, you can test it by running the
test-palindromic
target:
# Build artifact and run test
$ make test-palindromic
bin/test_palindromic.sh
Testing palindromic ...
ArraySet (output) ... Success
ArraySet (valgrind) ... Success
ListSet (output) ... Success
ListSet (valgrind) ... Success
BitSet (output) ... Success
BitSet (valgrind) ... Success
Score 3.00 / 3.00
Status Success
Once you have completed all the activities above, you are to complete the following reflection quiz:
As with Reading 01, you will need to store your answers in a
homework05/answers.json
file. You can use the form above to generate the
contents of this file, or you can write the JSON by hand.
To check your quiz directly, you can use the check.py
script:
$ ../.scripts/check.py
Checking homework05 quiz ...
Q1 0.30
Q2 0.30
Q3 0.30
Q4 0.30
Q5 0.30
Q6 0.20
Q7 0.30
Score 2.00 / 2.00
Status Success
For extra credit, you are to solve the following LeetCode problem in C.
To receive credit, you must pass on LeetCode and achieve an Accepted submission.
To get credit for this Leet Point, show your solution and the LeetCode acceptance page to a TA to verify (or attached a screenshot with both to your Pull Request). You have up until two days after this assignment is due to verify your Leet Point.
Remember that you can always forgo this Leet Point for two extra days to do the homework. That is, if you need an extension, you can simply skip the Leet Point and you will automatically have until Friday to complete the assignment for full credit.
Just leave a note on your Pull Request of your intentions.
To submit your assignment, please commit your work to the homework05
folder
of your homework05
branch in your assignments GitHub repository:
#-----------------------------------------------------------------------
# Make sure you have already completed Activity 0: Preparation
#-----------------------------------------------------------------------
...
$ git add Makefile # Mark changes for commit
$ git commit -m "Homework 05: Activity 0" # Record changes
...
$ git add src/arrayset.c # Mark changes for commit
$ git commit -m "Homework 05: Activity 1" # Record changes
...
$ git add src/listset.c # Mark changes for commit
$ git commit -m "Homework 05: Activity 2" # Record changes
...
$ git add src/bitset.c # Mark changes for commit
$ git commit -m "Homework 05: Activity 3" # Record changes
...
$ git add src/palindromic.c # Mark changes for commit
$ git commit -m "Homework 05: Activity 4" # Record changes
...
$ git add answers.json # Mark changes for commit
$ git commit -m "Homework 05: Activity 5" # Record changes
...
$ git push -u origin homework05 # Push branch to GitHub
Remember to create a Pull Request and assign the appropriate TA from the Reading 05 TA List.
DO NOT MERGE your own Pull Request. The TAs use open Pull Requests to keep track of which assignments to grade. Closing them yourself will cause a delay in grading and confuse the TAs.