将数字字符串转换为“二进制表示形式”

时间:2020-10-21 16:03:14

标签: performance go bitmask

我最近在hackerrank上制作了“中奖彩票”编码挑战。 https://www.hackerrank.com/challenges/winning-lottery-ticket/

想法是计算包含0-9的所有数字的两行的组合,在下面的示例中,总共包含5个组合。

56789
129300455 
5559948277
012334556 
123456879

此想法是为了更快地更改某些事物的表示形式,以检查是否包含所有数字。 表示示例: 1278-> 01100001100

使用上面的前两行的示例: 56789129300455-> 1111111111

当检查2行的连接中是否包含数字时,如果遇到零,我可以直接中止,因为那将不是全0-9的对。

此逻辑有效,但是在要比较的行数很多时会失败。

// Go code 
func winningLotteryTicket(tickets []string) int {

    counter := 0
    for i := 0; i < len(tickets); i++ {
        for j := i + 1; j < len(tickets); j++ {
            
            if err := bitMask(fmt.Sprintf("%v%v", tickets[i], tickets[j])); err == nil {
                counter++
             }

        }
    }
 

    return counter
}

func bitMask(s string) error {

    for i := 0; i <= 9; i++ {
        if !strings.Contains(s, strconv.Itoa(i)) {
            return errors.New("No Pair")
        } 
    }

    return nil
}

不确定此表示形式是否称为bitMaks,如果不正确,请纠正我,我将调整此职位。

从我的角度来看,在字符串的连接上没有改善性能的方法,因为我必须检查每个组合。

不确定在函数“ bitMask”处的字符串中是否包含数字。 您有一个更好的方法吗?

1 个答案:

答案 0 :(得分:0)

位掩码是整数,而不是一和零的字符串。之所以称为位掩码,是因为我们对这些整数的数值不感兴趣,而仅对位模式感兴趣。我们可以对整数使用按位运算,因为它们是在硬件中直接在CPU中实现的,所以它们真的很快。

这是一个将字符串转换为实际位掩码的函数,每个一位都表明字符串中存在特定数字:

func mask(s string) uint16 {
    // We need ten bits, one for each possible decimal digit in s, so int16 and
    // uint16 are the smallest possible integer types that fit. For bitmasks it
    // is typical to select an unsigned type because the sign bit doesn't have
    // any meaning. As I said earlier, mask's numerical value is irrelevant.
    var mask uint16

    for _, c := range s {
        switch c {
        case '0':
            mask |= 0b0000000001
        case '1':
            mask |= 0b0000000010
        case '2':
            mask |= 0b0000000100
        case '3':
            mask |= 0b0000001000
        case '4':
            mask |= 0b0000010000
        case '5':
            mask |= 0b0000100000
        case '6':
            mask |= 0b0001000000
        case '7':
            mask |= 0b0010000000
        case '8':
            mask |= 0b0100000000
        case '9':
            mask |= 0b1000000000
        }
    }

    return mask
}

这很冗长,但是很明显会发生什么。

请注意,二进制数字文字可以用移位代替:

  • 0b00000000011<<0相同(1向左偏移零次)
  • 0b00000000101<<1相同(1向左移动了一次
  • 0b00000001001<<2相同(1向左移动两次),依此类推

使用此信息,并利用字节'0'至'9'本身就是整数(十进制的48至57,由它们在ASCII table中的位置所给定的事实),我们可以缩短此函数像这样:

func mask(s string) uint16 { 
    var mask uint16

    for _, c := range s {
        if '0' <= c && c <= '9' {
            mask |= 1 << (c - '0')
        }
    }

    return mask
}

要检查两条线,我们要做的就是对这两条线的掩码进行“或”运算,然后与0b1111111111进行比较(即检查是否设置了所有十位):

package main

import "fmt"

func main() {
    a := "56789"
    b := "129300455"

    mA := mask(a)
    mB := mask(b)

    fmt.Printf("mask(%11q) = %010b\n", a, mA)
    fmt.Printf("mask(%11q) = %010b\n", b, mB)
    fmt.Printf("combined          = %010b\n", mA|mB)
    fmt.Printf("all digits present: %v\n", mA|mB == 0b1111111111)
}

func mask(s string) uint16 {
    var mask uint16
    
    for _, c := range s {
        if '0' <= c && c <= '9' {
            mask |= 1 << (c - '0')
        }
    }

    return mask
}
mask(    "56789") = 1111100000
mask("129300455") = 1000111111
combined          = 1111111111
all digits present: true

在操场上尝试:https://play.golang.org/p/mr1KqnC9phB