首页 > Windows开发 > 详细

enjoy games —— Finding Binary Gaps In C#

时间:2021-05-08 16:28:26      阅读:21      评论:0      收藏:0      [点我收藏+]

问题:

binary gap within a positive integer N is any maximal sequence of consecutive zeros that is surrounded by ones at both ends in the binary representation of N.

For example, number 9 has binary representation 1001 and contains a binary gap of length 2. The number 529 has binary representation 1000010001 and contains two binary gaps: one of length 4 and one of length 3. The number 20 has binary representation 10100 and contains one binary gap of length 1. The number 15 has binary representation 1111 and has no binary gaps. The number 32 has binary representation 100000 and has no binary gaps.

Write a function:

class Solution { public int solution(int N); }

that, given a positive integer N, returns the length of its longest binary gap. The function should return 0 if N doesn‘t contain a binary gap.

For example, given N = 1041 the function should return 5, because N has binary representation 10000010001 and so its longest binary gap is of length 5. Given N = 32 the function should return 0, because N has binary representation ‘100000‘ and thus no binary gaps.

Write an efficient algorithm for the following assumptions:

  • N is an integer within the range [1..2,147,483,647].

答案:

using System;
// you can also use other imports, for example:
// using System.Collections.Generic;

// you can write to stdout for debugging purposes, e.g.
// Console.WriteLine("this is a debug message");

class Solution {
    public int solution(int N) {
        //Console.WriteLine(Convert.ToString(N, 2));
        // write your code in C# 6.0 with .NET 4.5 (Mono)
        // trackers
        int max_gap = 0;
        int gap = 0;
        int mask = 1;
        // shift until the first set bit

        while ((mask & N) == 0 && mask != 0)
        {
            mask <<= 1;
            //Console.WriteLine("mask:"+Convert.ToString(mask, 2));
        }

        // shift one more time to skip it
        // this avoid a duplicate comparison on the next step
        mask <<= 1;
        //Console.WriteLine("mask:"+Convert.ToString(mask, 2));

        // we are either at the first set bit or have run out of bits
        for (; mask != 0; mask <<= 1)
        {
            // check if the current bit is set
            if ((mask & N) == mask)
            {
                // check if the current gap is greater than the max candidate
                if (gap > max_gap)
                {
                    // promote the current gap count to max candidate
                    max_gap = gap;
                   // Console.WriteLine("max_gap:"+max_gap);
                }

                // close the running gap count
                gap = 0;
                //Console.WriteLine("gap:"+gap);
            }
            else
            {
                // if zero then increase the running gap count
                ++gap;
                //Console.WriteLine("gap:"+gap);
            }
        }
        // at this point we have the greatest gap length or zero
        //Console.WriteLine("max_gap:"+max_gap);
        return max_gap;


    }
}

  

解析:

https://jorgecandeias.github.io/2019/02/13/algorithm-finding-binary-gaps-in-csharp/

 

Variations of the binary gap problem are a classic at coding challenge sites nowadays.

This easy-to-solve, not-so-obvious-to-master problem can highlight how a programmer is aware of binary manipulation and how it affects performance.

This article describes two solutions for a variation of this problem and how their performance stacks against each other.

The Problem

Consider some integer, for example 14512.

Its binary representation is 11100010110000, with the most significant bit first.

binary gap is a sequence of consecutive bits of the same value (ones or zeros) that are surrounded by bits of opposite values. For example, a zero binary gap is a sequence of unset bits (zeroes) that are surrounded by set bits (ones).

The number 14512, or 11100010110000 in binary, has two zero binary gaps, one of length 3 and one of length 1.

11100010110000
   ^^^ ^

The right-most sequence of 4 unset bits does not count, as it is not surrounded by set bits on both sides.

maximum binary gap length is thus the length of the longest binary gap within the number’s binary representation.

This concept may look simple at first, but variations of it become interesting in the domain of compression algorithms that look for long chunks of similar data.

