CC 04 Maps
CC 04 Maps
Object
Dr. Charles R. Severance
www.cc4e.com
code.cc4e.com (sample code)
online.dr-chuck.com
Problems with our "pydict" class
• The previous dict implementation was just a linked list with a key – we
need to build multiple implementations for different performance
requirements
• We need to include our methods in the struct instead of using prefix-
style function naming conventions
• We need a better abstraction for looping that does not require
"peeking" at the class-internal attributes
Object Oriented Principles
• Encapsulation – Bundling code and data together
• https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)
• Abstraction – Separate interface from implementation
• https://en.wikipedia.org/wiki/Abstraction_(computer_science)
• Inheritance – Creating new classes by extending existing classes (DRY)
• https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)
• Polymorphism - Later
• https://en.wikipedia.org/wiki/Polymorphism_(computer_science)
Encapsulation by Naming
Convention
• So far to keep things simple we associate a method for a class with a
simple naming convention
• It seems simple enough but is a bad idea in practice.
• Python strings are objects that follow the principle of encapsulation
• PHP strings are a type plus an associated / prefixed library
• Before I show you the next slide, I need to emphasize that I love many
many things about PHP – but there are some annoyances
Encapsulation by Naming
Convention (is bad)
# Python # PHP
x = "A string with old in it" $x = "A string with old in it";
print(x.find('with')) print(strpos($x,'with')."\n");
print(y) print($y."\n");
print(len(y)) print(???????($y)."\n");
Encapsulation by Naming
Convention (is bad)
# Python # PHP
x = "A string with old in it" $x = "A string with old in it";
print(x.find('with')) print(strpos($x,'with')."\n");
print(y) print($y."\n");
print(len(y)) print(strlen($y)."\n");
Putting Methods in the Structure
int main(void) int main(void)
{ {
struct pydict * dct = pydict_new(); struct pydict * dct = pydict_new();
pydict_del(dct); dct->del(dct);
} }
Because we are emulating OO patterns in C, moving new() into the structure is possible but adds a layer of
complexity at compile and run time. Similarly we need to have 'dct' twice in method calls so methods have
access to the instance in the methods. Languages like C++ and Python solve this by adjusting their compiler.
Access Control / "Leaky"
Abstractions
• When the class is designed such that the calling code needs to look at
data attributes *inside* the class – we call this a "leaky abstraction"
• The implementation details like the choice of an internal variable
name within the class are "leaking" out into the calling code.
• When calling code depends on this internal implementation names
and approaches, it means that we cannot change the code in the class
without breaking calling code
• We need to define a "contract" between the class and its calling code
that we agree won't change. We call this contract an "interface".
struct dnode {
char *key;
int main(void)
{ char *value;
struct pydict * dct = pydict_new(); struct dnode *next;
pydict_put(dct, "z", "Catch phrase"); };
pydict_print(dct);
pydict_put(dct, "z", "W");
pydict_print(dct); struct pydict {
pydict_put(dct, "y", "B"); struct dnode *head;
pydict_put(dct, "c", "C"); struct dnode *tail;
pydict_put(dct, "a", "D");
pydict_print(dct); int count;
printf("Length =%d\n",pydict_len(dct)); };
printf("z=%s\n", pydict_get(dct, "z"));
printf("x=%s\n", pydict_get(dct, "x"));
printf("\nDump\n");
pydict_del(dct);
}
cc_03_04.c
Controlling Access to Object
Attributes
• As we build a class, we decide which elements are part of our
contract with our calling code
• In real OO languages, in the class definition, we mark attributes and
methods with our intended access level
• Accessible by the calling code – "public"
• Accessible only within the class – "private"
• Accessible within the class and other *internal* classes – "protected"
struct dnode {
/* protected */ char *key;
int main(void) /* protected */ char *value;
{ /* protected */ struct dnode *next;
struct pydict * dct = pydict_new(); };
pydict_put(dct, "z", "Catch phrase");
pydict_print(dct); struct pydict {
pydict_put(dct, "z", "W"); /* private */ struct dnode *head;
pydict_print(dct); /* private */ struct dnode *tail;
pydict_put(dct, "y", "B"); /* private */ int count;
pydict_put(dct, "c", "C");
pydict_put(dct, "a", "D"); /* public */ pydict_len();
pydict_print(dct); /* public */ pydict_len();
printf("Length =%d\n",pydict_len(dct)); /* public */ pydict_get();
/* public */ pydict_del();
printf("z=%s\n", pydict_get(dct, "z"));
printf("x=%s\n", pydict_get(dct, "x")); }; Abstraction Boundary
printf("\nDump\n");
cc_03_04.c
Access Control in Java
public class Point {
private double x,y;
Point(double x, double y) {
this.x = x;
this.y = y;
}
public:
Point(double xc, double yc) {
x = xc;
y = yc;
};
void dump() {
printf("Object point x=%f y=%f\n", x, y);
}
};
Access Control in Python
class Point:
__x = 0.0
__y = 0.0
def dump(self):
print('Object point@%x x=%f y=%f' % (id(self),self.__x,self.__y))
What in a Map?
For dictionary like structures
Modern Maps – Key / Value Pairs
• "Map" is a common term we use to describe abstract key/value
collections
• C++ map
• Python dictionary
• Java Map
• PHP Arrays
• Iterator Pattern as an abstraction for looping across multiple
implementations
d = dict()
cc_04_03.php
C++
https://cplusplus.com/reference/map/map/
#include <iostream>
#include <map>
using namespace std; Testing map class
z=1
int main() { x=42
map<string, int> mp;
Iterate
printf("Testing map class\n");
a=4
mp["z"] = 8;
mp["z"] = 1;
b=3
mp["y"] = 2; y=2
mp["b"] = 3; z=1
mp["a"] = 4;
printf("\nIterate\n");
for (auto cur = mp.begin(); cur != mp.end(); ++cur) {
printf(" %s=%d\n", cur->first.c_str(), cur->second);
}
}
cc_04_03.cpp
Java Map<String, Integer>
https://docs.oracle.com/javase/8/docs/api/java/util/Map.html
import java.util.Map;
import java.util.TreeMap; Testing Map class
{a=4, b=3, y=2, z=1}
public class cc_04_03 { z=1
x=42
public static void main(String[] args)
{
Map<String, Integer> map = new TreeMap<String, Integer> (); Iterate
Key = a, Value = 4
System.out.printf("Testing Map class\n"); Key = b, Value = 3
map.put("z", 8); Key = y, Value = 2
map.put("z", 1); Key = z, Value = 1
map.put("y", 2);
map.put("b", 3);
map.put("a", 4);
System.out.println(map);
System.out.println("z="+map.getOrDefault("z", 42));
System.out.println("x="+map.getOrDefault("x", 42));
System.out.printf("\nIterate\n");
for (Map.Entry<String,Integer> entry : map.entrySet()) {
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
}
}
cc_04_03.java
d = dict()
Testing dict class
print("Testing dict class\n");
d["z"] = 8 {'z': 1, 'y': 9, 'b': 3, 'a': 4}
d["z"] = 1 z=1
d["y"] = 9 x=42
<class 'dict_itemiterator'>
d["b"] = 3
Iterate
d["a"] = 4 ('z', 1)
print(d); ('y', 9)
('b', 3)
print("z=%d" % (d.get("z", 42), )); ('a', 4)
print("x=%d" % (d.get("x", 42), ));
items = iter(d.items())
print(type(items));
print("Iterate");
entry = next(items, False)
while (entry) :
print(entry)
entry = next(items, False)
cc_04_03.py
A Minute of C++
Procedural
Hybrid
Object Oriented
Classes were
added to PHP in
ps://en.wikipedia.org/wiki/Object-oriented_programming 2000
// C++ 1980 – Pre-process + compile Three Syntaxes
map<string, int> mp;
mp["z"] = 8;
d["z"] = 8
as if they new native int & operator [](const int & index) {
printf("– Returning reference to %d\n", index);
types like integer and }
return values[index];
floating point };
ten[5] = ten[1] + 2;
printf("Done assigning ten[5]\n");
printf("printf ten[5] contains %d\n", ten[5]);
}
cc_04_04.cpp
#include <iostream>
-- Returning reference to 1
-- Returning reference to 1
class TenInt { printf ten[1] contains 40
private: -- Returning reference to 1
int values[10]; -- Returning reference to 5
public: Done assigning ten[5]
int & operator [](const int & index) { -- Returning reference to 5
printf("-- Returning reference to %d\n", index);
printf ten[5] contains 42
return values[index];
}
};
int main() {
TenInt ten; Java did not want to support call by
ten[1] = 40;
printf("printf ten[1] contains %d\n", ten[1]); reference, and even more did not
support returning a reference from
ten[5] = ten[1] + 2; within a function. C++ figured that a
printf("Done assigning ten[5]\n");
printf("printf ten[5] contains %d\n", ten[5]);
good programmer would use these
} features wisely.
cc_04_04.cpp
The C++ Influence in Python goes
very deep
• The Python approach to x = dict()
x[1] = 40
providing this syntax to us print('print x[1]', x.__getitem__(1))
was to extend the
language to transform the # x[5] = x[1] + 2
x.__setitem__(5, x.__getitem__(1) + 2)
bracket syntax into print(x)
function calls to internal
class methods
print x[1] 40
{1: 40, 5: 42}
cc_04_04.py
C
Python
C++
int main() {
TenInt ten;
ten[1] = 40;
printf("printf ten[1] contains %d\n", ten[1]);
print ten[1] contains 40
ten[5] = ten[1] + 2; Done assigning ten[5]
printf("Done assigning ten[5]\n");
printf("printf ten[5] contains %d\n", ten[5]); print ten[5] contains 42
}
cc_04_05.py
cc_04_04.cpp
Implementing
Encapsulation
Moving methods into our C-based Map class (refactor your existing code)
int main(void)
{
struct MapEntry *cur;
struct Map * map = Map_new();
Testing Map class
printf("Testing Map class\n"); Object Map count=4
map->put(map, "z", 8); z=1
map->put(map, "z", 1); y=2
map->put(map, "y", 2); b=3
map->put(map, "b", 3);
a=4
map->put(map, "a", 4);
map->dump(map);
z=1
x=42
printf("z=%d\n", map->get(map, "z", 42));
printf("x=%d\n", map->get(map, "x", 42));
map->del(map);
}
cc_04_03.c
MapEntry Structure
• This is the structure that
will make up the nodes
struct MapEntry {
in the list.
char *key; /* public */
• The key is a character int value; /* public */
string – the actual data struct MapEntry *__prev;
will be saved in a newly struct MapEntry *__next;
allocated space. };
• The value is an int and
will be allocated right in
the node.
Map structure struct Map {
/* Private attributes */
struct MapEntry *__head;
• This contains the struct MapEntry *__tail;
attributes and methods int __count;
private values is }
}
expected
Destructor
/**
* Destructor for the Map Class
• Free the allocated key *
* Loops through and frees all the keys and
strings, then the * entries in the map. The values are integers
MapEntry structure * so there is no need to free them.
*/
• Note that we take cur- void __Map_del(struct Map* self) {
struct MapEntry *cur, *next;
>next before we free the cur = self->__head;
while(cur) {
node, assuming that cur free(cur->key);
data might be gone. /* value is just part of the struct */
next = cur->__next;
• At the very end we free free(cur);
cur = next;
the Map structure }
free((void *)self);
}
/**
Map_get
* map->get - Locate and return the value for the
* corresponding key or a default value
*
* self - The pointer to the instance of this class.
* key - A character pointer to the key value
* def - A default value to return if the key is
• Returns the value stored * not in the Map
class use – that is the * If the key is not in the Map, an entry is added. If there
* is already an entry in the Map for the key, the value
definition of "private" in * is updated.
*
OO-speak * Sample call:
*
• Uses __Map_find() to *
*
map->put(map, "x", 42);
check if the key is * This method takes inspiration from the Python code:
*
already in the map *
*/
map["key"] = value
Pictures
key: z struct MapEntry {
value: 22 char *key; /* public */
int value; /* public */
prev: ∅ struct MapEntry *__prev;
struct MapEntry *__next;
next: };
struct Map {
struct MapEntry *__head;
struct MapEntry *__tail;
key: w ...
value: 42 };
prev:
next: head
z=22 w=42 ∅
∅
Separation of Concerns
struct Map {
/* Attributes */
implementation
{
https://en.wikipedia.org/wiki/Separation_of_concerns
About Iterators next x = {'a': 1, 'b': 2, 'c': 3}
print('x is', x)
y = list(x)
• To allow code outside the the print('y is', y)
while True :
• Once we have an iterator, each item = next(it, False)
time we call "next" we get the if item is False : break
print('item is', item)
next item in the list until the end
on slides cc_04_05.py
Struct MapIter
• The MapIter is a "related /*
* A MapIter contains the current item and whether
class" to it can access * this is a forward or reverse iterator.
*/
"protected" values in struct MapIter {
struct MapEntry *__current;
MapEntry without struct MapEntry* (*next)(struct MapIter* self);
current
head
b=14 d=21 f=19 ∅
Map_iter() /**
* __Map_iter - Create an iterator from the head the
* self - The pointer to the instance of this class.
*
* returns NULL when there are no entries in the Map
• We allocate a MapIter *
* This is inspired by the following Python code
and set it up with initial * that creates an iterator from a dictionary:
*
• If self->head is NULL, the *
*
x = {'a': 1, 'b': 2, 'c': 3}
it = iter(x)
list is empty. */
struct MapIter* __Map_iter(struct Map* self)
head
b=14 d=21 f=19 ∅
Constructed, before the first call to next()
MapIter_next() /**
* __MapIter_next - Advance the iterator forwards
* or backwards and return the next item
*
* self - The pointer to the instance of this class.
• Note we must return the *
* returns NULL when there are no more entries
current MapEntry *
* This is inspired by the following Python code:
before we advance *
* item = next(iterator, False)
current so we see the */
head
b=14 d=21 f=19 ∅
Next is called, grab current, then advance
reval
MapIter_next() /**
* __MapIter_next - Advance the iterator forwards
* or backwards and return the next item
*
* self - The pointer to the instance of this class.
• Note we must return the *
* returns NULL when there are no more entries
current MapEntry *
* This is inspired by the following Python code:
before we advance *
* item = next(iterator, False)
current so we see the */
head
b=14 d=21 f=19 ∅
Next returns "b=14" and current is advanced
reval
MapIter_next() /**
* __MapIter_next - Advance the iterator forwards
* or backwards and return the next item
*
* self - The pointer to the instance of this class.
• Note we must return the *
* returns NULL when there are no more entries
current MapEntry *
* This is inspired by the following Python code:
before we advance *
* item = next(iterator, False)
current so we see the */
head
b=14 d=21 f=19 ∅
reval
MapIter_next() /**
* __MapIter_next - Advance the iterator forwards
* or backwards and return the next item
*
* self - The pointer to the instance of this class.
• Note we must return the *
* returns NULL when there are no more entries
current MapEntry *
* This is inspired by the following Python code:
before we advance *
* item = next(iterator, False)
current so we see the */
head
b=14 d=21 f=19 ∅
reval
MapIter_next() /**
* __MapIter_next - Advance the iterator forwards
* or backwards and return the next item
*
* self - The pointer to the instance of this class.
• Note we must return the *
* returns NULL when there are no more entries
current MapEntry *
* This is inspired by the following Python code:
before we advance *
* item = next(iterator, False)
current so we see the */
head
b=14 d=21 f=19 ∅
reval
Using an iterator printf("\nIterate\n");
iter = map->iter(map);
while(1) {
• You create the iterator cur = iter->next(iter);
if ( cur == NULL ) break;
printf(" %s=%d\n", cur->key, cur->value);
• Then in a loop, you call }
next() to get each iter->del(iter);
cc_04_06.c
successive entry in the
map, until you exhaust
the entries
x = {'a': 1, 'b': 2, 'c': 3}
• We could make key and it = iter(x)
value private in MapEntry while True :
on slides cc_04_05.py
Summary
• We have improved the design of our class interface
• Abstraction
• Encapsulation
Insert new Contributors and Translators here including names and dates