{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "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)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Solution Notebook" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Problem: Given a positive integer, get the next largest number and the next smallest number with the same number of 1's as the given number.\n", "\n", "* [Constraints](#Constraints)\n", "* [Test Cases](#Test-Cases)\n", "* [Algorithm](#Algorithm)\n", "* [Code](#Code)\n", "* [Unit Test](#Unit-Test)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Constraints\n", "\n", "* Is the output a positive int?\n", " * Yes\n", "* Can we assume the inputs are valid?\n", " * No\n", "* Can we assume this fits memory?\n", " * Yes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Test Cases\n", "\n", "* None -> Exception\n", "* 0 -> Exception\n", "* negative int -> Exception\n", "* General case:\n", "
\n",
    "    * Input:         0000 0000 1101 0111\n",
    "    * Next largest:  0000 0000 1101 1011\n",
    "    * Next smallest: 0000 0000 1100 1111\n",
    "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Algorithm\n", "\n", "### get_next_largest\n", "\n", "* Find the right-most non trailing zero, call this index\n", " * We'll use a mask of 1 and do a logical right shift on a copy of num to examine each bit starting from the right\n", " * Count the number of zeroes to the right of index\n", " * While num & 1 == 0 and num_copy != 0:\n", " * Increment number of zeroes\n", " * Logical shift right num_copy\n", " * Count the number of ones to the right of index\n", " * While num & 1 == 1 and num_copy != 0:\n", " * Increment number of ones\n", " * Logical shift right num_copy\n", " * The index will be the sum of number of ones and number of zeroes\n", " * Set the index bit\n", " * Clear all bits to the right of index\n", " * Set bits starting from 0\n", " * Only set (number of ones - 1) bits because we set index to 1\n", "\n", "We should make a note that Python does not have a logical right shift operator built in. We can either use a positive number of implement one for a 32 bit number:\n", "\n", " num % 0x100000000 >> n\n", "\n", "### get_next_smallest\n", "\n", "* The algorithm for finding the next smallest number is very similar to finding the next largest number\n", " * Instead of finding the right-most non-trailing zero, we'll find the right most non-trailing one and clear it\n", " * Clear all bits to the right of index\n", " * Set bits starting at 0 to the number of ones to the right of index, plus one\n", "\n", "Complexity:\n", "* Time: O(b), where b is the number of bits in num\n", "* Space: O(b), where b is the number of bits in num" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Code" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "class Bits(object):\n", "\n", " def get_next_largest(self, num):\n", " if num is None:\n", " raise TypeError('num cannot be None')\n", " if num <= 0:\n", " raise ValueError('num cannot be 0 or negative')\n", " num_ones = 0\n", " num_zeroes = 0\n", " num_copy = num\n", " # We'll look for index, which is the right-most non-trailing zero\n", " # Count number of zeroes to the right of index\n", " while num_copy != 0 and num_copy & 1 == 0:\n", " num_zeroes += 1\n", " num_copy >>= 1\n", " # Count number of ones to the right of index\n", " while num_copy != 0 and num_copy & 1 == 1:\n", " num_ones += 1\n", " num_copy >>= 1\n", " # Determine index and set the bit\n", " index = num_zeroes + num_ones\n", " num |= 1 << index\n", " # Clear all bits to the right of index\n", " num &= ~((1 << index) - 1)\n", " # Set bits starting from 0\n", " num |= ((1 << num_ones - 1) - 1)\n", " return num\n", "\n", " def get_next_smallest(self, num):\n", " if num is None:\n", " raise TypeError('num cannot be None')\n", " if num <= 0:\n", " raise ValueError('num cannot be 0 or negative')\n", " num_ones = 0\n", " num_zeroes = 0\n", " num_copy = num\n", " # We'll look for index, which is the right-most non-trailing one\n", " # Count number of zeroes to the right of index\n", " while num_copy != 0 and num_copy & 1 == 1:\n", " num_ones += 1\n", " num_copy >>= 1\n", " # Count number of zeroes to the right of index\n", " while num_copy != 0 and num_copy & 1 == 0:\n", " num_zeroes += 1\n", " num_copy >>= 1\n", " # Determine index and clear the bit\n", " index = num_zeroes + num_ones\n", " num &= ~(1 << index)\n", " # Clear all bits to the right of index\n", " num &= ~((1 << index) - 1)\n", " # Set bits starting from 0\n", " num |= (1 << num_ones + 1) - 1\n", " return num" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Unit Test" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting test_get_next_largest.py\n" ] } ], "source": [ "%%writefile test_get_next_largest.py\n", "import unittest\n", "\n", "\n", "class TestBits(unittest.TestCase):\n", "\n", " def test_get_next_largest(self):\n", " bits = Bits()\n", " self.assertRaises(Exception, bits.get_next_largest, None)\n", " self.assertRaises(Exception, bits.get_next_largest, 0)\n", " self.assertRaises(Exception, bits.get_next_largest, -1)\n", " num = int('011010111', base=2)\n", " expected = int('011011011', base=2)\n", " self.assertEqual(bits.get_next_largest(num), expected)\n", " print('Success: test_get_next_largest')\n", "\n", " def test_get_next_smallest(self):\n", " bits = Bits()\n", " self.assertRaises(Exception, bits.get_next_smallest, None)\n", " self.assertRaises(Exception, bits.get_next_smallest, 0)\n", " self.assertRaises(Exception, bits.get_next_smallest, -1)\n", " num = int('011010111', base=2)\n", " expected = int('011001111', base=2)\n", " self.assertEqual(bits.get_next_smallest(num), expected)\n", " print('Success: test_get_next_smallest')\n", "\n", "def main():\n", " test = TestBits()\n", " test.test_get_next_largest()\n", " test.test_get_next_smallest()\n", "\n", "\n", "if __name__ == '__main__':\n", " main()" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Success: test_get_next_largest\n", "Success: test_get_next_smallest\n" ] } ], "source": [ "%run -i test_get_next_largest.py" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 1 }