It is also a fine example of how not all O(N)-ish algorithms are born the same.

The precise way one writes code to achieve something can have a dramatic impact on how fast that code achieves that end.

Let’s see an example of that by attempting to find the length of the longest binary gap in any given number.

A Simple Approach

One simple approach can be to convert the integer into its binary representation as a string, and then iterate the characters one-by-one.

Here is a C# algorithm that performs just that.

public static int BinaryGapMaxLengthByIterating(int value, BinaryGapBit bit = BinaryGapBit.Unset)
{
    // trackers
    int max_gap = 0;
    int gap = 0;
    char mask = bit == BinaryGapBit.Unset ? ‘1‘ : ‘0‘;

    // convert the value to a binary string
    string binary = Convert.ToString(value, 2);

    // iterate until the first set bit
    int i = 0;
    while (i < binary.Length && binary[i] != mask)
    {
        ++i;
    }

    // we are either at the first set bit or have run out of bits
    for (; i < binary.Length; ++i)
    {
        // check if the current bit is set
        if (binary[i] == mask)
        {
            // check if the current gap is greater than the max candidate
            if (gap > max_gap)
            {
                // promote the current gap count to max candidate
                max_gap = gap;
            }

            // close the running gap count
            gap = 0;
        }
        else
        {
            // if zero then increase the running gap count
            ++gap;
        }
    }

    // at this point we have the greatest gap length or zero
    return max_gap;
}

public enum BinaryGapBit
{
    /// <summary>
    /// Look for gaps where the bits are unset.
    /// </summary>
    Unset = 0,

    /// <summary>
    /// Look for gaps where the bits are set.
    /// </summary>
    Set = 1
}

The algorithm above allows finding gaps of either zeroes or ones via the BinaryGapBit parameter, without affecting the iteration itself.

This is how it works for a zero-based gap:

  • Convert the integer number to its binary representation as a string.
  • Iterate through characters until the first ‘1‘.
  • For each character in the string…
    • Is it ‘1‘? If so…
      • If the current gap length is longer than the maximum gap length, then promote it to the maximum gap length.
      • Reset the current gap length.
    • Is it ‘0‘? If so, increment the current gap length.
  • Return the maximum gap length.

It is a simple to understand algorithm, with apparent time-complexity O(N), if you consider an arbitrary number of bits to cover.

However, that apparent O(N) is hit by a curve ball from Convert.ToString(value, 2);, of which you can see the code here.

Looking at that source code, it becomes difficult to discern the actual time-complexity of this solution without empirical testing over a larger dataset than a single integer.

This is in addition to the string allocation by string binary = ... on the algorithm itself.

If one is writing performance-sensitive code, this can be one allocation too many.

An Efficient Approach

One efficient approach comes from realizing that one does not need to convert anything to binary at all.

Integer numbers are already binary values to start with, and many languages allow for some form of direct binary manipulation.

The algorithm below exploits this by creating a binary mask to check wheather a given bit is set or not, and then shifting it to simulate an iteration through all the bits.

public static int BinaryGapMaxLengthByShifting(this int value, BinaryGapBit bit = BinaryGapBit.Unset)
{
    // trackers
    int max_gap = 0;
    int gap = 0;
    int mask = 1;

    // if searching for gaps of ones just flip the bits in the search space
    if (bit == BinaryGapBit.Set)
    {
        value = ~value;
    }

    // shift until the first set bit
    while ((mask & value) == 0 && mask != 0)
    {
        mask <<= 1;
    }

    // shift one more time to skip it
    // this avoid a duplicate comparison on the next step
    mask <<= 1;

    // we are either at the first set bit or have run out of bits
    for (; mask != 0; mask <<= 1)
    {
        // check if the current bit is set
        if ((mask & value) == mask)
        {
            // check if the current gap is greater than the max candidate
            if (gap > max_gap)
            {
                // promote the current gap count to max candidate
                max_gap = gap;
            }

            // close the running gap count
            gap = 0;
        }
        else
        {
            // if zero then increase the running gap count
            ++gap;
        }
    }

    // at this point we have the greatest gap length or zero
    return max_gap;
}

