From 7dfee269f150d879a7c2d47c3f7eff4b555c2576 Mon Sep 17 00:00:00 2001 From: "andy.boot" Date: Thu, 6 Apr 2017 23:02:33 +0100 Subject: [PATCH] Alternate flip_bit_challenge solution This solution is clearer Algorithm description updated for new solution. Comments added to code --- .../flip_bit/flip_bit_solution.ipynb | 120 +++++++----------- 1 file changed, 44 insertions(+), 76 deletions(-) diff --git a/bit_manipulation/flip_bit/flip_bit_solution.ipynb b/bit_manipulation/flip_bit/flip_bit_solution.ipynb index 6b7889f..3345e09 100644 --- a/bit_manipulation/flip_bit/flip_bit_solution.ipynb +++ b/bit_manipulation/flip_bit/flip_bit_solution.ipynb @@ -71,35 +71,25 @@ "source": [ "## Algorithm\n", "\n", - "* seen = []\n", - "* Build a list of sequence counts\n", - " * Look for 0's\n", - " * This will be 0 length if the input has trailing ones\n", - " * Add sequence length to seen\n", - " * Look for 1's\n", - " * Add sequence length to seen\n", - "* Find the largest sequence of ones looking at seen\n", - " * Loop through seen\n", - " * On each iteration of the loop, flip what we are looking for from 0 to 1 and vice versa\n", - " * If seen[i] represents 1's, continue, we only want to process 0's\n", - " * If this is our first iteration:\n", - " * max_result = seen[i+1] + 1 if seen[i] > 0\n", - " * continue\n", - " * If we are looking at leading zeroes (i == len(seen)-1):\n", - " * result = seen[i-1] + 1\n", - " * If we are looking at one zero:\n", - " * result = seen[i+1] + seen[i-1] + 1\n", - " * If we are looking at multiple zeroes:\n", - " * result = max(seen[i+1], seen[i-1]) + 1\n", - " * Update max_result based on result\n", + "* Overview:\n", + " * Use 2 counters. \n", + " * Each counter will track the number of 1's it has seen. \n", + " * When we see a 0 this indicates the end of the run for the long_counter and indicates the bit to flip for the short_counter. We then throw the long counter away, promote the short_counter to the long_counter and reset the short_counter\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 or implement one for a 32 bit number:\n", "\n", - " num % 0x100000000 >> n\n", + "* For each bit:\n", + " * If we see a '1' \n", + " * Increment both counters.\n", + " * If we see a '0' \n", + " * For the long_counter this indicates the end of the row of '1's. Compare our count to best (and update it if better).\n", + " * For the short_counter this indicates the bit we should flip from 0 to 1. Promote it to be the long_counter, it will continue to count bits as the long_counter\n", + " * Reset the short_counter to 0\n", " \n", + " \n", + "\n", "Complexity:\n", - "* Time: O(b)\n", - "* Space: O(b)" + "* Time: O(lg(n))\n", + "* Space: O(1)\n" ] }, { @@ -118,62 +108,37 @@ "outputs": [], "source": [ "class Bits(object):\n", - "\n", " MAX_BITS = 32\n", " \n", - " def _build_seen_list(self, num):\n", - " seen = []\n", - " looking_for = 0\n", - " count = 0\n", - " for _ in range(self.MAX_BITS):\n", - " if num & 1 != looking_for:\n", - " seen.append(count)\n", - " looking_for = not looking_for\n", - " count = 0\n", - " count += 1\n", - " num >>= 1\n", - " seen.append(count)\n", - " return seen\n", - " \n", " def flip_bit(self, num):\n", " if num is None:\n", - " raise TypeError('num cannot be None')\n", - " if num == -1:\n", - " return self.MAX_BITS\n", - " if num == 0:\n", - " return 1\n", - " seen = self._build_seen_list(num)\n", - " max_result = 0\n", - " looking_for = 0\n", - " for index, count in enumerate(seen):\n", - " result = 0\n", - " # Only look for zeroes\n", - " if looking_for == 1:\n", - " looking_for = not looking_for\n", - " continue\n", - " # First iteration, take trailing zeroes\n", - " # or trailing ones into account\n", - " if index == 0:\n", - " if count != 0:\n", - " # Trailing zeroes\n", - " try:\n", - " result = seen[index + 1] + 1\n", - " except IndexError:\n", - " result = 1\n", - " # Last iteration\n", - " elif index == len(seen) - 1:\n", - " result = 1 + seen[index - 1]\n", + " raise TypeError\n", + " count_no_bit_flip = 0\n", + " count_after_bit_flip = 0\n", + " best = 0\n", + " \n", + " # This handles negative numbers.\n", + " if num < 0:\n", + " num += 2**self.MAX_BITS\n", + " \n", + " for i in range(0, 32):\n", + " c = (num >> i) & 1\n", + " if c == 1:\n", + " count_no_bit_flip += 1\n", + " count_after_bit_flip += 1\n", " else:\n", - " # One zero\n", - " if count == 1:\n", - " result = seen[index + 1] + seen[index - 1] + 1\n", - " # Multiple zeroes\n", - " else:\n", - " result = max(seen[index + 1], seen[index - 1]) + 1\n", - " if result > max_result:\n", - " max_result = result\n", - " looking_for = not looking_for\n", - " return max_result" + " # If the count after the bit flip > best -> save it.\n", + " if count_after_bit_flip > best:\n", + " best = count_after_bit_flip\n", + " \n", + " # This counter now assumes we flipped this bit\n", + " count_after_bit_flip = count_no_bit_flip + 1\n", + " \n", + " # We start a new counter from this index.\n", + " count_no_bit_flip = 0\n", + " \n", + " return max(count_after_bit_flip, best)\n", + " " ] }, { @@ -210,6 +175,9 @@ " assert_raises(TypeError, bits.flip_bit, None)\n", " assert_equal(bits.flip_bit(0), 1)\n", " assert_equal(bits.flip_bit(-1), bits.MAX_BITS)\n", + " assert_equal(bits.flip_bit(-2), bits.MAX_BITS)\n", + " assert_equal(bits.flip_bit(-3), bits.MAX_BITS)\n", + " assert_equal(bits.flip_bit(-4), bits.MAX_BITS - 1)\n", " num = int('00001111110111011110001111110000', base=2)\n", " expected = 10\n", " assert_equal(bits.flip_bit(num), expected)\n",