├── .gitignore
├── Chapter 6 - Graphs.ipynb
├── README.md
├── Chapter 3 - Stacks and Queues.ipynb
├── Chapter 4 - Linked Lists.ipynb
├── Chapter 5 - Trees.ipynb
└── Chapter 2 - Arrays.ipynb
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *copy*.ipynb
3 | .ipynb_checkpoints
4 |
--------------------------------------------------------------------------------
/Chapter 6 - Graphs.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": {
3 | "name": "",
4 | "signature": "sha256:43a9354001c83189e570a9c2c7574faee340a75df29385a34abed713b1c30d56"
5 | },
6 | "nbformat": 3,
7 | "nbformat_minor": 0,
8 | "worksheets": [
9 | {
10 | "cells": [
11 | {
12 | "cell_type": "heading",
13 | "level": 2,
14 | "metadata": {},
15 | "source": [
16 | "ADT 6.1, PAGE 331"
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "collapsed": false,
22 | "input": [
23 | "# For practical implementation into bigger projects, one may refer to\n",
24 | "# standard libraries such as networkx - https://networkx.github.io/\n",
25 | "\n",
26 | "class Graph(object):\n",
27 | " def __init__():\n",
28 | " pass\n",
29 | " \n",
30 | " def is_empty():\n",
31 | " \"\"\"Returns true if the Graph is empty.\"\"\"\n",
32 | " \n",
33 | " \n",
34 | " \n",
35 | " \n",
36 | " "
37 | ],
38 | "language": "python",
39 | "metadata": {},
40 | "outputs": []
41 | }
42 | ],
43 | "metadata": {}
44 | }
45 | ]
46 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Data Structures By Horowitz - As iPython Notebooks
2 |
3 | Run these notebooks online - [](http://mybinder.org/repo/raghavrv/Data-Structures-By-Horowitz-in-Python)
4 |
5 |
6 | Python implementation of the examples in the text book [Fundamentals of
7 | Data Structures in C++ by E.Horowitz, S.Sahni and
8 | Mehta](http://www.amazon.in/dp/8173716064/ref=cm_sw_r_tw_dp_P0I9sb188T11M).
9 |
10 | This is a part of my contribution to the textbook companionship project
11 | organized by the [FOSSEE](http://python.fossee.in/) initiative of
12 | [IIT Bombay](https://www.iitb.ac.in/).
13 |
14 |
15 | #### Sneak Peek
16 |
17 | ##### Chapter 1
18 | **Plotting the Fibonacci Spiral in Python!**
19 |
20 |
21 |
22 |
23 | **Profiling the time cost of sequential search**
24 |
25 |
26 |
27 | ##### Chapter 2
28 |
29 | **The LaTeX enabled Polynomial module**
30 |
31 |
32 |
33 | **Find vs Fast Find**
34 |
35 |
36 |
37 | ##### Chapter 3
38 |
39 | **Maze Path Finder (Application of stack)**
40 |
41 |
42 |
43 | ##### Chapter 7
44 | **Visualizing Heap sort using graph viz plots**
45 |
46 | ```
47 | The input array ( Represented as dictionary ) :
48 | {1: 26, 2: 5, 3: 77, 4: 1, 5: 61, 6: 11, 7: 59, 8: 15, 9: 48, 10: 19}
49 | ```
50 |
51 | 
52 |
53 | (One of the intermediate steps)
54 |
55 | 
56 |
57 | ```
58 | Sorted : [77, 61, 59, 48, 26]
59 | ```
60 |
61 | (Final step and the sorted array)
62 |
63 | 
64 |
65 | ```
66 | Sorted : [77, 61, 59, 48, 26, 19, 15, 11, 5]
67 | The final sorted array is : [77, 61, 59, 48, 26, 19, 15, 11, 5, 1]
68 | ```
69 |
70 | ##### Chapter 8
71 | **RSA hash generation flowchart in Python!!**
72 |
73 |
74 | **Hashmap collision avoidance using chaining**
75 |
76 |
77 | #### Contribute
78 | If you wish to contribute too, visit [FOSSEE](http://fossee.in/) and
79 | see if you can find anything matching your interests.
80 |
81 | #### Copyright
82 | The copyrights for all the codes belong to the FOSSEE Department of IIT
83 | Bombay.
84 |
85 | #### Contact
86 | rvraghav93@gmail.com
87 |
--------------------------------------------------------------------------------
/Chapter 3 - Stacks and Queues.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": {
3 | "name": "",
4 | "signature": "sha256:44b54fb1fd14d6735dba1b11d6e67b5bb26265f7c730c29e178c4939bd141d0d"
5 | },
6 | "nbformat": 3,
7 | "nbformat_minor": 0,
8 | "worksheets": [
9 | {
10 | "cells": [
11 | {
12 | "cell_type": "heading",
13 | "level": 1,
14 | "metadata": {},
15 | "source": [
16 | "CHAPTER 3 - STACKS AND QUEUES"
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "collapsed": false,
22 | "input": [
23 | "# Imports:\n",
24 | "import numpy as np # Array manipulations\n",
25 | "import Queue as qu # Queue implementation\n",
26 | "import matplotlib.pyplot as plt # To plot the maze"
27 | ],
28 | "language": "python",
29 | "metadata": {},
30 | "outputs": [],
31 | "prompt_number": 1
32 | },
33 | {
34 | "cell_type": "heading",
35 | "level": 2,
36 | "metadata": {},
37 | "source": [
38 | "PROGRAM 3.1, PAGE 129"
39 | ]
40 | },
41 | {
42 | "cell_type": "code",
43 | "collapsed": false,
44 | "input": [
45 | "# Selection sort\n",
46 | "\n",
47 | "def selection_sort(a,n):\n",
48 | " # Sort a[0] to a[n-1]\n",
49 | " for i in range(n):\n",
50 | " j = i\n",
51 | " # Find smallest integer in a[i] to a[n-1]\n",
52 | " for k in range(i+1,n):\n",
53 | " if a[k] < a[j]:\n",
54 | " j = k\n",
55 | " \n",
56 | " a[i], a[j] = a[j], a[i]"
57 | ],
58 | "language": "python",
59 | "metadata": {},
60 | "outputs": [],
61 | "prompt_number": 2
62 | },
63 | {
64 | "cell_type": "heading",
65 | "level": 2,
66 | "metadata": {},
67 | "source": [
68 | "PROGRAM 3.2, PAGE 129"
69 | ]
70 | },
71 | {
72 | "cell_type": "code",
73 | "collapsed": false,
74 | "input": [
75 | "# Code Fragment to illustrate template instantiation\n",
76 | "farray = [ float(j)/2 for j in range(10,0,-1) ]\n",
77 | "intarray = [ j for j in range(25,0,-1) ]\n",
78 | "print \"Before sorting : \"\n",
79 | "print farray\n",
80 | "print intarray\n",
81 | "\n",
82 | "selection_sort(farray,len(farray))\n",
83 | "selection_sort(intarray,len(intarray))\n",
84 | "\n",
85 | "print \"After sorting : \"\n",
86 | "print farray\n",
87 | "print intarray"
88 | ],
89 | "language": "python",
90 | "metadata": {},
91 | "outputs": [
92 | {
93 | "output_type": "stream",
94 | "stream": "stdout",
95 | "text": [
96 | "Before sorting : \n",
97 | "[5.0, 4.5, 4.0, 3.5, 3.0, 2.5, 2.0, 1.5, 1.0, 0.5]\n",
98 | "[25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]\n",
99 | "After sorting : \n",
100 | "[0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0]\n",
101 | "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]\n"
102 | ]
103 | }
104 | ],
105 | "prompt_number": 3
106 | },
107 | {
108 | "cell_type": "heading",
109 | "level": 2,
110 | "metadata": {},
111 | "source": [
112 | "PROGRAM 3.4, PAGE 131 & PROGRAM 3.5, PAGE 132"
113 | ]
114 | },
115 | {
116 | "cell_type": "code",
117 | "collapsed": false,
118 | "input": [
119 | "# Definition of the class Bag containing integers\n",
120 | "\n",
121 | "# The concept of capacity is not necessary in python since\n",
122 | "# list size and memory management is done automatically by python\n",
123 | "\n",
124 | "class Bag(object):\n",
125 | " def __init__(self):\n",
126 | " \n",
127 | " self._array = []\n",
128 | " self._top = -1\n",
129 | " \n",
130 | " def size(self):\n",
131 | " '''returns the number of elements in the bag'''\n",
132 | " return self._top + 1\n",
133 | " \n",
134 | " def is_empty(self):\n",
135 | " '''return true if the bag is empty; false otherwise'''\n",
136 | " return size == 0\n",
137 | " \n",
138 | " def element(self):\n",
139 | " '''Return an element that is in the bag'''\n",
140 | " if self.is_empty():\n",
141 | " raise Exception('Bag is empty')\n",
142 | " else:\n",
143 | " return self._array[0]\n",
144 | " \n",
145 | " def push(self,elt):\n",
146 | " '''Add an integer to the end of the bag'''\n",
147 | " self._array.append(elt)\n",
148 | " self._top += 1\n",
149 | " \n",
150 | " def pop(self):\n",
151 | " '''Delete an integer from the bag'''\n",
152 | " self._top -= 1 \n",
153 | " return self._array.pop()"
154 | ],
155 | "language": "python",
156 | "metadata": {},
157 | "outputs": [],
158 | "prompt_number": 4
159 | },
160 | {
161 | "cell_type": "code",
162 | "collapsed": false,
163 | "input": [
164 | "bg = Bag()\n",
165 | "bg.push(5)\n",
166 | "bg.push(6)\n",
167 | "bg.push(7)\n",
168 | "bg.push(8)\n",
169 | "print \"Popped Item : \", bg.pop()\n",
170 | "print \"The contents of the bag : \", bg._array"
171 | ],
172 | "language": "python",
173 | "metadata": {},
174 | "outputs": [
175 | {
176 | "output_type": "stream",
177 | "stream": "stdout",
178 | "text": [
179 | "Popped Item : 8\n",
180 | "The contents of the bag : [5, 6, 7]\n"
181 | ]
182 | }
183 | ],
184 | "prompt_number": 5
185 | },
186 | {
187 | "cell_type": "markdown",
188 | "metadata": {},
189 | "source": [
190 | "
"
233 | ],
234 | "language": "python",
235 | "metadata": {},
236 | "outputs": [],
237 | "prompt_number": 6
238 | },
239 | {
240 | "cell_type": "heading",
241 | "level": 2,
242 | "metadata": {},
243 | "source": [
244 | "PROGRAM 5.6, PAGE 265"
245 | ]
246 | },
247 | {
248 | "cell_type": "code",
249 | "collapsed": false,
250 | "input": [
251 | "# Code for obtaining the next inorder element\n",
252 | "def _next(self):\n",
253 | " if \"s\" not in self.next.__dict__:\n",
254 | " # During every 1st iteration :\n",
255 | " \n",
256 | " self.next.__dict__[\"s\"] = list() # Define an empty list\n",
257 | " # Python equivalent of a C++ static list variable s\n",
258 | " \n",
259 | " if \"currentNode\" not in self.next.__dict__:\n",
260 | " self.next.__dict__[\"currentNode\"] = self.root\n",
261 | " \n",
262 | " while self.next.currentNode is not None:\n",
263 | " self.next.s.append(self.next.currentNode)\n",
264 | " self.next.__dict__[\"currentNode\"] = self.next.currentNode.leftChild\n",
265 | " \n",
266 | " if len(self.next.s) == 0:\n",
267 | " self.next.__dict__.pop(\"s\")\n",
268 | " self.next.__dict__.pop(\"currentNode\")\n",
269 | " raise StopIteration\n",
270 | " \n",
271 | " self.next.__dict__[\"currentNode\"] = self.next.s.pop()\n",
272 | " \n",
273 | " temp = self.next.currentNode.data\n",
274 | " \n",
275 | " self.next.__dict__[\"currentNode\"] = self.next.currentNode.rightChild\n",
276 | " \n",
277 | " return temp\n",
278 | "\n",
279 | "Tree.next = _next\n",
280 | "\n",
281 | "# A more simpler Python way to iterate over the tree elements in inorder traversal is by using the generator\n",
282 | "# Not given in Text Book\n",
283 | "\n",
284 | "def _InorderIterator(self, tree = -1):\n",
285 | " if tree == -1:\n",
286 | " tree = self.root\n",
287 | " \n",
288 | " if tree is not None:\n",
289 | " for node in self.InorderIterator(tree.leftChild):\n",
290 | " yield node\n",
291 | " \n",
292 | " yield tree.data\n",
293 | " \n",
294 | " for node in self.InorderIterator(tree.rightChild):\n",
295 | " yield node\n",
296 | " \n",
297 | "Tree.InorderIterator = _InorderIterator"
298 | ],
299 | "language": "python",
300 | "metadata": {},
301 | "outputs": [],
302 | "prompt_number": 7
303 | },
304 | {
305 | "cell_type": "heading",
306 | "level": 2,
307 | "metadata": {},
308 | "source": [
309 | "PROGRAM 5.7, PAGE 266"
310 | ]
311 | },
312 | {
313 | "cell_type": "code",
314 | "collapsed": false,
315 | "input": [
316 | "# Level order traversal of Binary tree\n",
317 | "\n",
318 | "def _LevelOrder(self):\n",
319 | " \n",
320 | " q = list() # Using a list as a queue\n",
321 | " currentNode = self.root\n",
322 | " \n",
323 | " while currentNode is not None:\n",
324 | " self.Visit(currentNode)\n",
325 | "\n",
326 | " if currentNode.leftChild:\n",
327 | " q.append(currentNode.leftChild)\n",
328 | " if currentNode.rightChild:\n",
329 | " q.append(currentNode.rightChild)\n",
330 | " if len(q) == 0:\n",
331 | " return\n",
332 | " \n",
333 | " currentNode = q.pop()\n",
334 | " \n",
335 | "Tree.LevelOrder = _LevelOrder"
336 | ],
337 | "language": "python",
338 | "metadata": {},
339 | "outputs": [],
340 | "prompt_number": 8
341 | },
342 | {
343 | "cell_type": "code",
344 | "collapsed": false,
345 | "input": [
346 | "# Sample Example to Showcase Working of Tree and its functions defined so far\n",
347 | "# ( NOT IN TEXTBOOK )\n",
348 | "# Tree Example Diagram @ http://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Binary_tree.svg/300px-Binary_tree.svg.png\n",
349 | "\n",
350 | "# Defining Height = 0 Nodes ( Leafs ):\n",
351 | "\n",
352 | "h0_1 = TreeNode(None, 5, None)\n",
353 | "h0_2 = TreeNode(None, 11, None)\n",
354 | "h0_3 = TreeNode(None, 4, None)\n",
355 | "\n",
356 | "# Defining Height = 1 Nodes ( Subtrees and one leaf - h1_1):\n",
357 | "\n",
358 | "h1_1 = TreeNode(None, 2, None)\n",
359 | "h1_2 = TreeNode(h0_1, 6, h0_2)\n",
360 | "h1_3 = TreeNode(h0_3, 9, None)\n",
361 | "\n",
362 | "# Defining Height = 2 Nodes ( Subtrees ):\n",
363 | "\n",
364 | "h2_1 = TreeNode(h1_1, 7, h1_2)\n",
365 | "h2_2 = TreeNode(None, 5, h1_3)\n",
366 | "\n",
367 | "# Defining Height = 3 Node ( root ):\n",
368 | "\n",
369 | "h3_1 = TreeNode(h2_1, 2, h2_2)\n",
370 | "\n",
371 | "tree1 = Tree(h3_1)\n",
372 | "\n",
373 | "print \"inorder iteration using next() definition ( as provided in the TextBook )\\n\"\n",
374 | "\n",
375 | "for i in tree1:\n",
376 | " print i,\n",
377 | " \n",
378 | "print \"\\n\\nInorderIterator using Generator function\\n\"\n",
379 | "\n",
380 | "for i in tree1.InorderIterator():\n",
381 | " print i, \n",
382 | " \n",
383 | "print \"\\n\\nPost order traversal using function Postorder()\\n\"\n",
384 | "\n",
385 | "tree1.Postorder()\n",
386 | "\n",
387 | "print \"\\n\\nPre order traversal using function Preorder()\\n\"\n",
388 | "\n",
389 | "tree1.Preorder()\n",
390 | "\n",
391 | "print \"\\n\\nIn order traversal using function Inorder()\\n\"\n",
392 | "\n",
393 | "tree1.Inorder()\n",
394 | "\n",
395 | "print \"\\n\\nIn order traversal using function NonrecInorder()\\n\"\n",
396 | "\n",
397 | "tree1.NonrecInorder()\n",
398 | "\n",
399 | "print \"\\n\\nLevel order traversal using function LevelOrder()\\n\"\n",
400 | "\n",
401 | "tree1.LevelOrder()"
402 | ],
403 | "language": "python",
404 | "metadata": {},
405 | "outputs": [
406 | {
407 | "output_type": "stream",
408 | "stream": "stdout",
409 | "text": [
410 | "inorder iteration using next() definition ( as provided in the TextBook )\n",
411 | "\n",
412 | "2 7 5 6 11 2 5 4 9 \n",
413 | "\n",
414 | "InorderIterator using Generator function\n",
415 | "\n",
416 | "2 7 5 6 11 2 5 4 9 \n",
417 | "\n",
418 | "Post order traversal using function Postorder()\n",
419 | "\n",
420 | "2 5 11 6 7 4 9 5 2 \n",
421 | "\n",
422 | "Pre order traversal using function Preorder()\n",
423 | "\n",
424 | "2 7 2 6 5 11 5 9 4 \n",
425 | "\n",
426 | "In order traversal using function Inorder()\n",
427 | "\n",
428 | "2 7 5 6 11 2 5 4 9 \n",
429 | "\n",
430 | "In order traversal using function NonrecInorder()\n",
431 | "\n",
432 | "2 7 5 6 11 2 5 4 9 \n",
433 | "\n",
434 | "Level order traversal using function LevelOrder()\n",
435 | "\n",
436 | "2 5 9 4 7 6 11 5 2\n"
437 | ]
438 | }
439 | ],
440 | "prompt_number": 9
441 | },
442 | {
443 | "cell_type": "heading",
444 | "level": 2,
445 | "metadata": {},
446 | "source": [
447 | "PROGRAM 5.8, PAGE 269"
448 | ]
449 | },
450 | {
451 | "cell_type": "code",
452 | "collapsed": false,
453 | "input": [
454 | "# O(1) space inorder traversal\n",
455 | "\n",
456 | "def _NoStackInorder(self):\n",
457 | " # Inorder traversal of Binary Tree using a fixed amount of additional storage\n",
458 | " if self.root is None:\n",
459 | " return\n",
460 | "\n",
461 | " top = lastRight = p = q = r = r1 = None\n",
462 | " p = q = self.root\n",
463 | " while True:\n",
464 | " while True:\n",
465 | " if ( p.leftChild is None ) and ( p.rightChild is None ):\n",
466 | " # Leaf Node\n",
467 | " self.Visit(p)\n",
468 | " break\n",
469 | "\n",
470 | " elif p.leftChild is None:\n",
471 | " # Visit p and move to p.rightChild\n",
472 | " self.Visit(p)\n",
473 | " r = p.rightChild\n",
474 | " p.rightChild = q\n",
475 | " q = p\n",
476 | " p = r\n",
477 | " \n",
478 | " else:\n",
479 | " # move to p.leftChild\n",
480 | " r = p.leftChild\n",
481 | " p.leftChild = q\n",
482 | " q = p\n",
483 | " p = r\n",
484 | "\n",
485 | " # p is a leaf node move upward to a node whose right subtree has not yet been examined\n",
486 | " av = p\n",
487 | " while True:\n",
488 | " if p == self.root:\n",
489 | " return None\n",
490 | "\n",
491 | " if q.leftChild is None:\n",
492 | " # q is linked via rightChild\n",
493 | " r = q.rightChild\n",
494 | " q.rightChild = p\n",
495 | " p = q\n",
496 | " q = r\n",
497 | "\n",
498 | " elif q.rightChild is None:\n",
499 | " # q is linked via leftChild\n",
500 | " r = q.leftChild\n",
501 | " q.leftChild = p\n",
502 | " p = q\n",
503 | " q = r\n",
504 | "\n",
505 | " self.Visit(p)\n",
506 | "\n",
507 | " else:\n",
508 | " # Check if p is a rightChild of q\n",
509 | " if q == lastRight:\n",
510 | " r = top\n",
511 | " lastRight = r.leftChild\n",
512 | " top = r.rightChild # unstack\n",
513 | " r.leftChild = r.rightChild = None\n",
514 | " r = q.rightChild\n",
515 | " q.rightChild = p\n",
516 | " p = q\n",
517 | " q = r\n",
518 | "\n",
519 | " else:\n",
520 | " # p is leftChild of q\n",
521 | " self.Visit(q)\n",
522 | " av.leftChild = lastRight\n",
523 | " av.rightChild = top\n",
524 | " top = av\n",
525 | " lastRight = q\n",
526 | " r = q.leftChild\n",
527 | " q.leftChild = p # Restore link to p\n",
528 | " \n",
529 | " r1 = q.rightChild\n",
530 | " q.rightChild = r\n",
531 | " p = r1\n",
532 | "\n",
533 | " break\n",
534 | "\n",
535 | "Tree.NoStackInorder = _NoStackInorder"
536 | ],
537 | "language": "python",
538 | "metadata": {},
539 | "outputs": [],
540 | "prompt_number": 10
541 | },
542 | {
543 | "cell_type": "code",
544 | "collapsed": false,
545 | "input": [
546 | "# Sample Output ( NOT IN TEXTBOOK )\n",
547 | "tree1.NoStackInorder()"
548 | ],
549 | "language": "python",
550 | "metadata": {},
551 | "outputs": [
552 | {
553 | "output_type": "stream",
554 | "stream": "stdout",
555 | "text": [
556 | "2 7 5 6 11 2 5 4 9\n"
557 | ]
558 | }
559 | ],
560 | "prompt_number": 11
561 | },
562 | {
563 | "cell_type": "heading",
564 | "level": 2,
565 | "metadata": {},
566 | "source": [
567 | "PROGRAM 5.9, PAGE 270"
568 | ]
569 | },
570 | {
571 | "cell_type": "code",
572 | "collapsed": false,
573 | "input": [
574 | "# Copying a binary tree.\n",
575 | "\n",
576 | "# Overloading __init__ to provide copy constructor like functionality\n",
577 | "\n",
578 | "def _init(self, copy_from = None):\n",
579 | " # This implementation differs from the Textbook definition\n",
580 | " # Here Tree is an encapsulation of the root\n",
581 | " # root is a Binary Tree formed by the Linking together TreeNodes\n",
582 | " # This will avoid confusion between Tree and TreeNode\n",
583 | " \"\"\" Create a new Binary Tree \"\"\"\n",
584 | " \n",
585 | " # if the copy_from is a Tree Class object\n",
586 | " if isinstance(copy_from, Tree):\n",
587 | " self.root = self.Copy(copy_from.root)\n",
588 | " \n",
589 | " # if the copy_from is a root ( TreeNode object )\n",
590 | " else:\n",
591 | " self.root = copy_from\n",
592 | " # This also copies None to root if passed\n",
593 | " \n",
594 | "Tree.__init__ = _init\n",
595 | "\n",
596 | "def _Copy(self, origNode):\n",
597 | " # Workhorse which recursively copies the whole binary tree.\n",
598 | " \n",
599 | " # Return a pointer to an exact copy of the binary tree rooted at origNode\n",
600 | " \n",
601 | " if origNode is None:\n",
602 | " return None\n",
603 | " \n",
604 | " return TreeNode(self.Copy(origNode.leftChild), origNode.data, self.Copy(origNode.rightChild))\n",
605 | "\n",
606 | "Tree.Copy = _Copy"
607 | ],
608 | "language": "python",
609 | "metadata": {},
610 | "outputs": [],
611 | "prompt_number": 12
612 | },
613 | {
614 | "cell_type": "heading",
615 | "level": 2,
616 | "metadata": {},
617 | "source": [
618 | "PROGRAM 5.10, PAGE 270"
619 | ]
620 | },
621 | {
622 | "cell_type": "code",
623 | "collapsed": false,
624 | "input": [
625 | "# Binary tree equivalence\n",
626 | "\n",
627 | "def _eq(self, t):\n",
628 | " if isinstance(t, Tree):\n",
629 | " return self.Equal(self.root, t.root)\n",
630 | "\n",
631 | "Tree.__eq__ = _eq\n",
632 | " \n",
633 | "def _Equal(self, a, b):\n",
634 | " # Workhorse which recursively checks for equivalence\n",
635 | " if (a is None) and (b is None):\n",
636 | " return True\n",
637 | " \n",
638 | " # Return True if :\n",
639 | " # Both a and b are not None\n",
640 | " # Data is same\n",
641 | " # left subtrees are equal\n",
642 | " # right subtrees are equal\n",
643 | " \n",
644 | " return ( a is not None ) and (b is not None ) and (a.data == b.data) and self.Equal(a.leftChild, b.leftChild) and self.Equal(a.rightChild, b.rightChild) \n",
645 | " \n",
646 | "Tree.Equal = _Equal\n"
647 | ],
648 | "language": "python",
649 | "metadata": {},
650 | "outputs": [],
651 | "prompt_number": 13
652 | },
653 | {
654 | "cell_type": "code",
655 | "collapsed": false,
656 | "input": [
657 | "# Sample Output ( NOT IN TEXT BOOK )\n",
658 | "\n",
659 | "tree2 = Tree(tree1) # Copy from tree1 to tree2\n",
660 | "tree2.NoStackInorder()\n",
661 | "\n",
662 | "print \"\\n\\nBinary Tree Equivalence: \", tree2 == tree1\n",
663 | "\n",
664 | "tree3 = Tree(None) # Empty tree\n",
665 | "print \"\\n\\nBinary Tree Equivalence 2: \", tree1 == tree3"
666 | ],
667 | "language": "python",
668 | "metadata": {},
669 | "outputs": [
670 | {
671 | "output_type": "stream",
672 | "stream": "stdout",
673 | "text": [
674 | "2 7 5 6 11 2 5 4 9 \n",
675 | "\n",
676 | "Binary Tree Equivalence: True\n",
677 | "\n",
678 | "\n",
679 | "Binary Tree Equivalence 2: False\n"
680 | ]
681 | }
682 | ],
683 | "prompt_number": 14
684 | },
685 | {
686 | "cell_type": "heading",
687 | "level": 2,
688 | "metadata": {},
689 | "source": [
690 | "PROGRAM 5.12, PAGE 273"
691 | ]
692 | },
693 | {
694 | "cell_type": "code",
695 | "collapsed": false,
696 | "input": [
697 | "# Expression Tree definition ( NOT GIVEN IN THE TEXT BOOK )\n",
698 | "# This definition is not given in the text book but is assumed for the algorithm to work\n",
699 | "\n",
700 | "# Here the structure of TreeNode have been kept the same for ease of presentability of sample output\n",
701 | "# In the Text Book a different structure is presented with Nodes having twin data field\n",
702 | "\n",
703 | "class ExprTree(Tree):\n",
704 | " \n",
705 | " def evaluate(self, node = -1, variable_values = None):\n",
706 | " \"\"\" \n",
707 | " Evaluates the Tree for the given list of variable values \n",
708 | " \n",
709 | " USAGE:\n",
710 | " >>> t = ExprTree(root)\n",
711 | " >>> t.evaluate([1, 0, 0])\n",
712 | " 1\n",
713 | " \n",
714 | " \"\"\"\n",
715 | " # Exception handling\n",
716 | " if (node == -1) and (variable_values is None):\n",
717 | " raise Exception(\"Variable Value vector is not passed\")\n",
718 | " \n",
719 | " # Initializing\n",
720 | " elif node == -1:\n",
721 | " node = self.root\n",
722 | " \n",
723 | " \n",
724 | " # PROGRAM 5.12, PAGE 273\n",
725 | " # Visiting a Node in an Expression Tree\n",
726 | " \n",
727 | " var_list = self.getVariables()\n",
728 | " \n",
729 | " # If the node contains boolean data\n",
730 | " if node.data in [1, 0]:\n",
731 | " return bool(node)\n",
732 | " \n",
733 | " # If the node contains a variable\n",
734 | " if node.data in var_list:\n",
735 | " var_index = var_list.index(node.data)\n",
736 | " var_value = variable_values[var_index]\n",
737 | " \n",
738 | " return bool(var_value)\n",
739 | " \n",
740 | " # If the node is an operator\n",
741 | " elif node.data in [ '^', 'v', '~' ]:\n",
742 | " \n",
743 | " if node.data == '^':\n",
744 | " # And operator\n",
745 | " return self.evaluate(node.rightChild, variable_values) and self.evaluate(node.leftChild, variable_values)\n",
746 | " elif node.data == 'v':\n",
747 | " # Or operator\n",
748 | " return self.evaluate(node.rightChild, variable_values) or self.evaluate(node.leftChild, variable_values)\n",
749 | " elif node.data == '~':\n",
750 | " # Not operator\n",
751 | " return not self.evaluate(node.rightChild, variable_values)\n",
752 | " \n",
753 | " def getVariables(self, node = -1):\n",
754 | " # Defining a static var_list for storage of variables while recursing throught the tree\n",
755 | " \n",
756 | " if node is -1:\n",
757 | " node = self.root\n",
758 | " \n",
759 | " if node == self.root:\n",
760 | " self.getVariables.__dict__[\"var_list\"] = []\n",
761 | " \n",
762 | " if node is None:\n",
763 | " return\n",
764 | "\n",
765 | " # If the node contains a variable string and the variable is not already in the static var_list\n",
766 | " if ( node.data not in ['^', 'v', '~', 1, 0, True, False, None] ) and ( node.data not in self.getVariables.__dict__[\"var_list\"] ):\n",
767 | " self.getVariables.__dict__[\"var_list\"].append(node.data)\n",
768 | "\n",
769 | " self.getVariables(node.rightChild) # Parse the right subtree\n",
770 | " self.getVariables(node.leftChild) # Parse the left subtree\n",
771 | "\n",
772 | " var_list = sorted(self.getVariables.__dict__[\"var_list\"])\n",
773 | " \n",
774 | " if node == self.root:\n",
775 | " # Cleaning up the function namespace if the function returns back to the root node\n",
776 | " self.getVariables.__dict__.pop('var_list')\n",
777 | " \n",
778 | " # Return the list of variables\n",
779 | " return var_list\n",
780 | "\n",
781 | "# SAMPLE OUTPUT - Refer Tree Diagram - Figure 5.18, Page 272\n",
782 | "\n",
783 | "# Constructing the Expression Tree\n",
784 | "\n",
785 | "# Defining leaf Nodes:\n",
786 | "\n",
787 | "l1 = TreeNode(None, 'x1', None)\n",
788 | "l2 = TreeNode(None, 'x2', None)\n",
789 | "l3 = TreeNode(None, 'x3', None)\n",
790 | "\n",
791 | "o1 = TreeNode(None, '~', l2)\n",
792 | "o2 = TreeNode(l1, '^', o1)\n",
793 | "o3 = TreeNode(None, '~', l1)\n",
794 | "o4 = TreeNode(o3, '^', l3)\n",
795 | "\n",
796 | "o5 = TreeNode(o2, 'v', o4)\n",
797 | "o6 = TreeNode(None, '~', l3)\n",
798 | "\n",
799 | "o7 = TreeNode(o5, 'v', o6)\n",
800 | "\n",
801 | "exp = ExprTree(o7)\n",
802 | "\n",
803 | "print \"Inorder traversal : \"\n",
804 | "exp.Inorder()\n",
805 | "\n",
806 | "print \"\\n\\nThe variable list is : \"\n",
807 | "print exp.getVariables()\n",
808 | "\n",
809 | "print \"\\nEvaluating the expression for (1,1,1) : \",\n",
810 | "print exp.evaluate(variable_values = [1, 1, 1])\n",
811 | "\n",
812 | "print \"\\nEvaluating the expression for (1,0,1) : \",\n",
813 | "print exp.evaluate(variable_values = [1, 0, 1])\n"
814 | ],
815 | "language": "python",
816 | "metadata": {},
817 | "outputs": [
818 | {
819 | "output_type": "stream",
820 | "stream": "stdout",
821 | "text": [
822 | "Inorder traversal : \n",
823 | "x1 ^ ~ x2 v ~ x1 ^ x3 v ~ x3 \n",
824 | "\n",
825 | "The variable list is : \n",
826 | "['x1', 'x2', 'x3']\n",
827 | "\n",
828 | "Evaluating the expression for (1,1,1) : False\n",
829 | "\n",
830 | "Evaluating the expression for (1,0,1) : True\n"
831 | ]
832 | }
833 | ],
834 | "prompt_number": 15
835 | },
836 | {
837 | "cell_type": "heading",
838 | "level": 2,
839 | "metadata": {},
840 | "source": [
841 | "PROGRAM 5.11, PAGE 273"
842 | ]
843 | },
844 | {
845 | "cell_type": "code",
846 | "collapsed": false,
847 | "input": [
848 | "# First version of satisfiability algorithm\n",
849 | "\n",
850 | "def _Satisfiability(self):\n",
851 | " var_list = self.getVariables()\n",
852 | " \n",
853 | " no_of_vars = len(var_list)\n",
854 | " \n",
855 | " for i in range(no_of_vars):\n",
856 | " \n",
857 | " # Convert an integer to a binary string\n",
858 | " # 5 --> '0b101' --> '101' --> '0101'\n",
859 | " binary_i = (bin(i)[2:]).zfill(no_of_vars)\n",
860 | " \n",
861 | " # Convert a binary string to a vector of binary data\n",
862 | " # \"1111\" --> ['1', '1', '1', '1'] --> [1, 1, 1, 1]\n",
863 | " input_vector = map(int, list(binary_i))\n",
864 | " \n",
865 | " if self.evaluate(variable_values = input_vector):\n",
866 | " print input_vector\n",
867 | " return True\n",
868 | " \n",
869 | " print \"No satisfiable combination\"\n",
870 | " return False\n",
871 | "\n",
872 | "ExprTree.Satisfiability = _Satisfiability\n",
873 | "\n",
874 | "# Sample Output\n",
875 | "exp.Satisfiability()"
876 | ],
877 | "language": "python",
878 | "metadata": {},
879 | "outputs": [
880 | {
881 | "output_type": "stream",
882 | "stream": "stdout",
883 | "text": [
884 | "[0, 0, 0]\n"
885 | ]
886 | },
887 | {
888 | "metadata": {},
889 | "output_type": "pyout",
890 | "prompt_number": 16,
891 | "text": [
892 | "True"
893 | ]
894 | }
895 | ],
896 | "prompt_number": 16
897 | },
898 | {
899 | "cell_type": "code",
900 | "collapsed": false,
901 | "input": [
902 | "# Class Definition for ThreadedNode\n",
903 | "class ThreadedNode(TreeNode):\n",
904 | " def __init__(self):\n",
905 | " super(ThreadedNode, self).__init__()\n",
906 | " # Defining 2 new fields rightThread and leftThread\n",
907 | " self.rightThread = self.leftThread = None\n",
908 | " \n",
909 | "# Class Definition for ThreadedTree\n",
910 | "class ThreadedTree(ThreadedNode):\n",
911 | " def __init__(self):\n",
912 | " super(ThreadedTree, self).__init__()"
913 | ],
914 | "language": "python",
915 | "metadata": {},
916 | "outputs": [],
917 | "prompt_number": 17
918 | },
919 | {
920 | "cell_type": "heading",
921 | "level": 2,
922 | "metadata": {},
923 | "source": [
924 | "PROGRAM 5.13, PAGE 277"
925 | ]
926 | },
927 | {
928 | "cell_type": "code",
929 | "collapsed": false,
930 | "input": [
931 | "# Finding the inorder successor in a threaded binary tree\n",
932 | "def _ThreadedInorderIterator(self):\n",
933 | " \"\"\" Return the inorder successor of currentNode in a threaded binary tree \"\"\"\n",
934 | " temp = currentnode.rightChild\n",
935 | " \n",
936 | " if currentNode.rightThread is None:\n",
937 | " while temp.leftThread is None:\n",
938 | " temp = temp.leftChild\n",
939 | " \n",
940 | " currentNode = temp\n",
941 | " \n",
942 | " if currentNode == root:\n",
943 | " return None\n",
944 | " else:\n",
945 | " return currentNode.data\n",
946 | " \n",
947 | "ThreadedTree.ThreadedInorderIterator = _ThreadedInorderIterator"
948 | ],
949 | "language": "python",
950 | "metadata": {},
951 | "outputs": [],
952 | "prompt_number": 18
953 | },
954 | {
955 | "cell_type": "heading",
956 | "level": 2,
957 | "metadata": {},
958 | "source": [
959 | "PROGRAM 5.14, PAGE 279"
960 | ]
961 | },
962 | {
963 | "cell_type": "code",
964 | "collapsed": false,
965 | "input": [
966 | "# Inserting r as the right child of s\n",
967 | "\n",
968 | "def _InsertRight(s, r):\n",
969 | " \"\"\" Insert r as the right child of s \"\"\"\n",
970 | " r.rightChild = s.rightChild\n",
971 | " r.rightThread = s.rightThread\n",
972 | " r.leftChild = s\n",
973 | " r.leftThread = True # Left child is a thread\n",
974 | " s.rightChild = r\n",
975 | " s.rightThread = False\n",
976 | " if r.rightThread is None:\n",
977 | " temp = self.InorderSucc(r)\n",
978 | " # Returns the inorder successor of r\n",
979 | " temp.leftChild = r "
980 | ],
981 | "language": "python",
982 | "metadata": {},
983 | "outputs": [],
984 | "prompt_number": 19
985 | },
986 | {
987 | "cell_type": "heading",
988 | "level": 2,
989 | "metadata": {},
990 | "source": [
991 | "ADT 5.2, PAGE 280"
992 | ]
993 | },
994 | {
995 | "cell_type": "code",
996 | "collapsed": false,
997 | "input": [
998 | "# A max priority queue\n",
999 | "\n",
1000 | "# This defines the skeletal structure of a Max Priority Queue Class\n",
1001 | "\n",
1002 | "class MaxPQ(object):\n",
1003 | " def __init__(self):\n",
1004 | " pass\n",
1005 | " def IsEmpty(self):\n",
1006 | " # Returns true if the PQ is empty\n",
1007 | " pass\n",
1008 | " def Top(self):\n",
1009 | " # Returns the reference to the max element ( Max priority queue top element )\n",
1010 | " pass\n",
1011 | " def Push(self):\n",
1012 | " # Add an element to the priority queue\n",
1013 | " pass\n",
1014 | " def Pop(self):\n",
1015 | " # delete element with max priority\n",
1016 | " pass\n",
1017 | " def __del__(self):\n",
1018 | " # Distructor to delete reference to names that are no longer required\n",
1019 | " pass"
1020 | ],
1021 | "language": "python",
1022 | "metadata": {},
1023 | "outputs": [],
1024 | "prompt_number": 20
1025 | },
1026 | {
1027 | "cell_type": "heading",
1028 | "level": 2,
1029 | "metadata": {},
1030 | "source": [
1031 | "PROGRAM 5.15, PAGE 282"
1032 | ]
1033 | },
1034 | {
1035 | "cell_type": "code",
1036 | "collapsed": false,
1037 | "input": [
1038 | "# Max heap constructor\n",
1039 | "class MaxHeap(object):\n",
1040 | " def __init__(self, theCapacity = 10):\n",
1041 | " if theCapacity < 1:\n",
1042 | " raise Exception(\"Capacity must be >= 1\")\n",
1043 | "\n",
1044 | " self.capacity = theCapacity\n",
1045 | " self.heapSize = 0\n",
1046 | " self.heap = dict()\n",
1047 | " \n",
1048 | " def IsEmpty(self):\n",
1049 | " return ( self.heapSize == 0 ) or ( len(self.heap) == 0 )"
1050 | ],
1051 | "language": "python",
1052 | "metadata": {},
1053 | "outputs": [],
1054 | "prompt_number": 21
1055 | },
1056 | {
1057 | "cell_type": "heading",
1058 | "level": 1,
1059 | "metadata": {},
1060 | "source": [
1061 | "PROGRAM 5.16, PAGE 285"
1062 | ]
1063 | },
1064 | {
1065 | "cell_type": "code",
1066 | "collapsed": false,
1067 | "input": [
1068 | "# Insertion into a max heap\n",
1069 | "\n",
1070 | "def _Push(self, e):\n",
1071 | " \"\"\" Insert e into max heap \"\"\"\n",
1072 | " \n",
1073 | " if self.heapSize == self.capacity:\n",
1074 | " # Double the capacity\n",
1075 | " self.capacity *= 2\n",
1076 | " \n",
1077 | " self.heapSize += 1\n",
1078 | " currentNode = self.heapSize\n",
1079 | " \n",
1080 | " while ( currentNode != 1 ) and ( self.heap[currentNode/2] < e) :\n",
1081 | " # Bubble up\n",
1082 | " self.heap[currentNode] = self.heap[currentNode / 2] # Move parent down\n",
1083 | " currentNode /= 2\n",
1084 | " \n",
1085 | " self.heap[currentNode] = e\n",
1086 | " \n",
1087 | "MaxHeap.Push = _Push"
1088 | ],
1089 | "language": "python",
1090 | "metadata": {},
1091 | "outputs": [],
1092 | "prompt_number": 22
1093 | },
1094 | {
1095 | "cell_type": "heading",
1096 | "level": 2,
1097 | "metadata": {},
1098 | "source": [
1099 | "PROGRAM 5.17, PAGE 286"
1100 | ]
1101 | },
1102 | {
1103 | "cell_type": "code",
1104 | "collapsed": false,
1105 | "input": [
1106 | "# Deletion from a Max heap\n",
1107 | "\n",
1108 | "def _Pop(self):\n",
1109 | " # Delete max element\n",
1110 | " if self.IsEmpty():\n",
1111 | " raise Exception(\"Heap is empty. Cannot delete\")\n",
1112 | " \n",
1113 | " # Remove last element from heap\n",
1114 | " lastE = self.heap[self.heapSize]\n",
1115 | " self.heapSize -= 1\n",
1116 | " \n",
1117 | " # Tricle down\n",
1118 | " currentNode = 1 # Root\n",
1119 | " child = 2 # A child of the CurrentNode\n",
1120 | " while child <= self.heapSize:\n",
1121 | " # set child to larger child of currentNode\n",
1122 | " if child < self.heapSize and self.heap[child] < self.heap[child+1]:\n",
1123 | " child += 1\n",
1124 | " \n",
1125 | " if lastE >= self.heap[child]:\n",
1126 | " # If lastE can be put in the currentNode, break out of the loop\n",
1127 | " break\n",
1128 | " \n",
1129 | " # If not\n",
1130 | " \n",
1131 | " self.heap[currentNode] = self.heap[child]\n",
1132 | " currentNode = child # move child up\n",
1133 | " child *= 2 # Move down a level\n",
1134 | " \n",
1135 | " self.heap[currentNode] = lastE\n",
1136 | " \n",
1137 | "MaxHeap.Pop = _Pop"
1138 | ],
1139 | "language": "python",
1140 | "metadata": {},
1141 | "outputs": [],
1142 | "prompt_number": 23
1143 | },
1144 | {
1145 | "cell_type": "code",
1146 | "collapsed": false,
1147 | "input": [
1148 | "# Sample Output - NOT IN TEXTBOOK\n",
1149 | "# Refer Figure 5.26, Page 284\n",
1150 | "\n",
1151 | "a = MaxHeap()\n",
1152 | "\n",
1153 | "a.Push(20)\n",
1154 | "a.Push(15)\n",
1155 | "a.Push(10)\n",
1156 | "a.Push(14)\n",
1157 | "a.Push(2)\n",
1158 | "a.Push(5)\n",
1159 | "a.Pop() # 20 will be removed.\n",
1160 | "\n",
1161 | "print a.heap"
1162 | ],
1163 | "language": "python",
1164 | "metadata": {},
1165 | "outputs": [
1166 | {
1167 | "output_type": "stream",
1168 | "stream": "stdout",
1169 | "text": [
1170 | "{1: 15, 2: 14, 3: 10, 4: 2, 5: 2, 6: 5}\n"
1171 | ]
1172 | }
1173 | ],
1174 | "prompt_number": 24
1175 | },
1176 | {
1177 | "cell_type": "heading",
1178 | "level": 2,
1179 | "metadata": {},
1180 | "source": [
1181 | "ADT 5.3, PAGE 287"
1182 | ]
1183 | },
1184 | {
1185 | "cell_type": "code",
1186 | "collapsed": false,
1187 | "input": [
1188 | "# A dictionary\n",
1189 | "\n",
1190 | "class Dictionary:\n",
1191 | " def __init__(self):\n",
1192 | " pass\n",
1193 | " \n",
1194 | " def IsEmpty(self):\n",
1195 | " pass\n",
1196 | " \n",
1197 | " def Insert(self, pair):\n",
1198 | " pass\n",
1199 | " \n",
1200 | " def Delete(self, pair):\n",
1201 | " pass"
1202 | ],
1203 | "language": "python",
1204 | "metadata": {},
1205 | "outputs": [],
1206 | "prompt_number": 25
1207 | },
1208 | {
1209 | "cell_type": "heading",
1210 | "level": 2,
1211 | "metadata": {},
1212 | "source": [
1213 | "PAIR / BST CLASS DECLARATIONS"
1214 | ]
1215 | },
1216 | {
1217 | "cell_type": "code",
1218 | "collapsed": false,
1219 | "input": [
1220 | "# Pair Class - Key / Value pair ( data ) of a bst node\n",
1221 | "class Pair(object):\n",
1222 | " def __init__(self, key = None, value = None):\n",
1223 | " self.first = key # key\n",
1224 | " self.second = value # value\n",
1225 | " # To print the key value pairs when Pair is \"print\"-ed\n",
1226 | " def __repr__(self):\n",
1227 | " return \"( \" + str(self.first) + \" , \" + str(self.second) + \" )\"\n",
1228 | " __str__ = __repr__\n",
1229 | " \n",
1230 | "# Abstract Class for the Binary Search Tree deriving from the Tree Class\n",
1231 | "class BST(Tree):\n",
1232 | " def __init__(self, *inputs):\n",
1233 | " Tree.__init__(self, *inputs)"
1234 | ],
1235 | "language": "python",
1236 | "metadata": {},
1237 | "outputs": [],
1238 | "prompt_number": 26
1239 | },
1240 | {
1241 | "cell_type": "heading",
1242 | "level": 2,
1243 | "metadata": {},
1244 | "source": [
1245 | "PROGRAM 5.18, PAGE 289"
1246 | ]
1247 | },
1248 | {
1249 | "cell_type": "code",
1250 | "collapsed": false,
1251 | "input": [
1252 | "# Recursive search of a binary search tree\n",
1253 | "def _Get(self, frm, k):\n",
1254 | " # Search binary search tree for a pair with key k\n",
1255 | " # If such a pair is found, return a pointer to this pair, other wise return None\n",
1256 | " \n",
1257 | " # Driver\n",
1258 | " if ( k is None ):\n",
1259 | " k = frm\n",
1260 | " return self.Get(self.root, k)\n",
1261 | " \n",
1262 | " # Workhorse\n",
1263 | " if ( frm is None ):\n",
1264 | " return None\n",
1265 | " \n",
1266 | " \n",
1267 | " if ( k < frm.data.first ): # TreeNode will have Pair object as its data attribute\n",
1268 | " return self.Get(frm.leftChild, k)\n",
1269 | " \n",
1270 | " elif ( k > frm.data.first ):\n",
1271 | " return self.Get(frm.rightChild, k)\n",
1272 | " \n",
1273 | " else:\n",
1274 | " return frm.data # Returns the pair object which is the frm - TreeNode's data attribute.\n",
1275 | " \n",
1276 | "BST.Get = _Get"
1277 | ],
1278 | "language": "python",
1279 | "metadata": {},
1280 | "outputs": [],
1281 | "prompt_number": 27
1282 | },
1283 | {
1284 | "cell_type": "heading",
1285 | "level": 2,
1286 | "metadata": {},
1287 | "source": [
1288 | "PROGRAM 5.19, PAGE 290"
1289 | ]
1290 | },
1291 | {
1292 | "cell_type": "code",
1293 | "collapsed": false,
1294 | "input": [
1295 | "# Iterative search of a binary search tree.\n",
1296 | "\n",
1297 | "def _Get_Iterative(self, k):\n",
1298 | " while ( currentNode is not None ):\n",
1299 | " if ( k < currentNode.data.first ): \n",
1300 | " #NOTE: currentNode is a TreeNode object, data is a Pair object and first is the key of that Pair object.\n",
1301 | " currentNode = currentNode.leftChild\n",
1302 | " elif ( k > currentNode.data.first ):\n",
1303 | " currentNode = currentNode.rightChild\n",
1304 | " else:\n",
1305 | " return currentNode.data\n",
1306 | " \n",
1307 | " # If there is no matching pair ( while loop terminates without returning )\n",
1308 | " return None "
1309 | ],
1310 | "language": "python",
1311 | "metadata": {},
1312 | "outputs": [],
1313 | "prompt_number": 28
1314 | },
1315 | {
1316 | "cell_type": "heading",
1317 | "level": 2,
1318 | "metadata": {},
1319 | "source": [
1320 | "PROGRAM 5.20, PAGE 290"
1321 | ]
1322 | },
1323 | {
1324 | "cell_type": "code",
1325 | "collapsed": false,
1326 | "input": [
1327 | "# Searching the Binary Search Tree by rank\n",
1328 | "def _RankGet(self, r):\n",
1329 | " # Search the binary search tree for the rth smallest pair\n",
1330 | " while ( currentNode is not None ):\n",
1331 | " if ( r < currentNode.leftSize ):\n",
1332 | " currentNode = currentNode.leftChild\n",
1333 | " \n",
1334 | " elif ( r > currentNode.leftSize ):\n",
1335 | " r -= currentNode.leftSize\n",
1336 | " currentNode = currentNode.rightChild\n",
1337 | " \n",
1338 | " else:\n",
1339 | " return currentNode.data\n",
1340 | " \n",
1341 | "BST.RankGet = _RankGet"
1342 | ],
1343 | "language": "python",
1344 | "metadata": {},
1345 | "outputs": [],
1346 | "prompt_number": 29
1347 | },
1348 | {
1349 | "cell_type": "heading",
1350 | "level": 2,
1351 | "metadata": {},
1352 | "source": [
1353 | "PROGRAM 5.21, PAGE 292"
1354 | ]
1355 | },
1356 | {
1357 | "cell_type": "code",
1358 | "collapsed": false,
1359 | "input": [
1360 | "# Insertion into a binary search tree\n",
1361 | "def _Insert(self, thePair):\n",
1362 | " # Insert thePair\n",
1363 | " # Search for thePair.first, pp is the parent of p\n",
1364 | " p = self.root\n",
1365 | " pp = None\n",
1366 | " \n",
1367 | " while (p is not None):\n",
1368 | " pp = p\n",
1369 | " if ( thePair.first < p.data.first ):\n",
1370 | " p = p.leftChild\n",
1371 | " elif ( thePair.first > p.data.first ):\n",
1372 | " p = p.rightChild\n",
1373 | " else:\n",
1374 | " # Duplicate, Update associated element\n",
1375 | " p.data.second = thePair.second\n",
1376 | " return None\n",
1377 | " \n",
1378 | " # Perform the insertion\n",
1379 | " p = TreeNode(data = thePair)\n",
1380 | " if ( self.root is not None ):\n",
1381 | " if ( thePair.first < pp.data.first ):\n",
1382 | " pp.leftChild = p\n",
1383 | " else:\n",
1384 | " pp.rightChild = p\n",
1385 | " else:\n",
1386 | " root = p\n",
1387 | " \n",
1388 | "BST.Insert = _Insert"
1389 | ],
1390 | "language": "python",
1391 | "metadata": {},
1392 | "outputs": [],
1393 | "prompt_number": 30
1394 | },
1395 | {
1396 | "cell_type": "heading",
1397 | "level": 2,
1398 | "metadata": {},
1399 | "source": [
1400 | "PROGRAM 5.22, PAGE 295"
1401 | ]
1402 | },
1403 | {
1404 | "cell_type": "code",
1405 | "collapsed": false,
1406 | "input": [
1407 | "# Splitting a Binary Search Tree\n",
1408 | "def _Split(self, k, small, mid, big):\n",
1409 | " \"\"\"\n",
1410 | " The Binary Search Tree is split into 3 subtrees - small, mid and big\n",
1411 | " small is the BST with all the keys less than k\n",
1412 | " big is the BST with all the keys greater than k\n",
1413 | " mid is the Pair object with key equal to k ( if any such pair exists in BST )\n",
1414 | " \"\"\"\n",
1415 | " # Split the Binary Search Tree with respect to the key k\n",
1416 | " \n",
1417 | " # Empty tree :\n",
1418 | " if ( self.root is None ):\n",
1419 | " small.root = big.root = None\n",
1420 | " return\n",
1421 | " \n",
1422 | " sHead = TreeNode()\n",
1423 | " s = sHead\n",
1424 | " bHead = TreeNode()\n",
1425 | " b = bHead\n",
1426 | " currentNode = self.root\n",
1427 | " \n",
1428 | " while ( currentNode is not None ):\n",
1429 | " if ( k < currentNode.data.first ):\n",
1430 | " # Add it to the big\n",
1431 | " b.leftChild = currentNode\n",
1432 | " b = currentNode\n",
1433 | " currentNode = currentNode.leftChild\n",
1434 | " elif ( k > currentNode.data.first ):\n",
1435 | " # Add it to the small\n",
1436 | " s.rightChild = currentNode\n",
1437 | " s = currentNode\n",
1438 | " currentNode = currentNode.rightChild\n",
1439 | " else:\n",
1440 | " # Split at the currentNode\n",
1441 | " s.rightChild = currentNode.leftChild\n",
1442 | " b.leftChild = currentNode.rightChild\n",
1443 | " small.root = sHead.rightChild\n",
1444 | " del sHead\n",
1445 | " big.root = bHead.leftChild\n",
1446 | " del bHead\n",
1447 | " mid.first = currentNode.data.first; mid.second = currentNode.data.second\n",
1448 | " del currentNode\n",
1449 | " return\n",
1450 | " \n",
1451 | " # No pair with key k\n",
1452 | " s.rightChild = b.leftChild = None\n",
1453 | " small.root = sHead.rightChild\n",
1454 | " del sHead\n",
1455 | " big.root = bHead.leftChild\n",
1456 | " del bHead\n",
1457 | " mid = None\n",
1458 | " return\n",
1459 | " \n",
1460 | "BST.Split = _Split"
1461 | ],
1462 | "language": "python",
1463 | "metadata": {},
1464 | "outputs": [],
1465 | "prompt_number": 31
1466 | },
1467 | {
1468 | "cell_type": "code",
1469 | "collapsed": false,
1470 | "input": [
1471 | "# Example problem on BST ( NOT IN TEXTBOOK )\n",
1472 | "\n",
1473 | "node_2 = TreeNode(None, Pair(2,\"Two\"), None) \n",
1474 | "# Here 2 is the key and \"Two\" is the value of the Pair object\n",
1475 | "# Which forms the data of the TreeNode object\n",
1476 | "node_5 = TreeNode(node_2, Pair(5,\"Two\"), None)\n",
1477 | "\n",
1478 | "node_80 = TreeNode(None, Pair(80, \"Eighty\"), None)\n",
1479 | "node_40 = TreeNode(None, Pair(40, \"Forty\"), node_80)\n",
1480 | "\n",
1481 | "# Root node\n",
1482 | "node_30 = TreeNode(node_5, Pair(30, \"Thirty\"), node_40)\n",
1483 | "\n",
1484 | "bst_1 = BST(node_30)\n",
1485 | "\n",
1486 | "print \"The Inorder traversal of the bst before Insert : \"\n",
1487 | "bst_1.Inorder()\n",
1488 | "\n",
1489 | "bst_1.Insert(Pair(35, \"Thirty Five\"))\n",
1490 | "\n",
1491 | "print \"\\n\\nThe Inorder traversal of the bst after Inserting : \"\n",
1492 | "bst_1.Inorder()"
1493 | ],
1494 | "language": "python",
1495 | "metadata": {},
1496 | "outputs": [
1497 | {
1498 | "output_type": "stream",
1499 | "stream": "stdout",
1500 | "text": [
1501 | "The Inorder traversal of the bst before Insert : \n",
1502 | "( 2 , Two ) ( 5 , Two ) ( 30 , Thirty ) ( 40 , Forty ) ( 80 , Eighty ) \n",
1503 | "\n",
1504 | "The Inorder traversal of the bst after Inserting : \n",
1505 | "( 2 , Two ) ( 5 , Two ) ( 30 , Thirty ) ( 35 , Thirty Five ) ( 40 , Forty ) ( 80 , Eighty )\n"
1506 | ]
1507 | }
1508 | ],
1509 | "prompt_number": 32
1510 | },
1511 | {
1512 | "cell_type": "code",
1513 | "collapsed": false,
1514 | "input": [
1515 | "# Ilustration of splitting the tree :\n",
1516 | "print \"Splitting the tree with key k = 30\"\n",
1517 | "print \"--------------------------------------\"\n",
1518 | "small_subtree = BST()\n",
1519 | "mid_pair = Pair()\n",
1520 | "big_subtree = BST()\n",
1521 | "bst_1.Split(30, small_subtree, mid_pair, big_subtree )\n",
1522 | "\n",
1523 | "print \"\\nThe small_subtree ( Inorder traversal ) is : \"\n",
1524 | "small_subtree.Inorder()\n",
1525 | "\n",
1526 | "print \"\\n\\nThe mid_pair ( key, value ) is : \"\n",
1527 | "print mid_pair\n",
1528 | "\n",
1529 | "print \"\\n\\nThe big_subtree ( Inorder traversal ) is : \"\n",
1530 | "big_subtree.Inorder()"
1531 | ],
1532 | "language": "python",
1533 | "metadata": {},
1534 | "outputs": [
1535 | {
1536 | "output_type": "stream",
1537 | "stream": "stdout",
1538 | "text": [
1539 | "Splitting the tree with key k = 30\n",
1540 | "--------------------------------------\n",
1541 | "\n",
1542 | "The small_subtree ( Inorder traversal ) is : \n",
1543 | "( 2 , Two ) ( 5 , Two ) \n",
1544 | "\n",
1545 | "The mid_pair ( key, value ) is : \n",
1546 | "( 30 , Thirty )\n",
1547 | "\n",
1548 | "\n",
1549 | "The big_subtree ( Inorder traversal ) is : \n",
1550 | "( 35 , Thirty Five ) ( 40 , Forty ) ( 80 , Eighty )\n"
1551 | ]
1552 | }
1553 | ],
1554 | "prompt_number": 33
1555 | },
1556 | {
1557 | "cell_type": "heading",
1558 | "level": 2,
1559 | "metadata": {},
1560 | "source": [
1561 | "PROGRAM 5.23, PAGE 307"
1562 | ]
1563 | },
1564 | {
1565 | "cell_type": "code",
1566 | "collapsed": false,
1567 | "input": [
1568 | "# Class definition and constructor for sets\n",
1569 | "class Sets(object):\n",
1570 | " def __init__(self, numberOfElements):\n",
1571 | " if ( numberOfElements < 2 ):\n",
1572 | " raise Exception(\"Must have at least 2 elements\")\n",
1573 | " self.n = numberOfElements\n",
1574 | " self.parent = [-1]*n"
1575 | ],
1576 | "language": "python",
1577 | "metadata": {},
1578 | "outputs": [],
1579 | "prompt_number": 34
1580 | },
1581 | {
1582 | "cell_type": "heading",
1583 | "level": 2,
1584 | "metadata": {},
1585 | "source": [
1586 | "PROGRAM 5.24, PAGE 308"
1587 | ]
1588 | },
1589 | {
1590 | "cell_type": "code",
1591 | "collapsed": false,
1592 | "input": [
1593 | "# Simple functions for union and find\n",
1594 | "def _SimpleUnion(self, i, j):\n",
1595 | " # Replace the disjoint sets with roots i and j, i != j with their union\n",
1596 | " self.parent[i] = j\n",
1597 | "\n",
1598 | "Sets.SimpleUnion = _SimpleUnion\n",
1599 | " \n",
1600 | "def _SimpleFind(self, i):\n",
1601 | " while ( parent[i] >= 0 ):\n",
1602 | " i = parent[i]\n",
1603 | " return\n",
1604 | "\n",
1605 | "Sets.SimpleFind = _SimpleFind"
1606 | ],
1607 | "language": "python",
1608 | "metadata": {},
1609 | "outputs": [],
1610 | "prompt_number": 35
1611 | },
1612 | {
1613 | "cell_type": "heading",
1614 | "level": 2,
1615 | "metadata": {},
1616 | "source": [
1617 | "PROGRAM 5.26, PAGE 313"
1618 | ]
1619 | },
1620 | {
1621 | "cell_type": "code",
1622 | "collapsed": false,
1623 | "input": [
1624 | "# Collapsing Rule\n",
1625 | "def _CollapsingFind(self, i):\n",
1626 | " # Find the root of the tree containing element i,\n",
1627 | " # Use the collapsing root rule to calculate all nodes from i to the root\n",
1628 | " r = i\n",
1629 | " while sellf.parent[r] >= 0:\n",
1630 | " r = parent[r]\n",
1631 | " \n",
1632 | " while i != r:\n",
1633 | " # Collapse\n",
1634 | " s = parent[i]\n",
1635 | " parent[i] = r\n",
1636 | " i = s\n",
1637 | " \n",
1638 | " return r"
1639 | ],
1640 | "language": "python",
1641 | "metadata": {},
1642 | "outputs": [],
1643 | "prompt_number": 36
1644 | },
1645 | {
1646 | "cell_type": "code",
1647 | "collapsed": false,
1648 | "input": [],
1649 | "language": "python",
1650 | "metadata": {},
1651 | "outputs": []
1652 | }
1653 | ],
1654 | "metadata": {}
1655 | }
1656 | ]
1657 | }
--------------------------------------------------------------------------------
/Chapter 2 - Arrays.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": {
3 | "name": "",
4 | "signature": "sha256:0cdf200479cb45c126f9773bd9d617dc0324ef8c22b43c0364a14404bd5eb8cb"
5 | },
6 | "nbformat": 3,
7 | "nbformat_minor": 0,
8 | "worksheets": [
9 | {
10 | "cells": [
11 | {
12 | "cell_type": "code",
13 | "collapsed": false,
14 | "input": [
15 | "# Imports\n",
16 | "import numpy\n",
17 | "# For random string generation\n",
18 | "from string import ascii_letters\n",
19 | "from random import choice\n",
20 | "from time import time # For analysis of the running time\n",
21 | "import matplotlib.pyplot as plt # to plot the run time analysis"
22 | ],
23 | "language": "python",
24 | "metadata": {},
25 | "outputs": [],
26 | "prompt_number": 1
27 | },
28 | {
29 | "cell_type": "heading",
30 | "level": 1,
31 | "metadata": {},
32 | "source": [
33 | "CHAPTER 2 - ARRAYS"
34 | ]
35 | },
36 | {
37 | "cell_type": "heading",
38 | "level": 2,
39 | "metadata": {},
40 | "source": [
41 | "PROGRAM 2.1, PAGE 75"
42 | ]
43 | },
44 | {
45 | "cell_type": "code",
46 | "collapsed": false,
47 | "input": [
48 | "# Definition of Class Rectangle\n",
49 | "\n",
50 | "class Rectangle:\n",
51 | " def __init__(self,x=None,y=None,h=None,w=None):\n",
52 | " '''Constructor of Rectangle'''\n",
53 | " pass\n",
54 | " def __del__(self):\n",
55 | " '''Destructor of Rectangle'''\n",
56 | " pass\n",
57 | " def get_height(self):\n",
58 | " '''returns Height of rectangle'''\n",
59 | " pass\n",
60 | " def get_width(self):\n",
61 | " '''Returns Width of rectangle'''\n",
62 | " pass\n",
63 | " _x_low = None\n",
64 | " _y_low = None\n",
65 | " _height = None\n",
66 | " _width = None\n",
67 | " # In python attributes starting with an underscore are conventionaly understood to be private attributes."
68 | ],
69 | "language": "python",
70 | "metadata": {},
71 | "outputs": [],
72 | "prompt_number": 2
73 | },
74 | {
75 | "cell_type": "heading",
76 | "level": 2,
77 | "metadata": {},
78 | "source": [
79 | "PROGRAM 2.2, PAGE 76"
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "collapsed": false,
85 | "input": [
86 | "# Implementations of Operations on Rectangle\n",
87 | "def get_height(self):\n",
88 | " return self._height\n",
89 | "def get_width(self):\n",
90 | " return self._width\n",
91 | "\n",
92 | "Rectangle.get_height = get_height\n",
93 | "Rectangle.get_width = get_width\n",
94 | "\n",
95 | "# This is the closest C++ equivalent to defining class methods outside the class. [ using class::member() {} ]\n",
96 | "\n",
97 | "# Though THIS IS NOT AN ACCEPTED PRACTICE IN PYTHON. This would be an workaround to define \n",
98 | "# the member functions outside of the class definition as and when the functionality of the method in defined\n",
99 | "# in the text"
100 | ],
101 | "language": "python",
102 | "metadata": {},
103 | "outputs": [],
104 | "prompt_number": 3
105 | },
106 | {
107 | "cell_type": "heading",
108 | "level": 2,
109 | "metadata": {},
110 | "source": [
111 | "PROGRAM 2.4, PAGE 78"
112 | ]
113 | },
114 | {
115 | "cell_type": "code",
116 | "collapsed": false,
117 | "input": [
118 | "# Definition Of Constructor For Rectangle\n",
119 | "\n",
120 | "def constructor_for_Rectangle_class(self,x=None,y=None,h=None,w=None):\n",
121 | " '''Constructor for class Rectangle'''\n",
122 | " self._x_low = x\n",
123 | " self._y_low = x\n",
124 | " self._height = h\n",
125 | " self._width = w\n",
126 | "\n",
127 | "# __init__ method is invoked in python when a member of the class is instantiated\n",
128 | "Rectangle.__init__ = constructor_for_Rectangle_class"
129 | ],
130 | "language": "python",
131 | "metadata": {},
132 | "outputs": [],
133 | "prompt_number": 4
134 | },
135 | {
136 | "cell_type": "heading",
137 | "level": 2,
138 | "metadata": {},
139 | "source": [
140 | "PROGRAM 2.3, PAGE 77"
141 | ]
142 | },
143 | {
144 | "cell_type": "code",
145 | "collapsed": false,
146 | "input": [
147 | "r = Rectangle(1,2,5,6)\n",
148 | "s = Rectangle(1,2,8,9)\n",
149 | "\n",
150 | "t = s\n",
151 | "\n",
152 | "if ( r.get_height() * r.get_width() ) > ( t.get_height() * t.get_width() ):\n",
153 | " print 'r',\n",
154 | "else:\n",
155 | " print 's',\n",
156 | "\n",
157 | "print 'has the greater area'"
158 | ],
159 | "language": "python",
160 | "metadata": {},
161 | "outputs": [
162 | {
163 | "output_type": "stream",
164 | "stream": "stdout",
165 | "text": [
166 | "s has the greater area\n"
167 | ]
168 | }
169 | ],
170 | "prompt_number": 5
171 | },
172 | {
173 | "cell_type": "heading",
174 | "level": 2,
175 | "metadata": {},
176 | "source": [
177 | "PROGRAM 2.6, PAGE 80"
178 | ]
179 | },
180 | {
181 | "cell_type": "code",
182 | "collapsed": false,
183 | "input": [
184 | "s._x_low"
185 | ],
186 | "language": "python",
187 | "metadata": {},
188 | "outputs": [
189 | {
190 | "metadata": {},
191 | "output_type": "pyout",
192 | "prompt_number": 6,
193 | "text": [
194 | "1"
195 | ]
196 | }
197 | ],
198 | "prompt_number": 6
199 | },
200 | {
201 | "cell_type": "code",
202 | "collapsed": false,
203 | "input": [
204 | "# Overloading the equality check operator\n",
205 | "\n",
206 | "# Operator == is specified as a function __eq__(op1,op2)\n",
207 | "\n",
208 | "def __eq__(self,rhs):\n",
209 | " if (self._x_low == rhs._x_low) and (self._y_low == rhs._y_low) and ( self._height == rhs._height ) and ( self._width == rhs._width ) :\n",
210 | " return True\n",
211 | " else:\n",
212 | " return False\n",
213 | " \n",
214 | "# Overriding the default behaviour of == by defining a __eq__ memeber function\n",
215 | "Rectangle.__eq__ = __eq__\n",
216 | "\n",
217 | "# SAMPLE O/P\n",
218 | "print r == s"
219 | ],
220 | "language": "python",
221 | "metadata": {},
222 | "outputs": [
223 | {
224 | "output_type": "stream",
225 | "stream": "stdout",
226 | "text": [
227 | "False\n"
228 | ]
229 | }
230 | ],
231 | "prompt_number": 7
232 | },
233 | {
234 | "cell_type": "heading",
235 | "level": 2,
236 | "metadata": {},
237 | "source": [
238 | "PROGRAM 2.7, PAGE 80"
239 | ]
240 | },
241 | {
242 | "cell_type": "code",
243 | "collapsed": false,
244 | "input": [
245 | "# Overloading the right shift operator <<\n",
246 | "import operator, sys, io, IPython\n",
247 | "\n",
248 | "\n",
249 | "class MyStdout(IPython.kernel.zmq.iostream.OutStream):\n",
250 | " \"\"\"\n",
251 | " MyStdout class inherits from the OutStream of IPython kernel.\n",
252 | " This is done to simulate the C++ OutStream \"cout\"\n",
253 | " \"\"\" \n",
254 | " def __init__(self):\n",
255 | " self.__dict__ = sys.stdout.__dict__.copy()\n",
256 | " # All elements of sys.stdout are copied to the MyStdout [inherited class]\n",
257 | "\n",
258 | " def __lshift__(self,rect_instance):\n",
259 | " # lshift ( << ) operator is overloaded to allow printing of Rectangle\n",
260 | " \n",
261 | " if isinstance(rect_instance, Rectangle): \n",
262 | " self.write('Position is:'+str(rect_instance._x_low)+' '+str(rect_instance._y_low))\n",
263 | " self.write('\\nHeight is: '+str(rect_instance._height))\n",
264 | " self.write('\\nWidth is: '+str(rect_instance._width))\n",
265 | " else:\n",
266 | " self.write(rect_instance)\n",
267 | " \n",
268 | "# A new MyStdout Class inheriting the elements of stdout [OutStream Object Class] is created with the \n",
269 | "# overloaded operator function __lshift__. \n",
270 | "\n",
271 | "sys.stdout = MyStdout()\n",
272 | "\n",
273 | "#an instance of MyStdout is then assigned to sys.stdout\n",
274 | "\n",
275 | "#Operator << is specified as a function __lshift__() [Equivalent to c++ style 'operator<<'"
276 | ],
277 | "language": "python",
278 | "metadata": {},
279 | "outputs": [],
280 | "prompt_number": 8
281 | },
282 | {
283 | "cell_type": "code",
284 | "collapsed": false,
285 | "input": [
286 | "# C++ Style I/O using << operator overloading in python !\n",
287 | "\n",
288 | "cout = sys.stdout\n",
289 | "endl = '\\n'\n",
290 | "\n",
291 | "cout<= 0 else 0"
368 | ],
369 | "language": "python",
370 | "metadata": {},
371 | "outputs": [],
372 | "prompt_number": 10
373 | },
374 | {
375 | "cell_type": "heading",
376 | "level": 2,
377 | "metadata": {},
378 | "source": [
379 | "ADT 2.2, PAGE 85"
380 | ]
381 | },
382 | {
383 | "cell_type": "code",
384 | "collapsed": false,
385 | "input": [
386 | "# Abstract Data Type GeneralArray\n",
387 | "default_value = 0\n",
388 | "\n",
389 | "class GeneralArray:\n",
390 | " def __init__(self, j, List, init_value = default_value):\n",
391 | " pass\n",
392 | " def retrieve(self,index):\n",
393 | " pass\n",
394 | " def store(self,index,x):\n",
395 | " x = float(x)\n",
396 | " pass "
397 | ],
398 | "language": "python",
399 | "metadata": {},
400 | "outputs": [],
401 | "prompt_number": 11
402 | },
403 | {
404 | "cell_type": "heading",
405 | "level": 2,
406 | "metadata": {},
407 | "source": [
408 | "ADT 2.3, PAGE 88 & PROGRAM 2.8, PAGE 91 & PROGRAM 2.9, PAGE 92"
409 | ]
410 | },
411 | {
412 | "cell_type": "code",
413 | "collapsed": false,
414 | "input": [
415 | "# Adding two Polynomials\n",
416 | "\n",
417 | "# Declaring and Defining Prerequisites Term and Polynomial\n",
418 | "\n",
419 | "# Class Terms to define a single term of a polynomial.\n",
420 | "\n",
421 | "# Refer Page 90\n",
422 | "\n",
423 | "# NOTE : This is just to illustrate how a Polynomial data type can be constructed in Python\n",
424 | "# For all practical purposes, use sympy, an efficient symbolic math and Computer Algebra System library in Python\n",
425 | "\n",
426 | "class Term:\n",
427 | " def __init__(self):\n",
428 | " self.coef = 0.0\n",
429 | " self.exp = 0\n",
430 | "\n",
431 | " \n",
432 | "# Abstract data type Polynomial - Representation 3 [ Refer Page 89 ]\n",
433 | "class Polynomial:\n",
434 | " def __init__(self,d=0):\n",
435 | " self.degree = d\n",
436 | " self.coeff = [0.0]\n",
437 | " self.term_array = []\n",
438 | " self.terms = 0\n",
439 | " pass\n",
440 | "\n",
441 | " # Method new_term to add a new term to the polynomial [ Refer Page 92 ]\n",
442 | "\n",
443 | " def new_term(self,coef,exp):\n",
444 | " NewTrm = Term()\n",
445 | " NewTrm.coef = coef\n",
446 | " NewTrm.exp = exp\n",
447 | " pos = 0\n",
448 | " # Compute the position of the new term ( sorted by exponents )\n",
449 | " while ( pos < self.terms ) and ( self.term_array[pos].exp < NewTrm.exp ):\n",
450 | " pos += 1\n",
451 | " \n",
452 | " # If there is already a term with the given exponent, add the coefficient\n",
453 | " if ( pos < self.terms ) and ( self.term_array[pos].exp == NewTrm.exp ):\n",
454 | " self.term_array[pos].coef += coef\n",
455 | " return\n",
456 | " \n",
457 | " self.term_array.insert(pos, NewTrm)\n",
458 | " self.terms += 1\n",
459 | " self.degree = max(self.degree, exp) \n",
460 | " \n",
461 | " # Method Add to add two polynomials\n",
462 | "\n",
463 | " def add(self,Polynomial_b):\n",
464 | " if isinstance(Polynomial_b, Polynomial):\n",
465 | " c = Polynomial()\n",
466 | " a_pos = 0\n",
467 | " b_pos = 0\n",
468 | " while ( a_pos < self.terms ) and ( b_pos < Polynomial_b.terms ) :\n",
469 | " # Similar Power terms\n",
470 | " if self.term_array[a_pos].exp == Polynomial_b.term_array[b_pos].exp :\n",
471 | " t = float(term_array[a_pos].coef) + float(Polynomial_b.term_array[b_pos].coef)\n",
472 | " if t != 0 :\n",
473 | " c.new_term(t, self.term_array[a_pos].exp)\n",
474 | " a_pos += 1\n",
475 | " b_pos += 1\n",
476 | " \n",
477 | " # Dissimilar Power terms\n",
478 | " elif self.term_array[a_pos].exp < Polynomial_b.term_array[b_pos].exp:\n",
479 | " c.new_term(Polynomial_b.term_array[b_pos].coef, Polynomial_b.term_array[b_pos].exp)\n",
480 | " b_pos += 1\n",
481 | " else:\n",
482 | " c.new_term(self.term_array[a_pos].coef, self.term_array[a_pos].exp)\n",
483 | " c.display()\n",
484 | " a_pos += 1\n",
485 | " \n",
486 | " # Leftover terms of a\n",
487 | " while a_pos < self.terms:\n",
488 | " c.new_term(self.term_array[a_pos].coef, self.term_array[a_pos].exp)\n",
489 | " a_pos += 1\n",
490 | " \n",
491 | " # Leftover terms of b\n",
492 | " while b_pos < Polynomial_b.terms:\n",
493 | " c.new_term(Polynomial_b.term_array[b_pos].coef, Polynomial_b.term_array[b_pos].exp)\n",
494 | " b_pos += 1\n",
495 | " \n",
496 | " # C contains the resulting polynomial\n",
497 | " return c\n",
498 | " else:\n",
499 | " raise(TypeError)\n",
500 | "\n",
501 | " # Overloading Operator + to do the same funcition as Polynomial_a.add(Polynomial_b)\n",
502 | "\n",
503 | " def __add__(self,Polynomial_b):\n",
504 | " return Polynomial.add(self,Polynomial_b)\n",
505 | " \n",
506 | " def display(self):\n",
507 | " \"\"\"Display the polynomial using the latex display in IPython notebook. \n",
508 | " Note that this works only inside IPython notebook\"\"\"\n",
509 | " \n",
510 | " from IPython.display import display, Math, Latex\n",
511 | " \n",
512 | " latex = \"{\"\n",
513 | " \n",
514 | " for t in reversed(self.term_array):\n",
515 | " if t.coef >= 0 and latex != \"{\" :\n",
516 | " latex += \" + \"\n",
517 | " \n",
518 | " if t.exp == 0:\n",
519 | " latex += \" {coef} \".format(coef=t.coef)\n",
520 | " elif t.exp == 1:\n",
521 | " latex += \" {coef}x \".format(coef=t.coef)\n",
522 | " else:\n",
523 | " latex += \" {coef}x^{exp} \".format(coef=t.coef, exp=t.exp)\n",
524 | " \n",
525 | " latex += \"}\"\n",
526 | " \n",
527 | " display(Math(latex))"
528 | ],
529 | "language": "python",
530 | "metadata": {},
531 | "outputs": [],
532 | "prompt_number": 12
533 | },
534 | {
535 | "cell_type": "markdown",
536 | "metadata": {},
537 | "source": [
538 | "### Example Usage\n",
539 | "$f = {2.5x^2 - 3x + 5}$
\n",
540 | "$g = {3.2x^3 - 4x}$
\n",
541 | "$h = f + g$
\n",
542 | "$h = {3.2x^3 + 2.5x^2 - 7x + 5}$
"
543 | ]
544 | },
545 | {
546 | "cell_type": "code",
547 | "collapsed": false,
548 | "input": [
549 | "# Example Usage :\n",
550 | "\n",
551 | "f = Polynomial()\n",
552 | "f.new_term(2.5, 2); f.new_term(-3, 1); f.new_term(5,0)\n",
553 | "f .display()"
554 | ],
555 | "language": "python",
556 | "metadata": {},
557 | "outputs": [
558 | {
559 | "latex": [
560 | "$${ 2.5x^2 -3x + 5 }$$"
561 | ],
562 | "metadata": {},
563 | "output_type": "display_data",
564 | "text": [
565 | ""
566 | ]
567 | }
568 | ],
569 | "prompt_number": 13
570 | },
571 | {
572 | "cell_type": "code",
573 | "collapsed": false,
574 | "input": [
575 | "g = Polynomial()\n",
576 | "g.new_term(3.2, 3); g.new_term(-4, 1);\n",
577 | "g.display()"
578 | ],
579 | "language": "python",
580 | "metadata": {},
581 | "outputs": [
582 | {
583 | "latex": [
584 | "$${ 3.2x^3 -4x }$$"
585 | ],
586 | "metadata": {},
587 | "output_type": "display_data",
588 | "text": [
589 | ""
590 | ]
591 | }
592 | ],
593 | "prompt_number": 14
594 | },
595 | {
596 | "cell_type": "code",
597 | "collapsed": false,
598 | "input": [
599 | "h = f + g\n",
600 | "h.display()"
601 | ],
602 | "language": "python",
603 | "metadata": {},
604 | "outputs": [
605 | {
606 | "latex": [
607 | "$${ 3.2x^3 + 2.5x^2 -7x + 5 }$$"
608 | ],
609 | "metadata": {},
610 | "output_type": "display_data",
611 | "text": [
612 | ""
613 | ]
614 | }
615 | ],
616 | "prompt_number": 15
617 | },
618 | {
619 | "cell_type": "heading",
620 | "level": 2,
621 | "metadata": {},
622 | "source": [
623 | "ADT 2.4, PAGE 97 "
624 | ]
625 | },
626 | {
627 | "cell_type": "code",
628 | "collapsed": false,
629 | "input": [
630 | "# Abstract Data Type SparseMatrix\n",
631 | "\n",
632 | "class MatrixTerm(object):\n",
633 | " def __init__(self,row=None,col=None,value=None):\n",
634 | " self.row = row\n",
635 | " self.col = col\n",
636 | " self.value = value\n",
637 | " def set_value(self, row=None, col=None, value=None):\n",
638 | " if row is not None:\n",
639 | " self.row = row\n",
640 | " if col is not None:\n",
641 | " self.col = col\n",
642 | " if value is not None:\n",
643 | " self.value = value \n",
644 | "\n",
645 | "class SparseMatrix(object):\n",
646 | " '''A Set of triples , where row and column are non-negative integers\n",
647 | " and form a unique combination; value is also an integer '''\n",
648 | " def __init__(self, rows, cols, terms):\n",
649 | " '''The constructor function creates a SparseMatrix with\n",
650 | " r rows c columns and a capacity of t nonzero terms.'''\n",
651 | " self._rows = rows # No of rows\n",
652 | " self._cols = cols # No of cols\n",
653 | " self._terms = terms # No of terms\n",
654 | " # self._capacity = t # This term is not used since in python lists can be extended\n",
655 | " self._sm_array = [ MatrixTerm() for i in range(terms) ] # List which stores the triplet\n",
656 | " \n",
657 | " def Transpose(self):\n",
658 | " '''Returns the transpose of the SparseMatrix'''\n",
659 | " # self is comparable with *this in c++\n",
660 | " pass\n",
661 | " \n",
662 | " def add(self, b):\n",
663 | " '''Performs matrix addition of self with the b SpareMatrix'''\n",
664 | " pass\n",
665 | " \n",
666 | " def Multiply(self, b):\n",
667 | " '''\n",
668 | " If the number of columns in self equals the number of rows in b, then returns the\n",
669 | " multiplication matrix of self and b.\n",
670 | " If not an exception is thrown\n",
671 | " '''\n",
672 | " pass"
673 | ],
674 | "language": "python",
675 | "metadata": {},
676 | "outputs": [],
677 | "prompt_number": 16
678 | },
679 | {
680 | "cell_type": "heading",
681 | "level": 2,
682 | "metadata": {},
683 | "source": [
684 | "PROGRAM 2.10, PAGE 100"
685 | ]
686 | },
687 | {
688 | "cell_type": "code",
689 | "collapsed": false,
690 | "input": [
691 | "# Transposing a Matrix\n",
692 | "# Runs in O(terms*cols)\n",
693 | "# In comparison, the naive method runs in O(rows*cols)\n",
694 | "\n",
695 | "def transpose(self):\n",
696 | " '''Returns the transpose of self'''\n",
697 | " \n",
698 | " tran = SparseMatrix(self._cols, self._rows, self._terms)\n",
699 | " \n",
700 | " if(self._terms < 0):\n",
701 | " # If self is a zero matrix\n",
702 | " return tran\n",
703 | " \n",
704 | " # If self is a Non Zero matrix\n",
705 | " \n",
706 | " tran_term_index = 0\n",
707 | " \n",
708 | " for c in range(self._cols):\n",
709 | " for i in range(self._terms):\n",
710 | " # Find and move terms in column c\n",
711 | " if self._sm_array[i].col == c:\n",
712 | " # Add the i-th term with row number and col number interchanged to the tran matrix\n",
713 | " tran._sm_array[tran_term_index].set_value(row = c, \n",
714 | " col = self._sm_array[i].row,\n",
715 | " value = self._sm_array[i].value\n",
716 | " )\n",
717 | " tran_term_index += 1\n",
718 | " return tran\n",
719 | "\n",
720 | "SparseMatrix.transpose = transpose"
721 | ],
722 | "language": "python",
723 | "metadata": {},
724 | "outputs": [],
725 | "prompt_number": 17
726 | },
727 | {
728 | "cell_type": "heading",
729 | "level": 2,
730 | "metadata": {},
731 | "source": [
732 | "PROGRAM 2.11, PAGE 102"
733 | ]
734 | },
735 | {
736 | "cell_type": "code",
737 | "collapsed": false,
738 | "input": [
739 | "# A faster method for transposing of Sparse Matrices\n",
740 | "# Runs in O(terms + cols)\n",
741 | "\n",
742 | "def fast_transpose(self):\n",
743 | " \n",
744 | " tran = SparseMatrix(self._cols, self._rows, self._terms)\n",
745 | " \n",
746 | " if self._terms>0:\n",
747 | " cols = self._cols\n",
748 | " terms = self._terms\n",
749 | " sm_array = self._sm_array\n",
750 | " \n",
751 | " # No. of terms in the i-th column => row_size[i] => no of terms in the i-th row of transpose\n",
752 | " row_size = [0]*cols\n",
753 | " \n",
754 | " # Starting index of the sm_array for row i of tran array => row_start[i]\n",
755 | " row_start = [0]*cols\n",
756 | " \n",
757 | " for i in range(terms):\n",
758 | " row_size[sm_array[i].col] += 1\n",
759 | " \n",
760 | " # Based on the no. of terms in the previous row the row start index table can\n",
761 | " # be computed. This helps in speeding up the transpose method.\n",
762 | " for i in range(1,cols):\n",
763 | " row_start[i] = row_start[i-1] + row_size[i-1]\n",
764 | " \n",
765 | " for i in range(terms):\n",
766 | " j = row_start[sm_array[i].col]\n",
767 | " tran._sm_array[j].row = sm_array[i].col\n",
768 | " tran._sm_array[j].col = sm_array[i].row\n",
769 | " tran._sm_array[j].value = sm_array[i].value\n",
770 | " row_start[sm_array[i].col] += 1\n",
771 | " \n",
772 | " return tran\n",
773 | "\n",
774 | "SparseMatrix.fast_transpose = fast_transpose"
775 | ],
776 | "language": "python",
777 | "metadata": {},
778 | "outputs": [],
779 | "prompt_number": 18
780 | },
781 | {
782 | "cell_type": "code",
783 | "collapsed": false,
784 | "input": [
785 | "def get_matrix(self,array):\n",
786 | " \"\"\"\n",
787 | " Stores the triplets in the array to self\n",
788 | " \"\"\"\n",
789 | " try:\n",
790 | " for i in range(len(array)):\n",
791 | " tupl = array[i]\n",
792 | " a._sm_array[i] = MatrixTerm(tupl[0],tupl[1],tupl[2])\n",
793 | " except Exception,e:\n",
794 | " pass\n",
795 | " \n",
796 | "def print_matrix(self):\n",
797 | " bmat = numpy.zeros((self._rows,self._cols))\n",
798 | " for term in self._sm_array:\n",
799 | " try:\n",
800 | " bmat[term.row][term.col] = term.value\n",
801 | " except Exception, e:\n",
802 | " pass\n",
803 | " print bmat\n",
804 | "\n",
805 | "# Helper methods to input an array of triplets and to print the sparse matrix\n",
806 | "SparseMatrix.get_matrix = get_matrix\n",
807 | "SparseMatrix.print_matrix = print_matrix\n",
808 | "\n",
809 | "a = SparseMatrix(6,6,8)\n",
810 | "get_matrix(a,[(0,0,-15),(0,3,12),(0,5,-5),(1,1,1),(1,2,13),(2,3,-16),(4,0,9),(5,2,12)])\n",
811 | "print 'Matrix a\\n'\n",
812 | "print_matrix(a)"
813 | ],
814 | "language": "python",
815 | "metadata": {},
816 | "outputs": [
817 | {
818 | "output_type": "stream",
819 | "stream": "stdout",
820 | "text": [
821 | "Matrix a\n",
822 | "\n",
823 | "[[-15. 0. 0. 12. 0. -5.]\n",
824 | " [ 0. 1. 13. 0. 0. 0.]\n",
825 | " [ 0. 0. 0. -16. 0. 0.]\n",
826 | " [ 0. 0. 0. 0. 0. 0.]\n",
827 | " [ 9. 0. 0. 0. 0. 0.]\n",
828 | " [ 0. 0. 12. 0. 0. 0.]]\n"
829 | ]
830 | }
831 | ],
832 | "prompt_number": 19
833 | },
834 | {
835 | "cell_type": "code",
836 | "collapsed": false,
837 | "input": [
838 | "# Technically timeit should be run on different sized / valued inputs but here the same matrix a\n",
839 | "# is transposed repetitively instead to avoid complexity of generating a random sparse matrix\n",
840 | "\n",
841 | "%timeit a.transpose()\n",
842 | "tra = a.transpose()\n",
843 | "print '\\nTranspose of a\\n'\n",
844 | "print_matrix(tra)"
845 | ],
846 | "language": "python",
847 | "metadata": {},
848 | "outputs": [
849 | {
850 | "output_type": "stream",
851 | "stream": "stdout",
852 | "text": [
853 | "100000 loops, best of 3: 17.8 \u00b5s per loop\n",
854 | "\n",
855 | "Transpose of a\n",
856 | "\n",
857 | "[[-15. 0. 0. 0. 9. 0.]\n",
858 | " [ 0. 1. 0. 0. 0. 0.]\n",
859 | " [ 0. 13. 0. 0. 0. 12.]\n",
860 | " [ 12. 0. -16. 0. 0. 0.]\n",
861 | " [ 0. 0. 0. 0. 0. 0.]\n",
862 | " [ -5. 0. 0. 0. 0. 0.]]\n"
863 | ]
864 | }
865 | ],
866 | "prompt_number": 20
867 | },
868 | {
869 | "cell_type": "code",
870 | "collapsed": false,
871 | "input": [
872 | "%timeit a.fast_transpose()\n",
873 | "tra = a.fast_transpose()\n",
874 | "print '\\nFast Transpose of a\\n'\n",
875 | "print_matrix(tra)"
876 | ],
877 | "language": "python",
878 | "metadata": {},
879 | "outputs": [
880 | {
881 | "output_type": "stream",
882 | "stream": "stdout",
883 | "text": [
884 | "100000 loops, best of 3: 13.8 \u00b5s per loop\n",
885 | "\n",
886 | "Fast Transpose of a\n",
887 | "\n",
888 | "[[-15. 0. 0. 0. 9. 0.]\n",
889 | " [ 0. 1. 0. 0. 0. 0.]\n",
890 | " [ 0. 13. 0. 0. 0. 12.]\n",
891 | " [ 12. 0. -16. 0. 0. 0.]\n",
892 | " [ 0. 0. 0. 0. 0. 0.]\n",
893 | " [ -5. 0. 0. 0. 0. 0.]]\n"
894 | ]
895 | }
896 | ],
897 | "prompt_number": 21
898 | },
899 | {
900 | "cell_type": "code",
901 | "collapsed": false,
902 | "input": [
903 | "# Note that the sm_array has all the triplets ordered by row number.\n",
904 | "print \"Row\\tCol\\tValue\"\n",
905 | "print \"=======================\"\n",
906 | "for t in tra._sm_array:\n",
907 | " print \"%d\\t%d\\t%d\"%(t.row, t.col, t.value)"
908 | ],
909 | "language": "python",
910 | "metadata": {},
911 | "outputs": [
912 | {
913 | "output_type": "stream",
914 | "stream": "stdout",
915 | "text": [
916 | "Row\tCol\tValue\n",
917 | "=======================\n",
918 | "0\t0\t-15\n",
919 | "0\t4\t9\n",
920 | "1\t1\t1\n",
921 | "2\t1\t13\n",
922 | "2\t5\t12\n",
923 | "3\t0\t12\n",
924 | "3\t2\t-16\n",
925 | "5\t0\t-5\n"
926 | ]
927 | }
928 | ],
929 | "prompt_number": 22
930 | },
931 | {
932 | "cell_type": "heading",
933 | "level": 2,
934 | "metadata": {},
935 | "source": [
936 | "PROGRAM 2.12, PAGE 103"
937 | ]
938 | },
939 | {
940 | "cell_type": "code",
941 | "collapsed": false,
942 | "input": [
943 | "# Storing a Matrix Term\n",
944 | "\n",
945 | "def add_term(self,val,r,c):\n",
946 | " # If sum != 0, then it is stored along with r and c as last term in self\n",
947 | " if val != 0:\n",
948 | " # Compute the index where the new Term must be inserted into the \n",
949 | " # sm_array which is ordered by row no\n",
950 | " \n",
951 | " i = 0\n",
952 | " while i < self._terms:\n",
953 | " if self._sm_array[i].row > r:\n",
954 | " break\n",
955 | " i += 1\n",
956 | " \n",
957 | " self._sm_array.insert(i, MatrixTerm(row = r, col = c, value = val))\n",
958 | " self._terms += 1\n",
959 | " \n",
960 | "SparseMatrix.add_term = add_term"
961 | ],
962 | "language": "python",
963 | "metadata": {},
964 | "outputs": [],
965 | "prompt_number": 23
966 | },
967 | {
968 | "cell_type": "heading",
969 | "level": 2,
970 | "metadata": {},
971 | "source": [
972 | "PROGRAM 2.14, PAGE 104 - 106"
973 | ]
974 | },
975 | {
976 | "cell_type": "code",
977 | "collapsed": false,
978 | "input": [
979 | "# Multiply Sparse Matrices\n",
980 | "# Works in O(b.cols * a.terms + a.rows * b.terms)\n",
981 | "# This outperforms the standard O(a.rows * a.cols * b.cols) time if b.terms and a.terms are relatively low\n",
982 | "\n",
983 | "def multiply(self,b):\n",
984 | " '''Return the product of sparse matrices self and b'''\n",
985 | " \n",
986 | " if self._cols != b._rows:\n",
987 | " raise Exception('Incompatible Matrices.')\n",
988 | "\n",
989 | " # b transpose makes the sm_array contain terms ordered by columns of b ( rows of b\u1d40 )\n",
990 | " bXpose = b.fast_transpose()\n",
991 | " \n",
992 | " # The product matrix\n",
993 | " product = SparseMatrix(self._rows, b._cols, 0)\n",
994 | " \n",
995 | " curr_row_index = 0\n",
996 | " curr_row_begin = 0\n",
997 | " curr_row_a = self._sm_array[0].row # Current row of a that is being multiplied with the col of b\n",
998 | " \n",
999 | " # Introducing terms to help deal with the end condition\n",
1000 | " self._sm_array.append(MatrixTerm(row = self._rows, col = -1, value = None))\n",
1001 | " bXpose._sm_array.append(MatrixTerm(row = b._cols, col = -1, value = None))\n",
1002 | " \n",
1003 | " Sum = 0\n",
1004 | " \n",
1005 | " while curr_row_index < self._terms :\n",
1006 | " # Generate row current_row_a of d\n",
1007 | " curr_col_b = bXpose._sm_array[0].row\n",
1008 | " curr_col_index = 0\n",
1009 | " \n",
1010 | " while curr_col_index <= b._terms:\n",
1011 | " # Multiply row curr_row_a of self by column curr_col_b of b\n",
1012 | " \n",
1013 | " if self._sm_array[curr_row_index].row != curr_row_a:\n",
1014 | " # End of row curr_row_a\n",
1015 | " \n",
1016 | " # Add the sum to the product matrix at position { curr_row_a, curr_col_b }\n",
1017 | " product.add_term(Sum,curr_row_a,curr_col_b)\n",
1018 | " \n",
1019 | " Sum = 0 # Reset sum to 0\n",
1020 | " \n",
1021 | " # Reset to the beginning of current row ( of a ) for multiplication with next column ( of b )\n",
1022 | " curr_row_index = curr_row_begin\n",
1023 | " \n",
1024 | " # Advance to next column ( of b )\n",
1025 | " while bXpose._sm_array[curr_col_index].row == curr_col_b:\n",
1026 | " curr_col_index += 1\n",
1027 | " \n",
1028 | " # Note that in Sparse Matrix the terms are arranged in rows\n",
1029 | " # bXpose's terms are arranged in columns of b ( or rows of bXpose )\n",
1030 | " curr_col_b = bXpose._sm_array[curr_col_index].row\n",
1031 | " \n",
1032 | " elif bXpose._sm_array[curr_col_index].row != curr_col_b :\n",
1033 | " # End of column curr_col_b of b\n",
1034 | " \n",
1035 | " # Add the sum to the product matrix at position { curr_row_a, curr_col_b }\n",
1036 | " product.add_term(Sum,curr_row_a,curr_col_b)\n",
1037 | " \n",
1038 | " Sum = 0 # Reset sum to 0\n",
1039 | " \n",
1040 | " # Reset to the beginning of current row ( of a ) for multiplication with next column ( of b )\n",
1041 | " curr_row_index = curr_row_begin\n",
1042 | " \n",
1043 | " # Advance to the next column in b \n",
1044 | " # ( note that bXpose._sm_array[curr_col_index].row points to the next column already )\n",
1045 | " curr_col_b = bXpose._sm_array[curr_col_index].row\n",
1046 | " \n",
1047 | " elif self._sm_array[curr_row_index].col < bXpose._sm_array[curr_col_index].col :\n",
1048 | " curr_row_index += 1 # Advance to the next term in row\n",
1049 | " \n",
1050 | " # FOLLOWING EXPLANATION DIAGRAMS NOT IN TEXTBOOK\n",
1051 | " #\n",
1052 | " # ( If the following diagrams appear in two lines shrink your page size to below 100% )\n",
1053 | " #\n",
1054 | " # In the row curr_row_a, if the current column of the current term is \n",
1055 | " # same as the current row of b ( col of bXpose ) in the curr_col_b column\n",
1056 | " # multiply the terms and add to the Sum\n",
1057 | " #\n",
1058 | " #\n",
1059 | " # a b bXpose product\n",
1060 | " # \n",
1061 | " # . . 0 . . . . 0 . . 0 . . . . .\n",
1062 | " # 0 x . 0 \u25c0\u2015\u2015 curr_row_a . 0 0 . . 0 . 0 . . x . \u25c0\u2015\u2015 Result row\n",
1063 | " # . 0 . . 0 . . . . 0 . x \u25c0\u2500\u2510 . . . .\n",
1064 | " # 0 . 0 . . 0 x . 0 . . . \u2502 . . . . \n",
1065 | " # \u25b2 \u2502 \u25b2\n",
1066 | " # \u2502 curr_col_b \u2502 \n",
1067 | " # curr_col_b Result col\n",
1068 | " # \n",
1069 | " # in a : x = a._sm_matrix[curr_row_index]\n",
1070 | " # in b : x = b._sm_matrix[curr_col_index]\n",
1071 | " # in product : x is the new term to be added for which the Sum is being computed\n",
1072 | " #\n",
1073 | " \n",
1074 | " elif self._sm_array[curr_row_index].col == bXpose._sm_array[curr_col_index].col :\n",
1075 | " # Add to sum\n",
1076 | " Sum += self._sm_array[curr_row_index].value * bXpose._sm_array[curr_col_index].value\n",
1077 | " curr_row_index += 1\n",
1078 | " curr_col_index += 1\n",
1079 | " \n",
1080 | " else: \n",
1081 | " curr_col_index += 1\n",
1082 | " # Next term in curr_col_b\n",
1083 | " \n",
1084 | " while self._sm_array[curr_row_index].row == curr_row_a :\n",
1085 | " # Advance to next row ( of a )\n",
1086 | " curr_row_index += 1\n",
1087 | " \n",
1088 | " curr_row_begin = curr_row_index\n",
1089 | " curr_row_a = self._sm_array[curr_row_index].row\n",
1090 | " \n",
1091 | " # To remove the last terms in a and d since the terms are empty:\n",
1092 | " del product._sm_array[-1]\n",
1093 | " del self._sm_array[-1]\n",
1094 | " del b._sm_array [-1]\n",
1095 | " \n",
1096 | " return product\n",
1097 | "\n",
1098 | "SparseMatrix.multiply = multiply"
1099 | ],
1100 | "language": "python",
1101 | "metadata": {},
1102 | "outputs": [],
1103 | "prompt_number": 24
1104 | },
1105 | {
1106 | "cell_type": "code",
1107 | "collapsed": false,
1108 | "input": [
1109 | "mult = a.multiply(tra)\n",
1110 | "print 'Matrix a is \\n'\n",
1111 | "print_matrix(a)\n",
1112 | "print \"\\nMatrix a . a' is :\\n\"\n",
1113 | "print_matrix(mult)"
1114 | ],
1115 | "language": "python",
1116 | "metadata": {},
1117 | "outputs": [
1118 | {
1119 | "output_type": "stream",
1120 | "stream": "stdout",
1121 | "text": [
1122 | "Matrix a is \n",
1123 | "\n",
1124 | "[[-15. 0. 0. 12. 0. -5.]\n",
1125 | " [ 0. 1. 13. 0. 0. 0.]\n",
1126 | " [ 0. 0. 0. -16. 0. 0.]\n",
1127 | " [ 0. 0. 0. 0. 0. 0.]\n",
1128 | " [ 9. 0. 0. 0. 0. 0.]\n",
1129 | " [ 0. 0. 12. 0. 0. 0.]]\n",
1130 | "\n",
1131 | "Matrix a . a' is :\n",
1132 | "\n",
1133 | "[[ 394. 0. -192. 0. -135. 0.]\n",
1134 | " [ 0. 170. 0. 0. 0. 156.]\n",
1135 | " [-192. 0. 256. 0. 0. 0.]\n",
1136 | " [ 0. 0. 0. 0. 0. 0.]\n",
1137 | " [-135. 0. 0. 0. 81. 0.]\n",
1138 | " [ 0. 156. 0. 0. 0. 0.]]\n"
1139 | ]
1140 | }
1141 | ],
1142 | "prompt_number": 25
1143 | },
1144 | {
1145 | "cell_type": "heading",
1146 | "level": 2,
1147 | "metadata": {},
1148 | "source": [
1149 | "ADT 2.5, PAGE 114"
1150 | ]
1151 | },
1152 | {
1153 | "cell_type": "code",
1154 | "collapsed": false,
1155 | "input": [
1156 | "# Abstract Datatype String\n",
1157 | "class String(object):\n",
1158 | " def __init__(self,init):\n",
1159 | " # Constructor that initializes self to string init of length of m\n",
1160 | " self._str = init\n",
1161 | " self._length = len(init)\n",
1162 | " self.failure_function()\n",
1163 | " \n",
1164 | " def __eq__(self,t):\n",
1165 | " # overloading == operators\n",
1166 | " return self._str == t._str\n",
1167 | " \n",
1168 | " def __not__(self):\n",
1169 | " if self._length <= 0:\n",
1170 | " print True\n",
1171 | " else:\n",
1172 | " print False\n",
1173 | " \n",
1174 | " def length(self):\n",
1175 | " return self._length\n",
1176 | " \n",
1177 | " def concat(self,left):\n",
1178 | " self._str += t\n",
1179 | " return self\n",
1180 | " \n",
1181 | " def substr(self,i,j):\n",
1182 | " \"\"\"\n",
1183 | " Return a substring whose starting index is i and length is j\n",
1184 | " \"\"\"\n",
1185 | " try:\n",
1186 | " substr = self._str[i:i+j]\n",
1187 | " return substr\n",
1188 | " \n",
1189 | " except IndexError:\n",
1190 | " print 'Invalid index / length to get Substring'\n",
1191 | " \n",
1192 | " def find(self, pat):\n",
1193 | " '''Returns an index i such that pat matches the substring of self that begins at position of i\n",
1194 | " Returns -1 if pat is either empty or not a substring'''\n",
1195 | " pass "
1196 | ],
1197 | "language": "python",
1198 | "metadata": {},
1199 | "outputs": [],
1200 | "prompt_number": 26
1201 | },
1202 | {
1203 | "cell_type": "heading",
1204 | "level": 2,
1205 | "metadata": {},
1206 | "source": [
1207 | "PROGRAM 2.15, PAGE 115"
1208 | ]
1209 | },
1210 | {
1211 | "cell_type": "code",
1212 | "collapsed": false,
1213 | "input": [
1214 | "# Exhaustive Pattern Matching\n",
1215 | "\n",
1216 | "def find(self,pat):\n",
1217 | " '''Return -1 if pat does not occur in self\n",
1218 | " otherwise return the first position in self, where pat begins'''\n",
1219 | " \n",
1220 | " for start in range(self.length()-pat.length()+1) :\n",
1221 | " # Check for match beginning at str[start]\n",
1222 | " for j in range(pat.length()) :\n",
1223 | " # Character by character checking\n",
1224 | " \n",
1225 | " if self._str[start + j] != pat._str[j]:\n",
1226 | " break\n",
1227 | " \n",
1228 | " if j == pat.length()-1:\n",
1229 | " # Match found\n",
1230 | " return start\n",
1231 | " \n",
1232 | " # Pat is empty or does not occur in string\n",
1233 | " return -1 \n",
1234 | "\n",
1235 | "# NOTE : The entire program can be implemented using python string function as follows: \n",
1236 | "# self._str.find(pat)\n",
1237 | "\n",
1238 | "String.find = find"
1239 | ],
1240 | "language": "python",
1241 | "metadata": {},
1242 | "outputs": [],
1243 | "prompt_number": 27
1244 | },
1245 | {
1246 | "cell_type": "heading",
1247 | "level": 2,
1248 | "metadata": {},
1249 | "source": [
1250 | "PROGRAM 2.16, PAGE 117"
1251 | ]
1252 | },
1253 | {
1254 | "cell_type": "code",
1255 | "collapsed": false,
1256 | "input": [
1257 | "# Faster implementation of the find method using the failure array and failure function to \n",
1258 | "# allow resuming search at partial matches. The failure vector is stored at String.f\n",
1259 | "\n",
1260 | "# Runs in O( LengthP + LengthS )\n",
1261 | "\n",
1262 | "def fast_find(self, pat):\n",
1263 | " posP = 0\n",
1264 | " posS = 0\n",
1265 | "\n",
1266 | " lengthP = pat.length()\n",
1267 | " lengthS = self.length()\n",
1268 | " \n",
1269 | " while ( posP < lengthP ) and ( posS < lengthS ):\n",
1270 | " if pat._str[posP] == self._str[posS] :\n",
1271 | " posP += 1\n",
1272 | " posS += 1\n",
1273 | " \n",
1274 | " elif posP == 0:\n",
1275 | " posS += 1\n",
1276 | " \n",
1277 | " else:\n",
1278 | " posP = pat.f[posP - 1] + 1\n",
1279 | " \n",
1280 | " if posP < lengthP :\n",
1281 | " return -1\n",
1282 | " else:\n",
1283 | " return posS-lengthP\n",
1284 | " \n",
1285 | "String.fast_find = fast_find"
1286 | ],
1287 | "language": "python",
1288 | "metadata": {},
1289 | "outputs": [],
1290 | "prompt_number": 28
1291 | },
1292 | {
1293 | "cell_type": "heading",
1294 | "level": 2,
1295 | "metadata": {},
1296 | "source": [
1297 | "PROGRAM 2.17, PAGE 118"
1298 | ]
1299 | },
1300 | {
1301 | "cell_type": "code",
1302 | "collapsed": false,
1303 | "input": [
1304 | "# Computing the failure function\n",
1305 | "\n",
1306 | "def failure_function(self):\n",
1307 | " lengthP = self.length()\n",
1308 | " self.f = [0]*lengthP\n",
1309 | " self.f[0] = -1\n",
1310 | " for j in range(1,lengthP):\n",
1311 | " i = self.f[j-1]\n",
1312 | " while ( self._str[j] != self._str[i+1] ) and ( i >= 0 ):\n",
1313 | " i = self.f[i]\n",
1314 | " if self._str[j] == self._str[i+1]:\n",
1315 | " self.f[j] = i + 1\n",
1316 | " else:\n",
1317 | " self.f[j-1] = -1\n",
1318 | " \n",
1319 | "String.failure_function = failure_function"
1320 | ],
1321 | "language": "python",
1322 | "metadata": {},
1323 | "outputs": [],
1324 | "prompt_number": 29
1325 | },
1326 | {
1327 | "cell_type": "code",
1328 | "collapsed": false,
1329 | "input": [
1330 | "str1 = String('hello world')\n",
1331 | "str2 = String('sdf')\n",
1332 | "print str1.find(str2), str1.fast_find(str2)"
1333 | ],
1334 | "language": "python",
1335 | "metadata": {},
1336 | "outputs": [
1337 | {
1338 | "output_type": "stream",
1339 | "stream": "stdout",
1340 | "text": [
1341 | "-1 -1\n"
1342 | ]
1343 | }
1344 | ],
1345 | "prompt_number": 30
1346 | },
1347 | {
1348 | "cell_type": "code",
1349 | "collapsed": false,
1350 | "input": [
1351 | "str2 = String('ell')\n",
1352 | "print str1.find(str2), str1.fast_find(str2)"
1353 | ],
1354 | "language": "python",
1355 | "metadata": {},
1356 | "outputs": [
1357 | {
1358 | "output_type": "stream",
1359 | "stream": "stdout",
1360 | "text": [
1361 | "1 1\n"
1362 | ]
1363 | }
1364 | ],
1365 | "prompt_number": 31
1366 | },
1367 | {
1368 | "cell_type": "code",
1369 | "collapsed": false,
1370 | "input": [
1371 | "# Analysing the above two find methods\n",
1372 | "x_axis = range(10, 1000, 50)\n",
1373 | "find_run_time = []\n",
1374 | "fast_find_run_time = []\n",
1375 | "\n",
1376 | "\n",
1377 | "for i in x_axis:\n",
1378 | " string = String(''.join(choice(ascii_letters) for _ in range(i))) # Generates a random string of i chars\n",
1379 | " pattern = String(''.join(choice(ascii_letters) for _ in range(i/2))) # Generates a random pattern string of i chars\n",
1380 | " start = time()\n",
1381 | " for i in range(10):\n",
1382 | " string.find(pattern)\n",
1383 | " find_run_time.append((time() - start) / 10) # Takes average over 10 runs\n",
1384 | " start = time()\n",
1385 | " for i in range(10):\n",
1386 | " string.fast_find(pattern)\n",
1387 | " fast_find_run_time.append((time() - start) / 10)\n",
1388 | " \n",
1389 | "\n",
1390 | "# NOTE: All these are typically failure / worst case run times, \n",
1391 | "# since the probability of a random pattern matching with a random string is very low\n",
1392 | "\n",
1393 | "# Note the quadratic run time of find and linear run time of fast_find\n",
1394 | " \n",
1395 | "%matplotlib inline \n",
1396 | "plt.figure().set_size_inches(10,10)\n",
1397 | "plt.xlabel(\"Size of the input string : n \")\n",
1398 | "plt.ylabel(\"Time in ms for the find operation\")\n",
1399 | "\n",
1400 | "plt.plot(x_axis, find_run_time, marker = \"o\", linestyle = \"--\", c=\"b\", label=\"find\")\n",
1401 | "plt.plot(x_axis, fast_find_run_time, marker = \"o\", linestyle = \"--\", c=\"r\", label=\"fast_find\")\n",
1402 | "plt.legend()\n",
1403 | "plt.show()"
1404 | ],
1405 | "language": "python",
1406 | "metadata": {},
1407 | "outputs": [
1408 | {
1409 | "metadata": {},
1410 | "output_type": "display_data",
1411 | "png": "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\nPfRQBg4cSGVlJR9++GHof1apZuCTJEndKmlpSdpePHMmFBUFr6lTk588dSoUFVHS0JD8Gs3NoWp4\n9NFHGTVqFNdeey1LlixhxIgR/O53v+Pjjz8mFotx/fXXc//99wNw22238cknn/D222/z0UcfMW3a\nNDbeeGNqa2s7XaOurm6D94vH4wwZMoTp06fzySef0L9/f4qKijr1ED755JPsvvvuLFq0iB//+Med\nhom//e1v88UvfpFFixYxZcoUbrvttsiGdQ18kiSpW62lpUnb28aOhfb24NVV4Gtvp7WyMvk1ysp6\nVEt7ezsAhx56KHvuuScAe++9N9/85jeZPXs2AP3792fRokW88sorFBUVMXLkSAYMGLDeNfpqp512\n4rTTTqOoqIiTTz6Zd999lw8++IA333yTp556issuu4x+/foxatQojj766JTdt6cMfJIkqVuV1dXU\nlJd3aptcXs6YxGSGTF0D1j6DN3fuXA477DAGDx7MZpttxrRp01i0aBEAEyZMYOzYsXzzm99k++23\n54ILLqC1tXW9a/TVNttss+b9JptsAsDSpUtZuHAhgwYNYuONN17z/Z122ikl9+wNJ21IkqRurZ5U\nMaW+nuLmZtrKyhhXVdWjGbapuEZH3/72t6murmbmzJn079+fH/zgB2uekyspKeGiiy7ioosu4o03\n3uCoo47ic5/7HKeeempGhlW33XZbFi9ezLJly9YEwTfeeIPi4uK03zsZA58kSQpl9PjxvQ5nqbzG\nakuXLmXQoEH079+fJ598kjvuuIOxY8cC0NjYyBZbbMHw4cMZMGAA/fr1WxO2tt56a+LrTB5JtZ12\n2on99tuPiy++mCuuuIK5c+cyffr0Pi3r0hcO6UqSpJx03XXXcdFFFzFw4EAuu+wyvvGNb6z53nvv\nvccJJ5zApptuyvDhw9esvQcwadIk/vjHP7L55ptz7rnn9vr+607gWN222h133MHcuXPZfPPNufTS\nSznllFN6fa++im4FwOi0R/XApCRJ2a6oqCiyiQWFbEP/3BMBss95zR4+SZKkPGfgkyRJBWvOnDkM\nGDBgvdfAgQOjLi2lHNKVJElrOKQbDYd0JUmS1CcGPkmSpDznOnySJGmNQYMGRbbfayEbNGhQWq9f\niL9Rn+GTJEk5wWf4JEmSFIqBT5IkKc8Z+CRJkvKcgU+SJCnPGfgkSZLynIFPkiQpzxn4JEmS8pyB\n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\n",
1412 | "text": [
1413 | ""
1414 | ]
1415 | }
1416 | ],
1417 | "prompt_number": 32
1418 | }
1419 | ],
1420 | "metadata": {}
1421 | }
1422 | ]
1423 | }
--------------------------------------------------------------------------------