|
| 1 | +# Introduction |
| 2 | + |
| 3 | +The key to this exercise is to: |
| 4 | + |
| 5 | +- Create a suitable collection object to hold the values. |
| 6 | +- Keep track of size as elements are added and removed. |
| 7 | + |
| 8 | +## General Guidance |
| 9 | + |
| 10 | +Approaches to this exercise vary from easy but rather boring, to complex but educational. |
| 11 | + |
| 12 | +It would be useful to think about what you want from completing the exercise, then choose an appropriate collection class that fits your aims. |
| 13 | + |
| 14 | + |
| 15 | +## Exception classes |
| 16 | + |
| 17 | +All the approaches rely on being able to raise two custom exceptions, with suitable error messages. |
| 18 | + |
| 19 | +```python |
| 20 | +class BufferFullException(BufferError): |
| 21 | + """Exception raised when CircularBuffer is full.""" |
| 22 | + |
| 23 | + def __init__(self, message): |
| 24 | + self.message = message |
| 25 | + |
| 26 | +class BufferEmptyException(BufferError): |
| 27 | + """Exception raised when CircularBuffer is empty.""" |
| 28 | + |
| 29 | + def __init__(self, message): |
| 30 | + self.message = message |
| 31 | +``` |
| 32 | + |
| 33 | +Code for these error handling scenarios is always quite similar, so for brevity this aspect will be omitted from the various approaches to the exercise. |
| 34 | + |
| 35 | + |
| 36 | +## Approach: Using built-in types |
| 37 | + |
| 38 | +Python has an exceptionally flexible and widely-used `list` type. |
| 39 | +Most submitted solutions to `Circular Buffer` are based on this data type. |
| 40 | + |
| 41 | +A less versatile variants include [`bytearray`][bytearray] and [`array.array`][array.array]. |
| 42 | + |
| 43 | +`bytearray`s are similar to `list`s in many ways, but they are limited to holding only bytes (_represented as integers in the range `0 <= n < 256`_). |
| 44 | + |
| 45 | +For details, see the [built-in types][approaches-built-in] approach. |
| 46 | + |
| 47 | + |
| 48 | +Finally, [`memoryview`s][memoryview] allow for direct access to the binary data (_ without copying_) of Python objects that support the [`Buffer Protocol`][buffer-protocol]. |
| 49 | + `memoryview`s can be used to directly access the underlying memory of types such as `bytearray`, `array.array`, `queue`, `dequeue`, and `list` as well as working with [ctypes][ctypes] from outside libraries and C [structs][struct]. |
| 50 | + |
| 51 | + For additional information on the `buffer protocol`, see [Emulating Buffer Types][emulating-buffer-types] in the Python documentation. |
| 52 | + As of Python `3.12`, the abstract class [collections.abc.Buffer][abc-Buffer] is also available for classes that provide the [`__buffer__()`][dunder-buffer] method and implement the `buffer protocol`. |
| 53 | + |
| 54 | + |
| 55 | +## Approach: Using collection classes from the standard library |
| 56 | + |
| 57 | +A circular buffer is a type of fixed-size queue, and Python provides various implementations of this very useful type of collection. |
| 58 | + |
| 59 | +- The [`queue`][queue-module] module contains the [`Queue`][Queue-class] class, which can be initialized with a maximum capacity. |
| 60 | +- The [`collections`][collections-module] module contains a [`deque`][deque-class] class (short for Double Ended QUEue), which can also be set to a maximum capacity. |
| 61 | +- The [`array`][array.array] module contains an [`array`][array-array] class that is similar to Python's built-in `list`, but is limited to a single datatype (_available datatypes are mapped to C datatypes_). |
| 62 | +This allows values to be stored in a more compact and efficient fashion. |
| 63 | + |
| 64 | + |
| 65 | +For details, see the [standard library][approaches-standard-library] approach. |
| 66 | + |
| 67 | + |
| 68 | +## Which Approach to Use? |
| 69 | + |
| 70 | +Anyone just wanting to use a circular buffer to get other things done and is not super-focused on performance is likely to pick a `Queue` or `deque`, as either of these will handle much of the low-level bookkeeping. |
| 71 | + |
| 72 | +For a more authentic learning experience, using a `list` will provide practice in keeping track of capacity, with `bytearray` or `array.array` taking the capacity and read/write tracking a stage further. |
| 73 | + |
| 74 | + |
| 75 | +For a really deep dive into low-level Python operations, you can explore using `memoryview`s into `bytearray`s or [`numpy` arrays][numpy-arrays], or customize your own `buffer protocol`-supporting Python object, `ctype` or `struct`. |
| 76 | +Some 'jumping off' articles for this are [circular queue or ring buffer (Python and C)][circular-buffer], [memoryview Python Performance][memoryview-py-performance], and [Less Copies in Python with the buffer protocol and memoryviews][less-copies-in-Python]. |
| 77 | + |
| 78 | + |
| 79 | +In reality, anyone wanting to get a deeper understanding of how these collection structures work "from scratch" might do even better to try solving the exercise in a statically-typed system language such as C, Rust, or even try an assembly language like MIPS. |
| 80 | + |
| 81 | +[Queue-class]: https://docs.python.org/3.11/library/queue.html#queue.Queue |
| 82 | +[abc-Buffer]: https://docs.python.org/3/library/collections.abc.html#collections.abc.Buffer |
| 83 | +[approaches-built-in]: https://exercism.org/tracks/python/exercises/circular-buffer/approaches/built-in-types |
| 84 | +[approaches-standard-library]: https://exercism.org/tracks/python/exercises/circular-buffer/approaches/standard-library |
| 85 | +[array-array]: https://docs.python.org/3.11/library/array.html#array.array |
| 86 | +[array.array]: https://docs.python.org/3.11/library/array.html#module-array |
| 87 | +[buffer-protocol]: https://docs.python.org/3/c-api/buffer.html |
| 88 | +[bytearray]: https://docs.python.org/3/library/stdtypes.html#bytearray |
| 89 | +[circular-buffer]: https://towardsdatascience.com/circular-queue-or-ring-buffer-92c7b0193326 |
| 90 | +[collections-module]: https://docs.python.org/3.11/library/collections.html |
| 91 | +[ctypes]: https://docs.python.org/3/library/ctypes.html |
| 92 | +[deque-class]: https://docs.python.org/3.11/library/collections.html#collections.deque |
| 93 | +[dunder-buffer]: https://docs.python.org/3/reference/datamodel.html#object.__buffer__ |
| 94 | +[emulating-buffer-types]: https://docs.python.org/3/reference/datamodel.html#emulating-buffer-types |
| 95 | +[less-copies-in-Python]: https://eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol-and-memoryviews |
| 96 | +[memoryview-py-performance]: https://prrasad.medium.com/memory-view-python-performance-improvement-method-c241a79e9843 |
| 97 | +[memoryview]: https://docs.python.org/3/library/stdtypes.html#memoryview |
| 98 | +[numpy-arrays]: https://numpy.org/doc/stable/reference/generated/numpy.array.html |
| 99 | +[queue-module]: https://docs.python.org/3.11/library/queue.html |
| 100 | +[struct]: https://docs.python.org/3/library/struct.html |
0 commit comments