#!/usr/bin/env python # coding: utf-8 # This notebook was prepared by [Donne Martin](https://github.com/donnemartin). Source and license info is on [GitHub](https://github.com/donnemartin/interactive-coding-challenges). # # Solution Notebook # ## Problem: Implement the method draw_line(screen, width, x1, x2) where screen is a list of bytes, width is divisible by 8, and x1, x2 are absolute pixel positions. # # * [Constraints](#Constraints) # * [Test Cases](#Test-Cases) # * [Algorithm](#Algorithm) # * [Code](#Code) # * [Unit Test](#Unit-Test) # ## Constraints # # * Can we assume the inputs are valid? # * No # * For the output, do we set corresponding bits in screen? # * Yes # * Can we assume this fits memory? # * Yes # ## Test Cases # # * Invalid inputs -> Exception # * screen is empty # * width = 0 # * any input param is None # * x1 or x2 is out of bounds # * General case for len(screen) = 20, width = 32: # * x1 = 2, x2 = 6 # * screen[0] = int('00111110', base=2) # * x1 = 68, x2 = 80 # * screen[8], int('00001111', base=2) # * screen[9], int('11111111', base=2) # * screen[10], int('10000000', base=2) # ## Algorithm # # * Set start offset to x1 % 8 # * Set end offset to x2 % 8 # * Determine where the first and last full bytes are # * First full byte = x1 // 8 # * Increment by 1 if start offset != 0 # * Last full byte = x2 // 8 # * Decrement by 1 if end offset != 7 # * Fill the bytes with 1111 1111 # * If x1 and x2 are in the same byte # #
#     x1 = 2
#     x2 = 6
# 
#     index  0123 4567
#     byte   0011 1110
# 
#     We want to build left and right masks such that:
# 
#     left   0011 1111
#     right  1111 1110
#     ----------------
#            0011 1110  left & right
# 
#     Build the left mask:
# 
#     left   0000 0001  1
#            0100 0000  1 << (8 - x1)
#            0011 1111  (1 << (8 - x1)) - 1
# 
#     Build the right mask:
# 
#     right  0000 0000  1
#            0000 0010  1 << (8 - x2 - 1)
#            0000 0001  (1 << (8 - x2 - 1) - 1
#            1111 1110  ~((1 << (8 - x2 - 1) - 1)
# 
#     Combine the left and right masks:
# 
#     left   0011 1111
#     right  1111 1110
#     ----------------
#            0011 1110  left & right
# 
#     Set screen[x1//8] or screen[x2//8] |= mask
# 
# * Else #
#     If our starting partial byte is:
#            0000 1111
# 
#     Build start mask:
#     
#     start  0000 0001  1
#            0001 0000  1 << 1 << start offset
#            0000 1111  (1 << 1 << start offset) - 1
# 
#     If our ending partial byte is:
#            1111 1100
# 
#     Build end mask:
#     
#     end    1000 0000  0x10000000
#            1111 1100  0x10000000 >> end offset
# 
#     Set screen[x1//8] |= start mask
#     Set screen[x2//8] |= end mask
# 
# # Complexity: # * Time: O(length of screen) # * Space: O(1) # ## Code # In[1]: class BitsScreen(object): def draw_line(self, screen, width, x1, x2): if None in (screen, width, x1, x2): raise TypeError('Invalid argument: None') if not screen or not width: raise ValueError('Invalid arg: Empty screen or width') MAX_BIT_VALUE = len(screen) * 8 if x1 < 0 or x2 < 0 or x1 >= MAX_BIT_VALUE or x2 >= MAX_BIT_VALUE: raise ValueError('Invalid arg: x1 or x2 out of bounds') start_bit = x1 % 8 end_bit = x2 % 8 first_full_byte = x1 // 8 if start_bit != 0: first_full_byte += 1 last_full_byte = x2 // 8 if end_bit != (8 - 1): last_full_byte -= 1 for byte in range(first_full_byte, last_full_byte + 1): screen[byte] = int('11111111', base=2) start_byte = x1 // 8 end_byte = x2 // 8 if start_byte == end_byte: left_mask = (1 << (8 - start_bit)) - 1 right_mask = ~((1 << (8 - end_bit - 1)) - 1) mask = left_mask & right_mask screen[start_byte] |= mask else: start_mask = (1 << (8 - start_bit)) - 1 end_mask = 1 << (8 - end_bit - 1) screen[start_byte] |= start_mask screen[end_byte] |= end_mask # ## Unit Test # In[2]: get_ipython().run_cell_magic('writefile', 'test_draw_line.py', "import unittest\n\n\nclass TestBitsScreen(unittest.TestCase):\n\n def test_draw_line(self):\n bits_screen = BitsScreen()\n screen = []\n for _ in range(20):\n screen.append(int('00000000', base=2))\n bits_screen.draw_line(screen, width=32, x1=68, x2=80)\n self.assertEqual(screen[8], int('00001111', base=2))\n self.assertEqual(screen[9], int('11111111', base=2))\n self.assertEqual(screen[10], int('10000000', base=2))\n bits_screen.draw_line(screen, width=32, x1=2, x2=6)\n self.assertEqual(screen[0], int('00111110', base=2))\n bits_screen.draw_line(screen, width=32, x1=10, x2=13)\n self.assertEqual(screen[1], int('00111100', base=2))\n print('Success: test_draw_line')\n\n\ndef main():\n test = TestBitsScreen()\n test.test_draw_line()\n\n\nif __name__ == '__main__':\n main()\n") # In[3]: get_ipython().run_line_magic('run', '-i test_draw_line.py')