Welcome to ESC190: COMPUTER ALGORITHMS AND DATA STRUCTURES
Python vs C
Thinking about ESC190: why?
Types in C
char: a character (e.g., ‘@’, ‘b’)
int *: address of int
char *: address of char
Strings in C
| | |
@1032 | ‘h’ | |
@1033 | ‘i’ | |
@1034 | ‘\0’ | |
| | |
| | |
| | |
| | |
@1032 is the address of the first character of the string “hi”�We can store @1032 in a variable of type char *
(coming up: const)
Note that “hi” is really of type const char *. More on that later
Literals in C
Lecture 2
Goal: understand and practice the basic semantics of pointers C: arrays, strings, passing values to functions����Memory model sheet: https://docs.google.com/document/d/1ArkXvLQUWFbHNb_wwd_cdTB9RoOHKF0usXx7p1Bf-H4/edit?usp=sharing
Variable declaration in C (overview)
int a = 42;�long int la = 2103984203948L;�char c = ‘@’; // character: a single character
char *s = “abs”; //"string": the address of a first character in the
// sequence 'a', 'b', 's' '\0'
�char *p_c = &c; //& (called the ampersand) means "address of"
Arrays in C
int arr[] = {5, 10, 2}; // can use braces in initialization but not elsewhere�arr[0] = 3;
int arr2[10]; // an array of size 10
//cannot say: arr = {1, 2, 3}; // can only use {} notation for initialization
Overview: pointers
int a = 42;
int *p_a = &a; // p_a is the address of a��char *s = “abc”; // s is the address where the ‘a’ is��*p_a = 43; // put 43 in address p_a
Functions
// add takes in two integers, and returns an integer�int add(int a, int b)
{
return a + b;�}
Functions and pointers
int f(int *p_a)�{
*p_a = 43�}�// the function f takes an address, and puts 43 there
// analogous to passing a list in Python
Arrays and pointers
When used, arrays are generally converted to the pointer to the first element�(assume int a[])
�a[0] is the same as *(a+0), the first element of a
a[1] is the same as *(a+1), the second element of a
Lecture 3
More pointers!
Rules -- take 3
int *p_a = &a;
Rules -- take 3
int a = 42;� char *s = “xyz”;� int arr[] = {4, 5};
int *p_a = &a;
Rules -- take 3
void f(int a)
{� a = 42;
}���f(43) copies 43 to local variable a. The variable a is local, so f has no effect.
Rules -- take 3
void f(int *p_a)
{� p_a = 0;
}���int a = 45;��f(&a) copies &a to local variable p_a. The variable p_a is local, so f has no effect.
Rules -- take 3
void f(int *p_a)
{� *p_a = 0;
}���int a = 45;��f(&a) copies &a to local variable p_a. The variable p_a is local, but *p_a is the same as a, so f does have an effect
Storing blocks of values
Pointer arithmetic
char *s = “hi”; �s+1; // 1033�*(s+1); // ‘i’�
Pointer arithmetic
int arr[] = {3, 4};�arr+1; // 2064+4�*(arr+1); // 4
Pointer arithmetic and arrays
arr[5] is the same as *(arr+5)
// syntactic sugar
// less used: “syntactic salt”: adding features to the syntax that constrain what can be said
// actual new syntax: L[3]
Summary
f(b) is the same as <local a> = b
Exercise 1
void change_a( )
{
}
int a;
change_a( ); // make a change.
// Now, write a function that wouldn't change a but
// change a local variable.
Exercise 1
void change_a(int *p_a )
{
// *p_a = 42; // would change
int b = 45;
p_a = &b;
}
int a;
change_a(&a); // make a change.
// Now, write a function that wouldn't change a but
// change a local variable.
Exercise 2
void change_arr0( )
{
}
int arr[3] = {5, 6, 7};
change_arr0( ); // make arr[0] change.
Exercise 2
void change_arr0(int *p_a )
{
*p_a = 42;
}
int arr[3] = {5, 6, 7};
change_arr0(&(arr[0])); // make arr[0] change. // change_arr0(arr)
change_arr0(&(*arr))
Lecture 4
Review: strings in C
const char *
char *s1 = “abc”; // warning: implicit conversion to char*�s1[0] = ‘x’; // will compile, but might crash at runtime�const char *s2 = “xyz”; // compiles with no warnings�s2[0] = ‘y’; // will not compile
const int, char, etc
const int a = 42;
a = 43; // error��const char c = ‘x’;
c = ‘y’; // error
char * const
char * const str = "hello";
str = “world”; //error
str[0] = ‘H’; // OK
const char * const
const char * const str = "hello";
str = “world”; //error
str[0] = ‘H’; // error���char s[] = “abc”;�s[0] = ‘x’; // fine�s = … // will not compile
The point of “const correctness”
Why are string literals constant?
Lecture 5
Pointers: drills
Blocks of values + pointers
"C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off" Bjarne Stroustrup (inventor of C++)
(Python only lets you use a toy gun)
Summary: again
f(b) is the same as <local a> = b
Printf
printf is a variadic function
printf takes a format string, substitutes in values according to their types
printf("%d %c\n", 87, 87);
printf("%p\n", 87);
printf("%s\n", 87);
int x = 123;
printf("%ld\n", &x);
%d: decimal
%c: character
%s: string
%ld: long decimal
Lecture 6
What have we done so far?
Can now do anything -- but maybe not in the most convenient way!
Plan
typedef
Compound data structures
struct student{
char name[200];
char number[11]; // why 11?� double GPA;�};
struct student{
char name[200];
char number[11]; // why 11?� double GPA;�};��int main()�{� // access fields of a struct using .� struct student s1 = {“John Doe”, “1234567890”, 3.3};� printf(“%s %f\n”, s1.name, s1.GPA);��}
structs and typedef
char name[200];
char number[11]; // why 11?� double GPA;� } student;�� int main()� {� student s1 = {“John Doe”, “1234567890”, 3.3};� printf(“%s %f\n”, s1.name, s1.GPA);�� }�
Pointers to structs
typedef struct student{
char name[200];
char number[11]; // why 11?� double GPA;� } student;�� int main()� {� student s1 = {“John Doe”, “1234567890”, 3.3};� printf(“%s %f\n”, s1.name, s1.GPA);� student *p_s1 = &s1;� printf(“%s %f\n”, (*p_s1).name, (*p_s1).GPA); // *(p_s1.GPA)� }
Pointers to structs: ->
a->b is syntactic sugar for (*a).b�typedef struct student{
char name[200];
char number[11]; // why 11?� double GPA;� } student;�� int main()� {� student s1 = {“John Doe”, “1234567890”, 3.3};� printf(“%s %f\n”, s1.name, s1.GPA);� student *p_s1 = &s1;� printf(“%s %f\n”, p_s1->name, p_s1->GPA);� }
Sending integers, strings, and lists, in Python and C
# no equivalent in Python: we *always* # pass the address, never just the value def change_int(a): # no universal syntax for "go to the address # a and change a value there" (but see code in VS Code) �def dont_change_int(a): a = 42 | void dont_change_int(int a): { a = 42; } void change_int(int *p_a) { *p_a = 43; }���void dont_change_pa(int *p_a)�{ p_a = 0; } |
Sending integers, strings, and lists, in Python and C
def change_L(L): L[0] = 5 dont_change_L(L): L = [1, 2, 3] # works the same with ints | void change_arr(int *arr) { arr[0] = 5; } void dont_change_arr(int *arr) { arr = 0; #works the same with ints } |
Sending integers, strings, and lists, in Python and C
def change_str(s): # no universal way to change the contents of # a string in Python dont_change_s(s): s = "abc" # works the same with ints | void change_s(const char *s) { s[0] = 'x'; // compilation error } void dont_change_arr(const char* arr) { arr = 0; #works the same with ints arr = "abc"; } |
Sending integers, strings, and lists, in Python and C
(no way to modify the contents of a string in Python) | void change_str(char *s) { s[0] = 'x'; // no compilation error, but // may crash if s is actually // constant } int main() { char *s1 = "abc"; change_arr(s1); // may cause a crash char s2[] = "abc"; char s2[] = {'a', 'b', 'c', '\0'} change_str(s2); // OK, s2 is "xbc" |
strlen
1032 | 'h' |
1033 | 'i' |
1034 | '\0' |
1035 | x |
1036 | x |
Starting at 1032, how many steps to get to '\0'?
Want: what is the length of the string starting at 1032?
A: 1 + [length of the string starting at 1033]
malloc
malloc
#include <stdlib.h>�int *block_int = (int *)malloc(sizeof(int) * 150); // allocate space for 150 integers
// malloc returns the address of � // element 0
// cast the address to int *
block_int[7] = 42;
*(block_int + 7) = 42; // REMINDER: those are the same
// block + 7 gets to the right location in the memory table
// because C knows how many cells ints take up
malloc example
// assume ints take up 2 memory cells each��������
int *block_int = (int *)malloc(sizeof(int) * 2); // block_int is 1032
block_int[0] = 5; block_int[1] = 7;
block_int + 1; // 1034�block_int[1]; // same as *(block_int+1)��
1032 | 5 |
1033 | |
1034 | 7 |
1035 | |
1036 | |
Thinking about ESC190
sizeof
sizeof(int) // usually 4 bytes�sizeof(char) // always 1 byte
sizeof(char *) // usually addresses take up 8 bytes
// NOT the length of a string/array
sizeof(int *) // usually addresses take up 8 bytes� // NOT the length of a string/array��(When you get a choice between 32bit and 64bit downloads, that corresponds to differences in the size of the address
sizeof
int arr[] = {1, 2, 3};
sizeof(arr)/sizeoff(arr[0]) // length of the array
But note that if arr is passed to a function, it is converted to a pointer:
void sz(int *a) // int a[] is just syntactic sugar and won't help
{
sizeof(a); // 8
}
�int main()
{
int a[] = {1, 2, 3}
sz(a);
}
Lecture 7
sizeof
typedef struct student{
char name[200];
double GPA;
} student;
sizeof(student); // amount of space allocated for one student
malloc example
arrays vs memory blocks
// flip to VS Code for examples
free
int *block = (int *)malloc(sizeof(int) * 100)
// use block
free(block);
block[0] // undefined behaviour, might crash
Blocks of structs
typedef struct student{
char name[200];
int age;
} student;
// array:
student students[500];
student *students_block = (student *)malloc(sizeof(student)*500)
Dealing with strings
char s1[] = "hi"; // same as char s1[] = {'h', 'i', '\0'};
char s3[5]; strcpy(s3, s1);
char *s2 = 0;
// if "hi" is stored at addresses 1032, 1033, 1034�// the address of the 'h' is 1032, and s1 gets converted to 1032 when used
// s2 = s1; // legal, but strings are now aliases // s2 becomes 1032
strcpy(s2, s1); // not yet OK, since cannot copy to address s2
// same as s2[0] = s1[0], s2[1] = s1[1], ….�s2 = (char *)malloc(sizeof(char)*(strlen(s1)+1);
strcpy(s2, s1); // copy the contents of s1 into s2
Pointers to pointers
*p_p_a = 0; // set the value at address p_p_a to 0
// p_p_a happens to be of type int **
// so *p_p_a is of type int *
}�
int a = 42;
int *p_a = &a;
set_to_0(&p_a); // p_a is now 0. a is not affected!
Compilation
Running gcc manually
gcc myprogram1.c myprogram2.c -o myexec.exe
Header files
Flip over to example
Pre-processor
#define PI 3.14 → substitute 3.14 any time PI is in the program
Pre-processor
(switch to code)
Plan: Monday Jan 27
Reminder: strings
char *name;
strcpy(name, "Alice"); // Bad: name is not a valid address, cannot copy "Alice" there
name = (char *)malloc(100*sizeof(char));�strcpy(name, "Alice"); //OK now
//name[0] = 'a'; // fine
name = "Alice"; // OK, but cannot modify name[0]; If didn't free name, that's a memory leak�name[0] = 'a'; // could crash
Reminder: strings
char name[200] = "Bob"; // same as {'B', 'o', 'b', '\0'}
strcpy(name, "Alice"); // fine, because there are are 200 spaces in name
//name = "Alice"; // Bad: cannot reassign to arrays
name[0] = 'a'; // OK
strings in structs
struct student1{
char *name; // we store the address where the name is stored
}
// need to allocate name for each student
struct student2{
char name[200]; // we store 200 characters
}�// don't need to allocate name for each student
Strings and structs
In VS Code
Handout
realloc
Can resize blocks of memory using realloc��char *str = (char *)malloc(100 * sizeof(char));
// want to make more space��str = (char *)realloc(str, 200 * sizeof(char));
Error checking
char *block = malloc(10000000);
if (block == NULL){
printf("Out of memory\n");
exit(1); // exit terminates the program. the 1 is sent to the operating system
}
Why exit()?
Should you do error checking?
Pointers :: units in physics
create_str
�void create_str(char **p_str, int sz) { *p_str = (char *)malloc(sz * sizeof(char)); if (*p_str == NULL){ printf("Could not create string\n"); exit(1); } (*p_str)[0] = '\0'; | char *str = 0; create_str(&str, 100); � |
Pointers :: Units in physics
char *str = 0;
create_str(&str, 100);
Aside: dimensional analysis in Physics is fake news
strcat
Reminder:
Aside: ++
int a = 42;
int b = a++; // the value of a++ is the old value of a
// the effect of a++ is to increment a by 1
// the value of b is now 42, the value of a is 43
int c = ++b; // the value of ++b is the new value of b (i.e., b+1)
// the effect of ++b is to increment b by 1
// the value of b and c is now 43
strcpy and strcat with pointer arithmetic
*str2 = *str1 copies the value from address str1 to address str2
*str2++ = *str1++ copies values and then increments addresses
aside: value of assignment statement
the value of a = b is the new value of a
can do something like
while(a = b){ // stop if the new value of a is NULL
}
// we will not pursue this...
Blocks of structs + strings
Cleaning up mystr
Abstract Data Types:�Beyond Lists
Stack ADT
"LIFO": Last in, first out
Stack animation: https://www.cs.usfca.edu/~galles/visualization/StackArray.html
Stack operations
Implementing the ADT
Can just use lists
The rest of the semester
Aside
No known techniques to solve efficiently, and likely no possible techniques to solve efficiently:
Possible to do:
Python or C?
Today
Graphs
What are we up to in ESC190?
ESC190 plans
(Min-)Priority Queue ADT
Shortest paths
A* review
Chess search graph
Nodes: game states
Edges: valid moves
Possible heuristic: quality of position ��
Math exams
Trying to find a path from �starting expression to "QED"
Nodes: equation�Edges: valid transformations���DFS: try to pursue each idea to the end�Dijkstra: visit every possible branch, find the shortest path to QED�A*: prioritize exploring states that are close to start and seem promising
Possible heuristic: length �of the expression��(Shorter expression probably�means you're close to something�you can assert as true)
Binary search trees
Is elem in tree?
Looking for 4��4 < 8, so look in the left subtree of 8�4 > 3, so look in the right subtree of 3�4<6, so look in the left subtree of 6�4==4, done
Complexity
Looking for 4��4 < 8, so look in the left subtree of 8�4 > 3, so look in the right subtree of 3�4<6, so look in the left subtree of 6�4==4, done��As many steps as the height of the tree�If the tree is complete, it has 2^h-1 elements and height h�O(h) = O(log n) steps
Complexity: worst case
Also a BST: �� 1
\
2
\
3
In the worst case, the "BST" is just a linked list, and the height is the same as the number of nodes (-1).
So look-up is O(n)
Making worst-case look-up O(log(n))
class Node:
def __init__(self, key):
self.left = None
self.right = None
self.val = key
def make_tree():
root = Node(3)
root.left = Node(2)
root.right = Node(5)
root.left.left = Node(0)
root.left.right = Node(2)
# 3
# / \
# 1 5
# / \
# 0 2
return root
def in_tree(root, elem):
if root is None:
return False
if root.val == elem:
return True
if root.val < elem:
return in_tree(root.right, elem)
else:
return in_tree(root.left, elem)
Neural networks