AOC第6天 - 调整麻烦
#编程 #教程 #go #adventofcode

调整麻烦

Question
最后,我们搬出营地!
当我们开始离开营地时,其中一个精灵为我们提供了一个通信设备,这不足为奇,我们得到了唯一的故障设备...

设备接收从字符列表构建的信号流,为了修复我们的设备,我们需要能够找到 启动启动标记

第1部分

我们被告知,数据包的开始定义为一系列连续四个不同的字符
鉴于我们的签名,我们需要确定在出现第一个标记之前应该处理多少个字符,或者换句话说,在我们的标记末尾索引。

例如,mjq<start>jpqm<end>gbljsphdztnvjfqwrcgsmlb

<start><end>代表标记的开始和结尾

意味着答案应为 7 m索引)

基本上我们需要完成的工作是连续找到4个连续的字符,有多种方法可以做到这一点,但是我们将使用Set来计算给定范围内的唯一字符数量,即i-> i+4 << br> 在该范围的每个位置,我们将采用字符并将其添加到我们的集合中,如果设定大小为4,则在范围的末尾,我们获得了赢家,我们可以返回当前的索引i + 4

set是一种数据结构,可以保证每个键的唯一性,在GO中,没有内置类型,但是我们可以轻松地使用map[rune]bool类型创建一个集合,以使其更有趣,让我们创建创建通用集软件包

我们将创建一个名为set的软件包,其中一个名为SimpleSet的结构将支持一组基本的操作

  • 添加
  • 大小

这是我们的SimpleSet
的代码

package set

type SimpleSet[T comparable] struct {
    items map[T]bool
}

func NewSimpleSet[T comparable](values ...T) *SimpleSet[T] {
    m := make(map[T]bool)

    return &SimpleSet[T]{
        items: m,
    }
}

func (s *SimpleSet[T]) Add(key T) {
    s.items[key] = true
}

func (s *SimpleSet[T]) Has(key T) bool {
    _, ok := s.items[key]
    return ok
}

func (s *SimpleSet[T]) Size() int {
    return len(s.items)
}

我正在使用仿制药使此组在未来几天内有用,您可以阅读更多有关它的信息。

我没有得到什么,直到最近才有仿制药,想象一下为每种类型重复我们的设置相同的代码!


配备了我们的新套装,让我们解决第1部分!

func Part1(raw string) int {
    for i := range raw {
        set := set.NewSimpleSet[rune]()
        for _, c := range raw[i : i+4] {
            set.Add(c)
        }

        if set.Size() == 4 {
            return i + 4
        }
    }
    return -1
}

第2部分

完全像第1部分,但现在标记需要连续14个字符
我们可以采用我们的第1部分解决方案,并接受偏移以适合这两个部分

func Part1(raw string, offset int) int {
    for i := range raw {
        set := set.NewSimpleSet[rune]()
        for _, c := range raw[i : i+offset] {
            set.Add(c)
        }

        if set.Size() == offset {
            return i + offset
        }
    }
    return -1
}

我们的第2部分解决方案将是Part1(input, 14

,如果已经在我们的集合中,在我们做任何首先需要测量当前解决方案之前,可以通过提早返回来优化解决方案。

使用Go benchmarks功能可以轻松完成这一点。

创建一个新文件main_test.go并在那里写下我们的基准

package main

import (
    "testing"

    "github.com/galElmalah/aoc-2022/util"
)

func BenchmarkPart1(b *testing.B) {
    input := util.ReadFile("./input.txt")
    // run the Fib function b.N times
    for n := 0; n < b.N; n++ {
        Part1(input, 4)
    }
}

func BenchmarkPart2(b *testing.B) {
    input := util.ReadFile("./input.txt")
    // run the Fib function b.N times
    for n := 0; n < b.N; n++ {
        Part1(input, 14)
    }
}

运行go test -bench=. -count 3导致

BenchmarkPart1-8            3819            285783 ns/op
BenchmarkPart1-8            3873            285734 ns/op
BenchmarkPart1-8            4021            284767 ns/op
BenchmarkPart2-8            1086           1083411 ns/op
BenchmarkPart2-8            1083           1073575 ns/op
BenchmarkPart2-8            1046           1075867 ns/op

现在让我们将以下if添加到我们的内部循环

if set.Has(c) {
    break
}

重新运行基准

BenchmarkPart1-8            3607            296341 ns/op
BenchmarkPart1-8            3748            294505 ns/op
BenchmarkPart1-8            3734            289663 ns/op
BenchmarkPart2-8            2490            465996 ns/op
BenchmarkPart2-8            2526            454670 ns/op
BenchmarkPart2-8            2541            451878 ns/op

我们可以看到,对于第1部分,几乎没有改进,但是对于第2部分,早期的回报确实有明显的差异,很棒!

就是这样,我们创建了自己的设置数据结构,并使用GO基准来优化我们的解决方案。

我希望您喜欢并学到了一些新事物,因为我肯定做了!

您可以找到完整的代码here
感谢您的阅读!