├── .gitignore ├── artifacts └── python-for-coding-interview.pdf ├── USAGE.md ├── Makefile ├── Dockerfile ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | misc 4 | 5 | *.bat 6 | NOTES.md 7 | TODO.md 8 | 9 | artifacts/python-for-coding-interview-v*.pdf 10 | -------------------------------------------------------------------------------- /artifacts/python-for-coding-interview.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmicu/python-for-coding-interviews/HEAD/artifacts/python-for-coding-interview.pdf -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ## With docker 4 | 5 | ```text 6 | docker build -t pfci:latest . 7 | # Linter 8 | docker run --rm pfci:latest make linter 9 | # Generate PDF 10 | docker run --rm -v %cd%\artifacts:/repo/artifacts pfci:latest make pdf # Windows 11 | docker run --rm -v $(pwd)/artifacts:/repo/artifacts pfci:latest make pdf # Linux 12 | ``` 13 | 14 | ## Without docker 15 | 16 | ```text 17 | make linter 18 | make pdf 19 | ``` 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := all 2 | 3 | INPUT_FILE = README.md 4 | USAGE_FILE = USAGE.md 5 | OUTPUT_DIR = artifacts 6 | OUTPUT_FILE = $(OUTPUT_DIR)/python-for-coding-interview.pdf 7 | 8 | .PHONY: linter 9 | linter: 10 | mdl -r ~MD013 "$(INPUT_FILE)" 11 | mdl -r ~MD013 "$(USAGE_FILE)" 12 | 13 | .PHONY: pdf 14 | pdf: 15 | mkdir -p "$(OUTPUT_DIR)" 16 | pandoc "$(INPUT_FILE)" \ 17 | --pdf-engine=xelatex \ 18 | -M date="`date "+%B %e, %Y"`" \ 19 | -o "$(OUTPUT_FILE)" 20 | 21 | .PHONY: all 22 | all: linter pdf 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.10 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | 5 | RUN apt-get update && \ 6 | # Set timezone 7 | ln -fs /usr/share/zoneinfo/America/New_York /etc/localtime && \ 8 | apt-get install -y tzdata && \ 9 | dpkg-reconfigure --frontend noninteractive tzdata && \ 10 | # Install `pandoc` and `markdownlint` 11 | apt-get install make pandoc ruby texlive-xetex --yes && \ 12 | gem install mdl 13 | 14 | COPY Makefile /repo/ 15 | COPY README.md /repo/ 16 | COPY USAGE.md /repo/ 17 | 18 | RUN mkdir /repo/artifacts 19 | 20 | WORKDIR /repo 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 mmicu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Python for Coding Interviews 3 | subtitle: 4 | geometry: margin=1in 5 | --- 6 | 7 | # Introduction 8 | 9 | This guide includes a list of several and useful `Python` data structures to know for coding interviews. 10 | 11 | It is intended to show the main data structures incorporated in the language 12 | and their useful functions. More advance `Python` features will not be shown here. 13 | 14 | Additional material: 15 | 16 | | **Topic** | **Link** | 17 | |---|---| 18 | | Intro to Algorithms and Data Structures | | 19 | | Python DS | | 20 | | Python collections | | 21 | | Time complexity | | 22 | 23 | ## Index 24 | 25 | 1. [Primitive Types](#primitive-types) 26 | 1. [Tuples](#tuples) 27 | 1. [Lists](#lists) 28 | 1. [Strings](#strings) 29 | 1. [Stacks](#stacks) 30 | 1. [Queues](#queues) 31 | 1. [Sets](#sets) 32 | 1. [Hash Tables](#hash-tables) 33 | 1. [Heaps](#heaps) 34 | 1. [Collections](#collections) 35 | * [namedtuple](#collectionsnamedtuple) 36 | * [defaultdict](#collectionsdefaultdict) 37 | * [Counter](#collectionscounter) 38 | * [OrderedDict](#collectionsordereddict) 39 | 40 | \newpage 41 | 42 | ## Primitive Types 43 | 44 | 1. Booleans (`bool`). 45 | 1. Integers (`int`). 46 | 1. Floats (`float`). 47 | 1. Strings (`str`). 48 | 49 | ```python 50 | # Define variables 51 | >>> b, i, f, s = True, 12, 8.31, 'Hello, world!' 52 | >>> type(b) # 53 | >>> type(i) # ~ Unbounded 54 | >>> type(f) # ~ Bounded 55 | >>> type(s) # 56 | 57 | # Type Conversion 58 | >>> str(i) 59 | '12' 60 | >>> float(i) 61 | 12.0 62 | >>> str(b) 63 | 'True' 64 | >>> int('10') 65 | 10 66 | >>> int('10a') # `ValueError: invalid literal for int() with base 10: '10a'` 67 | 68 | # Operations 69 | >>> 5 * 2 70 | 10 71 | >>> 5 * 2. 72 | 10.0 73 | >>> 5 / 2 74 | 2.5 75 | >>> 5 // 2 # `//` is the integer division 76 | 2 77 | >>> 5 % 2 78 | 1 79 | 80 | # `min` and `max` 81 | >>> min(4, 2) 82 | 2 83 | >>> max(21, 29) 84 | 29 85 | 86 | # Some useful math functions 87 | >>> abs(-1.2) 88 | 1.2 89 | >>> divmod(9, 4) 90 | (2, 1) 91 | >>> 2 ** 3 # Equivalent to `pow(2, 3)` 92 | 8 93 | 94 | # Math functions from the `math` package 95 | >>> import math 96 | >>> math.ceil(7.2) 97 | 8 98 | >>> math.floor(7.2) 99 | 7 100 | >>> math.sqrt(4) 101 | 2.0 102 | 103 | # Pseudo lower and upper bounds 104 | >>> float('-inf') # Pseudo min-int 105 | -inf 106 | >>> float('inf') # Pseudo max-int 107 | inf 108 | 109 | # Pseudo lower and upper bounds (Python >= 3.5) 110 | >>> import math 111 | 112 | >>> math.inf 113 | inf 114 | >>> -math.inf 115 | -inf 116 | ``` 117 | 118 | ## `range` and `enumerate` 119 | 120 | ```python 121 | # `range` 122 | >>> list(range(3)) # Equivalent to `range(0, 3)` 123 | [0, 1, 2] 124 | >>> list(range(1, 10, 2)) 125 | [1, 3, 5, 7, 9] 126 | >>> for i in range(3): print(i) 127 | 0 128 | 1 129 | 2 130 | >>> for i in range(2, -1, -1): print(i) # Equivalent to `reversed(range(3))` 131 | 2 132 | 1 133 | 0 134 | 135 | # `enumerate` 136 | >>> for i, v in enumerate(range(3)): print(i, v) 137 | 0 0 138 | 1 1 139 | 2 2 140 | >>> for i, v in enumerate(range(3), start=10): print(i, v) 141 | 10 0 142 | 11 1 143 | 12 2 144 | 145 | # Reversed `enumerate` 146 | >>> for i, v in reversed(list(enumerate(['a', 'b', 'c']))): print(i, v) 147 | 2 c 148 | 1 b 149 | 0 a 150 | ``` 151 | 152 | ## Tuples 153 | 154 | ```python 155 | >>> t = (1, 2, 'str') 156 | >>> type(t) 157 | 158 | >>> t 159 | (1, 2, 'str') 160 | >>> len(t) 161 | 3 162 | 163 | >>> t[0] = 10 # Tuples are immutable: `TypeError: 'tuple' object does not support item assignment` 164 | 165 | >>> a, b, c = t # Unpacking 166 | >>> a 167 | 1 168 | >>> b 169 | 2 170 | >>> c 171 | 'str' 172 | >>> a, _, _ = t # Unpacking: ignore second and third elements 173 | >>> a 174 | 1 175 | ``` 176 | 177 | ## Lists 178 | 179 | `Python` uses `Timsort` algorithm in `sort` and `sorted` (). 180 | 181 | ```python 182 | # Define a list 183 | >>> l = [1, 2, 'a'] 184 | >>> type(l) # 185 | >>> len(l) 186 | 3 187 | >>> l[0] # First element of the list 188 | 1 189 | >>> l[-1] # Last element of the list (equivalent to `l[len(l) - 1]`) 190 | 'a' 191 | 192 | # Slicing 193 | >>> l[:] # `l[start:end]` which means `[start, end)` 194 | [1, 2, 'a'] 195 | >>> l[0:len(l)] # `start` is 0 and `end` is `len(l)` if omitted 196 | [1, 2, 'a'] 197 | 198 | # Some useful methods 199 | >>> l.append('b') # `O(1)` 200 | >>> l.pop() # `O(1)` just for the last element 201 | 'b' 202 | >>> l.pop(0) # `O(n)` since list must be shifted 203 | 1 204 | >>> l 205 | [2, 'a'] 206 | >>> l.remove('a') # `O(n)` 207 | >>> l.remove('b') # `ValueError: list.remove(x): x not in list` 208 | >>> l 209 | [2] 210 | >>> l.index(2) # It returns first occurrence (`O(n)`) 211 | 0 212 | >>> l.index(12) # `ValueError: 12 is not in list` 213 | 214 | # More compact way to define a list 215 | >>> l = [0] * 5 216 | >>> l 217 | [0, 0, 0, 0, 0] 218 | >>> len(l) 219 | 5 220 | >>> [k for k in range(5)] 221 | [0, 1, 2, 3, 4] 222 | >>> [k for k in reversed(range(5))] 223 | [4, 3, 2, 1, 0] 224 | 225 | # Compact way to define 2D arrays 226 | >>> rows, cols = 2, 3 227 | >>> m = [[0] * cols for _ in range(rows)] 228 | >>> len(m) == rows 229 | True 230 | >>> all(len(m[k]) == cols for k in range(rows)) 231 | True 232 | 233 | # Built-in methods 234 | >>> l = [3, 1, 2, 0] 235 | >>> len(l) 236 | 4 237 | >>> min(l) 238 | 0 239 | >>> max(l) 240 | 3 241 | >>> sum(l) 242 | 6 243 | >>> any(v == 3 for v in l) 244 | True 245 | >>> any(v == 5 for v in l) 246 | False 247 | >>> all(v >= 0 for v in l) 248 | True 249 | 250 | # Sort list in-place (`sort`) 251 | >>> l = [10, 2, 0, 1] 252 | >>> l 253 | [10, 2, 0, 1] 254 | >>> l.sort() # It changes the original list 255 | >>> l 256 | [0, 1, 2, 10] 257 | >>> l.sort(reverse=True) # It changes the original list 258 | >>> l 259 | [10, 2, 1, 0] 260 | 261 | # Sort a list a return a new one (`sorted`) 262 | >>> l = [10, 2, 0, 1] 263 | >>> sorted(l) # It returns a new list 264 | [0, 1, 2, 10] 265 | >>> l # Original list is not sorted 266 | [10, 2, 0, 1] 267 | 268 | # Sort by a different key 269 | >>> students = [ 270 | ('Mark', 21), 271 | ('Luke', 20), 272 | ('Anna', 18), 273 | ] 274 | >>> sorted(students, key=lambda s: s[1]) # It returns a new list 275 | [('Anna', 18), ('Luke', 20), ('Mark', 21)] 276 | >>> students.sort(key=lambda s: s[1]) # In-place 277 | >>> students 278 | [('Anna', 18), ('Luke', 20), ('Mark', 21)] 279 | ``` 280 | 281 | ## Strings 282 | 283 | ```python 284 | >>> s = 'Hello, world!' 285 | >>> type(s) # 286 | >>> len(s) 287 | 13 288 | 289 | >>> s[0] = 'h' # Strings are immutable: `TypeError: 'str' object does not support item assignment` 290 | >>> s += ' Another string' # A new string will be created, so concatenation is quite slow 291 | 292 | >>> s = 'Hello' 293 | >>> l = list(s) 294 | >>> l 295 | ['H', 'e', 'l', 'l', 'o'] 296 | >>> l[0] = 'h' 297 | >>> ''.join(l) 298 | 'hello' 299 | 300 | >>> 'lo' in s 301 | True 302 | >>> ord('a') 303 | 97 304 | >>> chr(97) 305 | 'a' 306 | ``` 307 | 308 | ## Stacks 309 | 310 | ```python 311 | >>> stack = [] # We can use a normal list to simulate a stack 312 | 313 | >>> stack.append(0) # `O(1)` 314 | >>> stack.append(1) 315 | >>> stack.append(2) 316 | 317 | >>> len(stack) 318 | 3 319 | 320 | >>> stack[0] # Bottom of the stack 321 | 0 322 | >>> stack[-1] # Top of the stack 323 | 2 324 | 325 | >>> stack.pop() # `O(1)` 326 | 2 327 | >>> stack.pop() 328 | 1 329 | 330 | >>> len(stack) 331 | 1 332 | 333 | >>> stack.pop() 334 | 0 335 | 336 | >>> stack.pop() # `IndexError: pop from empty list` 337 | >>> stack[-1] # `IndexError: pop from empty list` 338 | ``` 339 | 340 | ## Queues 341 | 342 | ```python 343 | >>> from collections import deque 344 | 345 | >>> queue = deque() 346 | 347 | # Enqueue -> append() 348 | >>> queue.append(0) # `O(1)` 349 | >>> queue.append(1) 350 | >>> queue.append(2) 351 | 352 | >>> len(queue) 353 | 3 354 | 355 | >>> queue[0] # Head of the queue 356 | 0 357 | >>> queue[-1] # Tail of the queue 358 | 2 359 | 360 | # Dequeue -> popleft() 361 | >>> queue.popleft() # `O(1)` 362 | 0 363 | >>> queue.popleft() 364 | 1 365 | 366 | >>> len(queue) 367 | 1 368 | 369 | >>> queue.popleft() 370 | 2 371 | 372 | >>> len(queue) 373 | 0 374 | 375 | >>> queue.popleft() # `IndexError: pop from an empty deque` 376 | >>> queue[0] # `IndexError: pop from an empty deque` 377 | ``` 378 | 379 | ## Sets 380 | 381 | ```python 382 | >>> s = set() 383 | >>> s.add(1) 384 | >>> s.add(2) 385 | >>> s 386 | {1, 2} 387 | >>> len(s) 388 | 2 389 | >>> s.add(1) # Duplicate elements are not allowed per definition 390 | >>> s 391 | {1, 2} 392 | >>> s.add('a') # We can mix types 393 | >>> s 394 | {1, 2, 'a'} 395 | >>> 1 in s # `O(1)` 396 | True 397 | >>> s.remove(1) 398 | >>> s 399 | {2, 'a'} 400 | >>> s.remove(1) # `KeyError: 1` 401 | >>> s.discard(1) # It won't raise `KeyError` even if the element does not exist 402 | >>> s.pop() # Remove and return an arbitrary element from the set 403 | 2 404 | 405 | >>> s0 = {1, 2, 'a'} 406 | >>> s0 407 | {1, 2, 'a'} 408 | >>> s1 = set([1, 2, 'a']) 409 | >>> s1 410 | {1, 2, 'a'} 411 | 412 | >>> s0 = {1, 2} 413 | >>> s1 = {1, 3} 414 | >>> s0 | s1 415 | {1, 2, 3} 416 | >>> s0.union(s1) # New set will be returned 417 | {1, 2, 3} 418 | 419 | >>> s0 = {1, 2} 420 | >>> s1 = {1, 3} 421 | >>> s0 & s1 422 | {1} 423 | >>> s0.intersection(s1) # New set will be returned 424 | {1} 425 | 426 | >>> s0 = {1, 2} 427 | >>> s1 = {1, 3} 428 | >>> s0 - s1 429 | {2} 430 | >>> s0.difference(s1) 431 | {2} 432 | ``` 433 | 434 | ## Hash Tables 435 | 436 | ```python 437 | >>> d = {'a': 'hello, world', 'b': 11} # Equivalent to `dict(a='hello, world', b=11)` 438 | >>> type(d) 439 | 440 | >>> d 441 | {'a': 'hello, world', 'b': 11} 442 | 443 | >>> d.keys() 444 | dict_keys(['a', 'b']) 445 | >>> d.values() 446 | dict_values(['hello, world', 11]) 447 | >>> for k, v in d.items(): print(k, v) 448 | a hello, world 449 | b 11 450 | 451 | >>> 'a' in d # `O(1)` 452 | True 453 | >>> 1 in d 454 | False 455 | >>> d['a'] += '!' 456 | >>> d 457 | {'a': 'hello, world!', 'b': 11} 458 | >>> d[1] = 'a new element' 459 | >>> d 460 | {'a': 'hello, world!', 'b': 11, 1: 'a new element'} 461 | 462 | >>> d[0] += 10 # `KeyError: 0` 463 | >>> d.get(0, 1) # Return `1` as default value since key `0` does not exist 464 | 1 465 | >>> d.get(1, '?') # Key `1` exists, so the actual value will be returned 466 | 'a new element' 467 | >>> d.get(10) is None 468 | True 469 | ``` 470 | 471 | ## Heaps 472 | 473 | The following commands show how to work with a `min heap`. 474 | Currently, `Python` does not have public methods for the `max heap`. 475 | You can overcome this problem by applying one of the following strategies: 476 | 477 | 1. Invert the value of each number. So, for example, if you want to add 478 | 1, 2 and 3 in the min heap, you can `heappush` -3, -2 and -1. 479 | When you `heappop` you invert the number again to get the proper value. 480 | This solution clearly works if your domain is composed by numbers >= 0. 481 | 1. [Invert your object comparison](https://stackoverflow.com/a/40455775). 482 | 483 | ```python 484 | >>> import heapq 485 | 486 | >>> min_heap = [3, 2, 1] 487 | >>> heapq.heapify(min_heap) 488 | >>> min_heap 489 | [1, 2, 3] 490 | 491 | >>> min_heap = [] 492 | >>> heapq.heappush(min_heap, 3) # `O(log n)` 493 | >>> heapq.heappush(min_heap, 2) 494 | >>> heapq.heappush(min_heap, 1) 495 | 496 | >>> min_heap 497 | [1, 3, 2] 498 | >>> len(min_heap) 499 | >>> min_heap[0] 500 | 1 501 | >>> heapq.heappop(min_heap) # `O(log n)` 502 | 1 503 | >>> min_heap 504 | [2, 3] 505 | 506 | >>> heapq.heappop(min_heap) 507 | 2 508 | >>> heapq.heappop(min_heap) 509 | 3 510 | >>> heapq.heappop(min_heap) # `IndexError: index out of range` 511 | ``` 512 | 513 | ### collections 514 | 515 | Container data types in the ([collections package]()). 516 | 517 | #### collections.namedtuple 518 | 519 | ```python 520 | >>> from collections import namedtuple 521 | 522 | >>> Point = namedtuple('Point', 'x y') 523 | 524 | >>> p0 = Point(1, 2) 525 | >>> p0 526 | Point(x=1, y=2) 527 | >>> p0.x 528 | 1 529 | >>> p0.y 530 | 2 531 | 532 | >>> p1 = Point(x=1, y=2) 533 | >>> p0 == p1 534 | True 535 | 536 | # Python >= 3.6.1 537 | >>> from typing import NamedTuple 538 | 539 | >>> class Point(NamedTuple): 540 | x: int 541 | y: int 542 | 543 | >>> p0 = Point(1, 2) 544 | >>> p1 = Point(x=1, y=2) 545 | >>> p0 == p1 546 | True 547 | ``` 548 | 549 | #### collections.defaultdict 550 | 551 | ```python 552 | >>> from collections import defaultdict 553 | 554 | >>> d = defaultdict(int) 555 | >>> d['x'] += 1 556 | >>> d 557 | defaultdict(, {'x': 1}) 558 | >>> d['x'] += 2 559 | >>> d 560 | defaultdict(, {'x': 3}) 561 | >>> d['y'] += 10 562 | >>> d 563 | defaultdict(, {'x': 3, 'y': 10}) 564 | 565 | >>> d = defaultdict(list) 566 | >>> d['x'].append(1) 567 | >>> d['x'].append(2) 568 | >>> d 569 | defaultdict(, {'x': [1, 2]}) 570 | ``` 571 | 572 | #### collections.Counter 573 | 574 | ```python 575 | >>> from collections import Counter 576 | 577 | >>> c = Counter('abcabcaa') 578 | >>> c 579 | Counter({'a': 4, 'b': 2, 'c': 2}) 580 | >>> c.keys() 581 | dict_keys(['a', 'b', 'c']) 582 | >>> c.items() 583 | dict_items([('a', 4), ('b', 2), ('c', 2)]) 584 | >>> for k, v in c.items(): print(k, v) 585 | a 4 586 | b 2 587 | c 2 588 | >>> c['d'] # It acts as a `defaultdict` for missing keys 589 | 0 590 | ``` 591 | 592 | #### collections.OrderedDict 593 | 594 | Please, notice that, since `Python` `3.6`, 595 | the order of items in a dictionary is guaranteed to be preserved. 596 | 597 | ```python 598 | >>> from collections import OrderedDict 599 | 600 | >>> d = OrderedDict() 601 | 602 | >>> d['first'] = 1 603 | >>> d['second'] = 2 604 | >>> d['third'] = 3 605 | >>> d 606 | OrderedDict([('first', 1), ('second', 2), ('third', 3)]) 607 | 608 | >>> for k, v in d.items(): print(k, v) 609 | first 1 610 | second 2 611 | third 3 612 | ``` 613 | --------------------------------------------------------------------------------