CMSC 143 Notes
Integer Data Types
- Different ones available as to use as little memory as possible
Name of type | Range (inclusive) | Notes | |
---|---|---|---|
char | -128 → 127 | %d (if number); %c (if you want to print corresponding character) | could represent a character I write on keyboard so that’s why it called char; even number of possible numbers |
unsigned char | 0 → 255 | %u | not used for characters |
short (int) | -32768 → 32,767 | %hd (half decimal) | |
unsigned short(int) | 0 → 65,535 | %hu | |
int | ~-2.147B → ~2.147B | %d | |
unsigned (int) | 0 → ~4.3B | %u | |
long long (int) | ~-9Q → ~9Q | %lld | |
unsigned long long (int) | 0 → ~18Q | %llu |
Float Data Types
float → %f to print float
double (double precision) → more accurate than float, uses twice as much memory (typically used) → %lf → to print double
Operators in C
+, -, *, %
Division
/ → real number result if at least one number is a real number; integer result if both numbers are integers
- 5.0/2.0 → 2.5
- 5.0/2 → 2.5
- 5/2.0 → 2.5
- 5/2 → 2
double foo() {
return 5/2;
}
//this returns 2 and then will be converted into 2.0 since it returns a double
NO built-in ************exponentiation operator
Comparison
== *= /= -= ==
! → not
< so on
Boolean operators
&&
||
! → not
Example: Leap Year
Leap year if divisible by 400 or (4 and not 100)
//function if it's a leap year
//int, there's no bool type in C; 0 is false, anything else is true
int is_leap_year(unsigned short year) {
return ((year % 400) == 0 || ((year % 4 == 0) && !(year % 100 == 0)));
//will return a 0 or a 1 - boolean expressions are all seen as 0s or 1s (not 0s)
}
is_leap_year(2024)
//don't say == 1 or == 0; although it returns an integer, C understands that
//0 means False and !0 means True in this instance
Example: Distance
#include <math.h>
double distance(double x1, double y1, double x2, double y2) {
double x_distance = x1 - x2;
double y_distance = y1 - y2;
return sqrt((x_distance)*(x_distance)+(y_distance)*(y_distance));
}
Creating and Initializing Variables
void foo() {
int x = 7;
double y;
//what's y = to at this point? memory has been set aside to hold y as an integer
printf("%d", y);
//this prints whatever is present in memory of y. prints whatever was stored
//in that spot before
y = 2.0;
x = 4;
double a = 2, b;
b = 3;
int c = 4;
c = 5.5;
//this is possible, but the c will be truncated - c = 5
Booleans (Switch)
unsigned char days_in_month(unsigned char m, unsigned short y) {
switch (m) {
case 9:
case 4:
case 6:
case 11
return 30;
case 2:
if is_leap_year(y):
return 29;
return 28;
default:
return 31;
}
}
void print days_in_month(unsigned char m, unsigned short y) {
switch (m) {
case 9:
case 4:
case 6:
case 11
printf("30");
case 2:
if is_leap_year(y) {
printf("29");
} else {
return printf("28");
}
default:
printf("31");
}
}
//if the month was 9 year was 2023 -> prints 302831
//case just brings you to where you need to be
void print days_in_month(unsigned char m, unsigned short y) {
switch (m) {
case 9:
case 4:
case 6:
case 11
printf("30");
break;
case 2:
if is_leap_year(y) {
printf("29");
break;
} else {
return printf("28");
break;
}
default:
printf("31");
}
}
//break takes you out of the switch to make this work
Loops
unsigned long long int factorial(unsigned int x) {
unsigned long long int res = 1;
while (x > 1) {
res *= x--;
//x-- uses value of x and then decrements it (postfix)
//--x decrements x and then usese value (prefix)
}
return res;
}
Infinite loop?
- hit control c to stop
- even if it goes past range of an integer, it will keep going (will wrap around)
Infinite recursive loop?
- Segmentation fault → and then stop
- Program tried to use resources beyond what it’s able to use (recursive calls take up memory)
- division by 0 will also cause this
Bottom while loop
do {
//code
///checks condition at end
} while (cmd != 'Q');
For loop
unsigned unsigned long int factorial(unsigned int x) {
unsigned long long int res = 1;
unsigned int i;
for (i = x; i > 1; i--) {
res *+ i;
}
//initializer, determination condition, update condition
return res;
}
10/9
Pointers → type of data that stores location of something else in memory (think hippocampus and frontal lobe — hippocampus has index of locations of memories in frontal lobe
- every byte of memory in your computer has a different address
void foo() {
int i = 7;
// this creates a slot of memory called i and it stores 7
int* p = &i;
//creating pointer out of p by using * operator -- & finds the location of a variable
//if i is at 1234, p now stores 1234
int j = *p;
//the * operator takes a pointer and provides what it's pointing to
//j now holds 7
//this is called dereferencing -> going from reference to actual value
If you were to look to try to access a spot of memory that you aren’t allowed to → SegmentationFault
int a = 7, b = 7, c = 13;
int *ap = &a, *bp = &b, *bc = &c;
int* p, a;
//star does not distribute, only looking at p in this example
a == b; -> True
a == c; -> False
ap == bp; -> False
*ap == *bp; -> True
A swapping function (not pointers and thus not correct)
void swap(int i, int j) {
int temp = i;
i = j;
j = temp;
}
void foo() {
int a = 7, b = 13;
swap(a, b);
}
//this doesn't work!
//C is pass by value, not pass by reference
A swapping function (with pointers and thus correct!)
void swap(int* i, int* j) {
int temp = *i;
*i = *j;
*j = temp;
}
void foo() {
int a = 7, b = 13;
swap(&a, &b);
}
10/11
Vector addition
//there isn't an innate way to bundle values like a tuple in C so we use locations
void vector_add(double x1, double y1, double x2, double y2,
double* xr, double* yr) {
//xr and yr denote where to place results
//deference xr and yr
*xr = x1 + x2;
*yr = y1 + y2;
}
void foo() {
double x, y;
vector_add(10, 20, 1, 2, &x, &y);
printf("%lf %lf\\n", x, y);
}
Arrays
All items in array must be same type
Storage in memory:
- Ordered in memory (sequential)
- contiguous (no gaps)
- Lengths are fixed length are you take up a certain amount of space when you create a list
- Their locations aren’t sequentially as each element will take up multiple bytes of space
- But since the same data type takes the same amount of place — you can calculate where the nth element is (e.g., double take 8 bytes, so if your list starts at 100 and your looking for the nth element, you can find it by looking at 100 + n * 8)
- Arrays are kept track by having a pointer at the beginning of the array
void foo() {
//array of doubles called a of length 4
//curly braces to initialize those elements
double a[4] = {10.5, -2.4, 86, 12.5}
//can't do this after initializing the elements
a = {1, 2, 3, 4}
//contains unknown values at those two places
double b[2];
//I can also do without saying the number of elements since C can count the number
double a[] = {1, 2, 3};
a[2] = 13;
//C cannot check bounds - it doesn't track length of array. This will work
//unless you get a Seg fault (you can't access that memory)
//C cannot check length
a[10000] = 2;
}
You can’t do the following on an array:
- slicing
- negative indexing
double sum(double* ds, unsigned int dslen) {
double res = 0;
unsigned int i;
for(i = 0; i < dslen; i++) {
res += ds[i];
}
return res;
}
#remove that do while loop, it does nothing different than a while loop
An alternative methods to above
double sum(double ds[], unsigned int dslen) {
}
Squaring everything in an array
void squares_change(double* ds, unsigned int dslen) {
unsigned int i;
for(i = 0; i < dslen; i++) {
ds[i] = ds[i] * ds[i];
}
}
Creating a copy of a list
Squaring in a copy of an array
double* squares_copy(double* ds, unsigned int dslen) {
/* can't use a brackets version instead */
double sqr_ds[dslen];
//this doesn't work bc C needs to know exact number for list length
//also, sqr_ds is a local variable and will disappear after this function
//so a copy of the pointer is returned but the list isn't there (dangling pointer)
return sqr_ds;
return *sqr_ds;
//gets first element of the array but doesn't return the whole array
//in short, arrays ARE NOT objects in C and they shouldn't be treated as such
}
//don't use the stack!
Stack → holds function data (local variables)
Heap → tell the system explicitly when to add and remove
//The proper way
double * squares_copy(double* ds, unsigned int dslen) {
double* res = (double*)malloc(sizeof(double)* dslen /*calculating bytes*/);
//malloc -> memory allocation; takes in parameter as bytes of memory you need
// use sizeof(double) for readability - 8 would be weird
//malloc returns a generic pointer *void so we cast it to double*
if (res == NULL) {
//this checks if the pointer actually goes to a real value instead of null
fprintf(stderr, "squares_copy unable to allocate result array\\n");
exit(1);
/*print out a helpful error message -> fprintf; printf has a slight delay and may not get printed
* f stands for file -> says
* where to send stuff, stderr will ensure it won't get batched up with program
* and will print out; returns 1 for main function (to indicate error) */
}
for(int i = 0; i < dslen; i++) {
res[i] = ds[i] * ds[i];
}
return res;
}
void foo() {
double a[] = {1, 2, 3, 4};
double* asq = squares_copy(a, 4);
unsigned int i;
for(i = 0; i < 4; i++) {
printf("%lf", asq[i]);
}
printf("\\n");
free(asq);
/*this will say that asq can now be used as memory
* every time you call malloc, you should call free once you don't need what
* was allocated; don't free it before you're done using it but do it at earliest
* opportunity! */
}
Test(squares_copy) {
double nums = {1, 2, 3, 4}
double * nums_sq = squares_copy(nums, 4);
cr_assert(nums_sq[0] == 1);
/* and so on and so forth */
free(nums_sq);
}
When the program is quit, everything is recycled. But, programs like web browsers are sometimes rarely quit so free is really, really important.
Creating a new list (of a different size) from another list
Creating an array with all the even numbers from one list (problem: how long to make array)
int* evens(int nums[], unsigned int numslen, unsigned int* rslen) {
unsigned int nevens = 0;
//Step 1: determine length of rex
for(unsigned int i = 0; i < numslen; i++) {
if (nums[i] % 2 == 0) {
nevens++;
}
//Step 2: create memory for res
int* res = (int*)malloc(sizeof(int)*nevens);
if (res == NULL) {
//this checks if the pointer actually goes to a real value instead of null
fprintf(stderr, "evens unable to allocate result array\\n");
exit(1);
}
//Step 3: fill res
unsigned int res_index = 0;
for(i = 0; i < nevens; i++) {
if (nums[i] % 2 == 0) {
res[res_index++] = nums[i];
}
}
/*change value at the location for length of res */
*reslen = nevens;
return res /*this returns a pointer to res as it's an array */;
}
void foo() {
int ns[] = {1, 2, 3, 4};
unsigned int nevens;
int* evs = evens(ns, 4, &nevens);
unsigned int i = 0;
for(i = 0; i < nevens; i++) {
printf("%d", evs[i]);
}
free(evs);
}
int x[] = {1, 2, 3, 4};
int* x = {
Alternative Ways to Index
double nums;
*nums = nums[0]; //This is true *nums gives you value at beginning of nums
//pointer arithmetic
*(nums + 2) = nums[2];
October 18, 2023
Strings
Lists of characters
Mark end of string with “\0” = NULL terminator → so, a string like “Hi” needs to be allocated with at least 3 elements because you need the null terminator (sometimes it’s longer than where the terminator is)
strlen is built into C, but here’s how you could write it
unsigned int strlen(char* s) {
/* Length of string does not include null terminating character
* as not part of the string */
unsigned int i = 0;
while (s[i] != '\\0' /* can also write != 0 as the ASKE key is 0 for 0\\ or simply s[i]*/ ) {
i++;
}
return i;
}
//another option
unsigned int strlen(char* s) {
//this will copy the pointer to t (we would need a star to deference s)
char* t = s;
//looks at *t and then increments t
while(*t++) {}
//t is incremented one two far since it adds another value after it sees '\\0'
return t - s - 1;
}
Emphasize — creates new string leaving old string intact, but new string has exclamation points instead of periods
char* emphasize(char* s) {
char* new = (char*)malloc(sizeof(char) * strlen(s) + 1);
unsigned int i = 0;
while(s[i]) {
if s[i] == '.' {
t[i] = '!';
}
else {
t[i] = s[i];
}
i++
}
t[i] = '\\0'
return t;
}
Copying strings
char* strdup(char* s) {
char* t = (char*)malloc(sizeof(char) * strlen(s) + 1);
unsigned int i = 0;
while(s[i]) {
t[i] = s[i];
i++
}
t[i] = '\\0'
return t;
}
/*strcopy takes in two strings and returns nothing (you have to give it two
*strings of same length */
void strcopy(char* dst, char* src) {
unsigned int i = 0;
while(src[i]) {
dst[i] = src[i];
i++;
}
dst[i] = '\\0';
}
/* the hella efficient string copy (yeah it's pretty cool)*/
void strcopy(char* dst, char* src) {
//this retrieves what was just assigned
while (*dst++ = *src++) {
}
}
++ or *++
int* x[] = {1, 2, 3, 4};
x++; //=> moves element *x looks at
(*x)++; //=> edits element at index x
*x++; //returns element at *x and then increments x
String functions (from string.h)
strlen
strdup
strcpy
strcmp
strcmp
int strcmp(a, b) {
/* a - b; if a (later alphabetical order) is bigger than b -> positive number;
* if they're the same -> 0 (so equal strings return false;
* if b is bigger -> negative number; */
}
Structures
Returning to Vector Add
/* the structure (template) of the point
* THIS IS IN THE HEADER FILE */
struct point {
//these are the fields!
double x;
double y;
}; //semicolon to end this
void foo() {
struct point p = {10, 20};
p.x = 5;
}
Distance Between Two Points!
#include <math.h>
double distance(struct point p1, struct point p2) {
double dx = p1.x - p2.x;
double dy = p1.y - p2.y;
return (sqrt(dx * dx + dy * dy));
}
void foo() {
struct point p = {10, 20};
p.x = 5;
struct point q = {0, -5);
double d = distance(p, q);
}
Midpoint Between Two Points!
struct point midpoint(struct point p1, struct point2) {
struct point res = {(p1.x + p2.x)/2, (p2.y + p2.y)/2};
return res;
}
void foo() {
struct point p = {10, 20};
p.x = 5;
struct point q = {0, -5);
struct point r = midpoint(p, q);
}
Why can we return a struct (which appears to be a local variable) and have it appear in the heap?
- structs are copied when used as parameters and when returned!
What if a structure has a list as a field?
- either hard-coded with double quotes or use malloc
OR
struct point {
double x, y;
char name[60];
};
/* I made room for 60 characters in memory, and this can be used} */
int somefunction() {
struct point res;
res.x = 2;
res.y = 2;
res.name[0] = 'm';
res.name[1] = 'o';
return res;
}
Pointers to Structs
These are good to use when using a relatively large struct so we don’t have to copy it all the time — structures as both parameters and return values send copies!
void bar() {
struct point* p = (struct point)malloc(sizeof(struct point));
//check if malloc returns null
//dereference pointer
(*p).x = 12;
//or
p -> y = -5;
10/23
Helpers for structs
struct point* point_new(double x, double y) {
struct point* res = (struct point*)malloc(sizeof(struct point));
if(res == NULL) {
fprintf(stderr, "point_new unable to allocate \\n");
}
res -> x = x;
res -> y = y;
return res;
}
Free stuff
void point_free(struct point* p) {
free(p);
}
void point_show(struct point* p) {
printf("(%lf, %lf)", p->x, p->y);
}
void foo() {
struct point p, q;
//p will be a new copy of a q - point to separate structs
p = q;
struct point *p, *q;
//p will point to same place as q
p = q;
}
struct point* point_copy(struct point* p) {
return point_new(point->x, point->y);
}
Structures within structures
struct circle {
struct point center;
double radius;
};
void foo() {
struct circle c;
c.center.x = 10;
c.center.y = 5;
c.radius = 3;
//let's try as a pointer
struct circle* c = (struct circle*)malloc(sizeof(struct circle));
//still . for x as center is not a pointer to a struct, it's a struct
c->center.x = 10;
c->center.y = 10;
c->radius = 3;
}
//**************************************************************************************************************But what if we point to the center instead?**************************************************************************************************************
struct circle {
struct point* center;
double radius;
}
void foo() {
//if I were to create a pointer again, I also need to create space for the center!
struct circle* c = (struct circle*)malloc(sizeof(struct circle));
c.center* = (struct point*)malloc(sizeof(point));
c->center->x = 10;
//If I were to use a star to access x
//arrow before asterisk
*c->center.x = 10;
}
void foo() {
//let's make c not a pointer but instead a struct
struct circle c;
c.center* = (struct circle*)malloc(sizeof(struct circle));
c.center->x = 10;
c.center->y = 10;
c.radius = 3;
}
Arrays of Structures
struct point ps[5];
p[0].x = 12;
struct point p = ps[0];
//creatig structures by giving them lists of field values within list
struct point ps2[] = {{1, 2}, {3, 4}};
//list of 10 struct points
struct point* ps3 = (struct point*)malloc(sizeof(struct point) * 10);
ps3[6].x = -4;
Arrays of Pointers to Structs
struct point* qs[2];
//point new returns pointer
qs[0] = point_new(//stuff);
qs[1] = point_new(//stuff);
qs[1]->x = 10;
//for a list of pointers to points with a length of 10
struct point** qs3 = (struct point**)malloc(sizeof(struct point*) * 10);
qs3[0] = point_new(/stuff);
qs3[3]->x = 7;
October 25, 2023
struct square {
struct point top_left;
double side_length;
};
struct rectangle {
struct point top_left;
double width;
double height;
};
Can we make a shape struct that can handle all these different shapes?
Unions of Structs
union shape { //only stores one of these structs at a time (alots space for biggest one, so in this case the rectangle)
//this is OR (only wants one)
struct circle c;
struct square s;
struct rectangle r;
};
struct tagged_shape {
//this is AND (wants both)
union shape u;
enum shape_tag tag; //enum shape_tag will handle values corresponding to struct
};
enum shape_tag { //enum stands for enumerated type
CIRCLE, SQUARE, RECTANGLE
};
memory storage for shape
x x x | y y y |
---|---|
r s w | h |
//let's create a rectangle
struct tagged_shape shp;
shp.u.r.top_left.x = 10;
shp.u.r.top_left.y = 20;
shp.u.r.width = 5;
shp.u.r.height = 3;
shp.tag = RECTANGLE;
//beneath is allowed even though it's improper, and r will return the width of the rectangle
double r = shape.u.c.radius;
double shape_area(struct tagged_shape s) {
switch(s.tag) {
case CIRCLE:
return M_PI * s.u.c.radius * s.u.c.radius;
case SQUARE:
return s.u.s.side * s.u.s.side;
case RECTANGLE:
return s.u.r.width * s.u.r.height;
//a lack of return would still have a place that stores the return statement but it wouldn't have been altered so we use x
default:
fprintf(stderr, "...");
exit(1);
}
}
//efficient method but still not amazing
struct rectangle r = {{10, 20}, 5, 3};
union shape s;
s.r = r;
struct tagged_shape shp = {r, RECTANGLE};
Linked Lists
Each element has a value and a pointer to the next element
To get an element deep within the list, you need to go though each and every element — last element of list has a NULL pointer
//this doesn't work bc of circular dependency
struct intlist {
int val;
struct intlist* next;
}
typedef
//giving unsigned long long int a one word type name; they are the same thing
typedef unsigned long long int ullint;
typedef struct point point;
Using typedef for linked lists
typdef struct intlist intlist;
//it's still circular reasoning, but the compiler no longer complains
struct intlist {
int val;
intlist* next;
}
intlist* prepend(int val, intlist* next) {
intlist* res = (intlist*)malloc(sizeof(intlist));
res -> val = val;
res -> next = next;
return res;
}
intlist* l = prepend(7, prepend(13, prepend(26, NULL)));
//adding 0 to the beginning
l = prepend(0, l);
Writing functions on linked lists
int sum(intlist* l) {
if (l == NULL) {
return 0;
}
return l->val + sum(l->next);
}
void squares_change(intlist* l) {
if(l == NULL) {
return;
}
l->val = l->val * l->val;
squares_change(l->next);
}
intlist* squares_copy(intlist* l) {
if(l == NULL) {
return NULL;
}
New list of evens of old list
intlist* evens(intlist* l) {
if(l == NULL) {
return NULL;
}
if(l->val % 2 == 0) {
return prepend(l-> val, evens(l->next));
}
return evens(l->next);
}
How to free intlists (have to free all the structs!)
//we should have one call to free for every call of malloc
void free_list(intlist* l) {
if (l == NULL) {
return;
}
free(l->next);
free(l);
}
Linked List (Iterative Versions)
Summing a linked list
//benefit here is that we don't need another stack frame for each recursive call (
int sum(intlist* l) {
int res = 0;
while(l != NULL) {
res += l->val;
l = l-> next;
}
return res;
}
Squaring a linked list
void squares_change(intlist* l) {
//NULL is false
while(l) {
l->value = l->value * l->value;
l = l->next;
}
}
Freeing the list
void free_list(intlist *l) {
while(l) {
intlist* temp = l->next;
free(l);
l = temp;
}
}
Creating a new list from an old list
intlist* squares_copy(intlist* l) {
intlist *head, *tail;
if (l == NULL) {
return NULL;
}
head = prepend(l->val * l-> val, NULL);
tail = head;
l = l-> next;
while(l) {
tail->next = prepend(l->val*l->val);
l = l->next;
tail = tail->next;
}
return head;
}
intlist* evens(intlist *l) {
intlist *head = NULL, *tail;
while(l) {
if(l->val % 2 == 0) {
if(*head == NULL) {
head = prepend(l->val, NULL);
tail = head;
}
else {
tail->next = prepend(l->val, NULL);
tail = tail->next;
}
}
l = l->next;
}
return head;
}
Multidimensional Data
2d array
int m[3][5]; //this is actually a 1d array with 15 elements
m[2][1] = 124;
//how does the compiler store this? takes first row and then glues second row on right after
//so m[2] doesn't work because it's not a grid
m[2][1] -> m[2 * 5 + 1]
//C makes a 1d array but does the math for us for our conceptual 1d array
Arrays of arrays
//array of ints -> int*
//array of arrays of ints -> int**
//lets create a 3 x 5 array
int** m = (int**)malloc(sizeof(int*) * 3);
//now, we need to create memory for the arrays within the array
for(i = 0; i < 3; i++) {
m[i] = (int*)malloc(sizeof(int) * 5);
}
//does not just do the index multiplication and addition; this goes to the array at the second index and then the first index of that array
m[2][1] = 12;
//the C compiler knows how to do the indexing based on how you create the arrays
Calling functions with arrays of arrays as parameters
//1d arrays
void foo(int a[],
//is eqivalent to
void foo(int* a,
//2d arrays
//THESE ARE NOT EQUIVALENT AS THEY INDEX DIFFERENTLY; ONE USES POINTERS FOR INDEXING AND THE OTHER USES 1D INDEXING
void foo(int a[][]
void foo (int a**
// void foo has to commit to the number of columns if [][] is used, so...
void foo(int a [][5], unsigned int nrows) {
}
//you have to hardcode the columns; you have the option to hardcode the rows
void foo(int a**, unsigned int nrows, unsigned int ncols) {
}
Stacks
- Last one in is first one out
- First one in is last one out
Linked lists are great for this since they aren’t fixed length
What if we made the top of the stack the end of linked list?
- Appending is costly since you need to iterate through entire list to append
- Removing is costly since you need to do the same
- However, if we keep a pointer to the tail of the list, this is actually really easy
- But what about popping from the list?
- We have a pointer to the last element, BUT the element before has to have it’s pointer to be NULL → we have to iterate through the whole list
- Well, we could have a pointer to tail - 1, but then we would have to update tail -1, which means we have to iterate through the whole list
- We have a pointer to the last element, BUT the element before has to have it’s pointer to be NULL → we have to iterate through the whole list
- But what about popping from the list?
What if we made the top of the stack the beginning of the list?
Appending? Easy! — old start is pointed to by new element and first pointer goes to new element
Removing? Easy! — old start is removed and we have pointer to next element from the old start
Queues
What if we made the front of the line the beginning of the list?
Appending (adding to queue)
- Loop to end of the list to adjust C’s pointer
- But, let’s add a tail pointer → this is really easy
Dequeueing
- Same as removing from a stack when the top of the stack is the beginning of the list
What if we made the back of the line the beginning of the list?
Appending
- Easy → just add an element to the beginning
Dequeuing
- Lord, this sucks. Same as the tail -1 full iteration as a stack with the top of the stack at the end of the linked list
Trees
With linked lists!
Add pointers to intlists (multiple pointers allows for children)
typedef struct intree intree;
struct inttree {
int val;
inttree *left, *right;
};
Summing trees
int treesum(intree* t) {
if(!t) {
return 0;
}
return t->val + treesum(t->left) + treesum(t->right);
}
Factoring out return statement
int treesum(intree* t) {
return (t)?(t->val + treesum(t->left) + treesum(t->right): 0;
}
Representing Numbers
Binary
143 to binary
143 % 2 → 1
143 / 2 → 71
71 % 2 → 1
71 / 2 → 30
35 % 2 = 1
35 / 2 = 17
17 % 2 = 1
17 / 2 → 8
8 % 2 = 0
8 / 2 = 4
4 % 2 = 0
4/ 2 = 2
2 % 2 = 0
2/ 2 = 1
1 % 2 = 1
1 / 2 = 0
10001111
So, 143 → 1 + 2 + 4 + 8 + 128
Octal
143 % 8 → 7
143 / 8 → 17
17 % 8 → 1
17 / 8 → 2
2 % 8 → 2
2 / 8 = 0
217
3 bits that correspond to 2 → 010
3 bits that correspond to 1 → 001
3 bits that correspond to 7 → 111
10001111 (octal is nice because we can directly convert it from octal to binary since we need 3 bits of binary for every bit of octal and we have direct translation)
- we can also convert binary to octal (just take 3 bits at a time)
This works for base 4, 8, 16, 32 (whatever maxes out the bits)
Base 16 = hexadecimal
After going to 9, we use a for 10, b for 11, c for 12, d for 13, e for 14, f for 15
- because if I see 11, is that 2 separate 1s or 11
Hexadecimal
143 % 16 → 15 (f)
143 / 16 = 8
8 % 16 → 8
8 / 16 = 0
8f
To binary:
f → 1111
8 → 1000
From binary:
10001111
1111 → f
1000 → 8
8f
Dictionaries
A well-balanced search tree
Array with pointers is fast but takes up a shit ton of memory
- Pointers take 8 bytes each
We can shorten the keys but then we have collisions — no longer a function
Hashing
Function that takes in some sort of data type (e.g., a string) and then gives a number
- similar inputs will lead to different outputs
Here’s one (pseudocode)
res = 0
for each char in str
res *= 37
res += char's ascii code
return res % size of hash table array
//we choose 37 because it’s a prime number, which tends to be relatively prime with a lot of other numbers
Created linked list for each hash value index because multiple vlaues may hash to same value
so if multiple things hash to 127 → createad a linked list at 127 that has k,v pairs
Create a hash table (look into this)
Bits
struct pixel {
unsigned char red, green, blue;
}
//256^3 colors to work with
struct pixel* image;
More precise pixels
struct pixel10 {
unsigned short red, green, blue;
}
//represents 65535 but only used to go up to 1023 (the excess is unneeded)
struct pixel10* image;
//in this case, we're only using 10 of the 16 bits for a value of color -> 62.5% of the memory
If we need 10 bits for red, 10 bits for green, and 10 bits for blue
Let’s entertain the example of only needed 5 bits for each value
r: 15 → 01111
g: 10 → 01010
b: 3 → 00011
let’s try to make a 15 bit number that holds all of these
01111 01010 00011 → make this an unsigned short
Some new operators
1100 (12)
&1010 (10)
= 1000
0 and 0 is false
0 and 1 is false
1 and 0 is false
1 and 1 is true
So, 1100 & 1010 = 8 (this is bitwise and)
Bitwise operators
Bitwise and
1100 (12)
&1010 (10)
= 1000
Bitwise or
1100
|1010
1110 → 14
Bitwise !
~1010
0101 = 5
~10 → 5
Shift
1111
<< 2 (number in decimal)
111100
So, how do we put together those 3 rgb values?
RRRRR (5 bits of red)
GGGGG (5 bits of green)
BBBBB (5 bits of blue)
We want RRRRRGGGGGBBBBB
Multiple rows of ors
RRRRR 00000 00000
|00000 GGGGG 00000
|00000 00000 BBBBB
= RRRRR GGGGG BBBBB
How do we get this? Let’s use shifting!
RRRRR
<< 10
= RRRRR0000000000
GGGGG
<< 5
= GGGGG
So, (r << 10)|(g < 5)|(b) gets us those 15 bits representing these 3 colors
Store it in an unsigned short and we get 0RRRRRGGGGGBBBBB and we can assign it to p
So, how do I get red out if it looks like 0RRRRR….?
<aside> 💡 Right shift: 1111 >> 2 = 11
</aside>
p >> 10 = 0RRRRR
So how do I get BBBBB?
Masking
we can use and operator
b = p&31;
//have to put a decimal number in the and
//converting bits to hexadecimal 0001|1111 -> 1 15 -> 1F
b = p&0x1F;
//0x denotes it's in hex
//any number with a leading 0 is interpreted as octal
leading 0 → octal
leading 0x → hx
let’s get g!
g = p>>5&0x1F;
Higher-Order Functions
void squares_chanage(intlist* l) {
while(l) {
l->val = l->val * l->val;
l = l->next;
}
}
Pointers to functions
int add10(int y) {
return x + 10;
}
void foo() {
//pointer to function - variable f is a pointer to a function with a return type of integer (int to the left of (*f)) and the input is an int (parens to
//the right)
int(*f)(int);
//what's wrong with below?
int *f(int)
//looks like a header of the function, just missing a name for the function
f = add10;
int y = f(5);
//calls f with 5 so returns 15
//you can use & and * or just not for function pointers
void map(intlist* l, int(*f)(int)) {
while(l) {
l->val = f(l->val);
l = l->next;
}
}
//e.g., if I have a square function like int square(int x) {...}
map(l, square);
CPUs
To make faster:
- find more efficient algorithms
- make cpu faster (clockspeed) — this is the heartbeat of the processor
- limited by the speed of light through the materials we use
- if a cpu does something 4 billion times a second, light doesn’t move very far in a billionth in a second — the faster we make the processor means less space can be traveled
So, CPUs are getting denser
- dots, wires are 3 nanometers
Use light exposure to determine etching of material on cpu
dots are limited in how small they can be (a few atoms at least)
Easier way of allowing cpus to do more? Allow it do more than one thing at once
- many processors within the processor (many cores)
- one cpu to add two numbers? hard!
- one cpu that has several units that can independently add numbers → much easier
Binary search tree
- can’t split this into work done by multiple pieces (bc of dependencies) — next step depends on previous step
Rotating a matrix
- very easy to do for each core since the next step does not rely on the last step
Difficult for a computer to analyze a program and split it up → you have to think about how to break up your algo into smaller pieces (multithreading)
Multithreading
Single-threaded programs has a single pointer to what it’s working on
Multiple threads → multiple pointers to work on multiple different things
pthreads (posix threads)
Approximating pi
double pi(unsigned int r, unsigned int from, unsigned int to) {
unsigned int counter = 0, x, y;
for(x = from; x < to; x++) {
for(y = from; y = to; y++) {
if(x * x + y * y < r * r) {
count++;
}
}
}
return (count * 4) / (r * r);
}
#include <pthread.h>
struct work {
unsigned int r, f, t;
double res;
};
void* thread(void* arg) {
struct work* w = (struct work*)arg;
w->res = pi(w->r, w->f, w->t);
}
int main() {
struct work w1 = {100000, 0, 25000}; w2 = {100000, 25000, 50000}...
pthread t1, t2, t3, t4;
pthread_create(&t1, NULL, thread, &w1);
pthread_create(&t2, NULL, thread, &w2);
pthread_create(&t3, NULL, thread, &w3);
pthread_create(&t4, NULL, thread, &w4);
double pi = w1.res + w2.res + w3.res + w4.res;
}
Sorting
insert
intlist* insert(intlist* l, int val) {
//requires an already sorted algo
if (l == NULL) {
return prepend(val, NULL);
}
if(val < l->val) {
return prepend(val, l);
}
l->next = insert(l->next, val);
return l;
}
insertion sort
intlist* insertion_sort(intlist* unsorted) {
intlist* sorted = NULL;
while(unsorted) {
sorted = insert(sorted, unsorted->val);
unsorted = unsorted->next;
}
return sorted;
}
Selection
unsigned int find_min(int* a, unsigned int from, unsigned int to) {
unsigned int min = from;
unsigned int i;
for(i = from + 1; i < to; i++) {
if(a[i] < a[min]) {
min = i;
}
}
return min;
}
Selection Sort
int* selection_sort(int* unsorted, unsigned int len) {
unsigned int i;
unsigned int small;
for(i = 0; i < len - 1; i++) {
small = find_min(unsorted, i, len);
int temp = unsorted[i];
unsorted[i] = unsorted[small];
unsorted[small] = temp;
}
return unsorted;