While the algorithm above is a bit harder to read that the previous, its behaviour still follows the same train of thought.

Step 1 - Find First Set Bit

  • Create a mask of value 1 - this translates to binary 00000000000001 (plus other 18 other zeroes to left for a 32-bit integer).
  • While the mask is not zero…
    • Apply a binary & (AND) operation between the mask and the value to see if the bit at the mask position is set.
    • If yes, exit the loop.
    • If not, shift the mask to the left and continue the loop.

The first iteration does not find a set bit in the last position because the & operation between both returns zero.

Value: 11100010110000
 Mask: 00000000000001
 --------------------
  AND: 00000000000000
                    ^

We therefore shift the mask one bit to the left with mask <<= 1 and try again…

Value: 11100010110000
 Mask: 00000000000010
 --------------------
  AND: 00000000000000
                   ^

Still no luck, so we shift and test a few more times. This time, the & operation returns a value that is equal to mask itself. This is our tell that we have found the first set bit.

Value: 11100010110000
 Mask: 00000000010000
 --------------------
  AND: 00000000010000
                ^--

We thus stop fast-scanning for a starting point and start counting gaps. We also shift the mask one bit further as there is no need to test the current bit again.

Value: 11100010110000
 Mask: 00000000100000
               ^

Step 2 - Count Gaps

This is also similiar to the simple approach but now we use binary shifting.

  • While the mask is not zero…
  • Test the mask against the value with the & (AND) operator.
    • If the bit is set…
      • If the current gap length is longer than the maximum gap length, promote it to the maximum gap length.
      • Reset the current gap length.
    • If the bit is not set, increment the current gap length.
  • Return the maximum gap length.

This is what it looks like…

The first test finds a set bit at the mask position. We therefore close the current gap count, which is still zero at this point.

Value: 11100010110000
 Mask: 00000000100000
 --------------------
  AND: 00000000100000
               ^

The next test returns zero. This means we can starting counting the current gap length.

Value: 11100010110000
 Mask: 00000001000000
 --------------------
  AND: 00000000000000
              ^

The next test returns the mask value. This means we found a set bit, so we take the current gap count of one, and promote it the maximum.

Value: 11100010110000
 Mask: 00000010000000
 --------------------
  AND: 00000010000000
             ^

We rinse and repeat a few more times, which will put the maximum gap length at 3 - the length of the gap between the fourth and sixth bit in the representation below.

Value: 11100010110000
 Mask: 10000000000000
 --------------------
  AND: 10000000000000
       ^-----

As we shift the mask to the left one last time, its set bit disappears, and the mask value itself becomes zero. This is our tell to stop the algortihm and return the current maximum length of 3.

Value: 11100010110000
 Mask: 00000000000000

Performance

What look like very similar algorithms can show dramatic performance differences.

This is a benchmark of the sample bit-shifting approach against the string-iteration approach as a baseline:

MethodMeanErrorStdDevRatio
BinaryGapMaxLengthByIterating 658.9 ns 1.1043 ns 1.0330 ns 1.00
BinaryGapMaxLengthByShifting 144.2 ns 0.8616 ns 0.8060 ns 0.22

You can run the benchmark on your own machine from the Quicker repository.

The bit-shifting algorithm, by saving on the allocation, runs in less than a quarter of the time, or over four times faster… even for a single integer.

Takeaway

It pays off to obsess about performance, even in simple cases like this.

Through the compounding effect, a better algorithm can make a significant difference to our systems, our users and our business, if we just keep looking for things to improve.

And that is what problems such as this attempt to highlight.

 

enjoy yourself:

https://app.codility.com/programmers/lessons/1-iterations/binary_gap/start/

 

enjoy games —— Finding Binary Gaps In C#

原文:https://www.cnblogs.com/panpanwelcome/p/14744000.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!