ARTS_3

概述#

ARTS 是耗子叔发起的编程挑战:

每周完成一个ARTS: 每周至少做一个 leetcode 的算法题、阅读并点评至少一篇英文技术文章、学习至少一个技术技巧、分享一篇有观点和思考的技术文章。(也就是 Algorithm、Review、Tip、Share 简称ARTS)

Algorithm#

https://leetcode.cn/problems/n-queens-ii/ N皇后Ⅱ(把上次的改改就过去了,不讲)

https://leetcode.cn/problems/maximal-rectangle/ 最大矩形

这道题面试也比较常出,只要搞懂他的前置(柱形图最大矩形)其实就没什么难做的了。

我们可以非常容易想到O(n4)的做法,对于每一行都跑柱形图最大矩形就行。那我们也很可以轻易的得到O(n3)的做法,就是把柱形图最大矩形的O(n2)做法拿过来套就可以。

所以在这里具体讲一下柱形图最大矩形的思路:

最主要是运用了单调栈的思想,因为暴力方法会重复遍历之前的位置,用单调栈处理的话我们只需要关心之后的增量,而之前的位置就只需要在弹栈的时候处理就行了。

就拿下图举例:

image-20221025104327489

你程序运行到下标为2的地方时,需要遍历0, 1两个下标

image-20221025105245498

你程序运行到下标为3的地方时,需要遍历0, 1, 2两个下标

image-20221025105359480

然后其实0, 1两个下标是我们重复遍历过了,所以我们希望能有种优化方式让我们,减少这一次的遍历。

观察发现最大矩形面积取决于最小的矩形的高度 x 连续大于等于它的宽度,然后我们发现一个很重要的性质,不断把当前高度最高的柱形抽出,它最大能扩展的宽度一定会在比它小矩形之前

于是我们就可以维护一个单调递增的栈,在每次弹栈的时候就很容易得出他能扩展的宽度。

当这个栈维护完之后,我们再一个个弹栈。此时栈内元素已经是当前最小的元素了,比他大的都已经被处理好(弹栈了),所以在他右边一定都是比他大的元素(如果有比它小的,他肯定已经再维护的时候被弹栈了),可以一直拓展,所以最右能拓展到的地方是n,最左能拓展到的地方是前一个元素的位置。

算出宽度 x 当前高度,一直这么算下去即可得到答案。

又臭又长的go代码:

func GetMaxMatrix(height []int) (ret int) {
	n := len(height)
	stack := make([]int, 0)
	for i := 0; i < n; i++ {
		for {
			if len(stack) != 0 && height[stack[len(stack)-1]] > height[i] {
				pre_i := stack[len(stack)-1]
				stack = stack[:len(stack)-1]
				w := i
				if len(stack) != 0 {
					w = i - stack[len(stack)-1] - 1
				}
				temp := w * height[pre_i]
				if temp > ret {
					ret = temp
				}
			} else {
				break
			}
		}
		stack = append(stack, i)
	}
	for i := len(stack); i != 0; i = len(stack) {
		pre_i := stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		w := n
		if len(stack) != 0 {
			w = n - stack[len(stack)-1] - 1
		}
		temp := w * height[pre_i]
		if temp > ret {
			ret = temp
		}
	}
	return
}

func maximalRectangle(matrix [][]byte) int {
	if len(matrix) == 0 {
		return 0
	}
	height := make([]int, len(matrix[0]))
	var ans int
	for i := range matrix {
		for j := range matrix[i] {
			if matrix[i][j] == '1' {
				height[j]++
			} else {
				height[j] = 0
			}
		}
		res := GetMaxMatrix(height)
		if ans < res {
			ans = res
		}
	}
	return ans
}

Tips#

Goland一键运行多个main#

在压测框架中,每次运行client与server都需要点两次按钮

image-20221008152121240

这样次数多了十分不便。

发现goland的Run Configuration里有个叫compound的类型,

image-20221008141907916

image-20221008141957120

点开来发现可以加入多个Go Build Configuration

image-20221008142105383

于是我们就可以先创建Go Build Configuration

image-20221008152206723

并将args设置好(这里run kind选择package或者file都可以)

Run kind的选择

file选项:运行的仅仅就是这个file以及这个file引用的其他package,如果用了同一package下的其他文件的对象,那么会报错。file的main

file选项:运行的这个file所在的package。

之后添加进Compound就行了。

常见问题:#

1. Compound里的运行顺序是按照上面显示的顺序吗?#

似乎image-20221008152706291当通过image-20221008152726155运行时是按顺序来的,而通过image-20221008152800947image-20221008152812659来运行时,顺序是不固定的。

我猜测虽然image-20221008152726155通过for go来运行,但是停止的机制可能是跟goland内部实现有关,所以停止+重启的顺序不相同。

简单写了一个server跟六个client测试了一下

server:

image-20221008151218457

clients:

image-20221008151232947

compound

image-20221008151307727

结果如下:

image-20221008153054604

2. Compound与go:generate 是相同的吗?#

我们运行go:generate terminal会有一行image-20221008153410418

也就是说他是通过go.exe的generate命令去调用go run运行main package,相当于是go run,也就是新开了一个进程去跑,所以是相同的。

更多generate的学习:http://c.biancheng.net/view/4442.html

Review#

https://betterprogramming.pub/how-to-speed-up-your-struct-in-golang-76b846209587

这篇文章主要讲的是struct的内存优化,emm我寻思,这种struct的内存对齐不是常识?

但凡学过cs的人都应该会知道吧...

结果这篇文章还被很多人点赞了,emm也不是说这样的文章不好,但是其实他都没讲什么东西。就讲了一个内存优化。但是其实优化这一步,是在最后功能实现完善之后才考虑的。而且定长的struct出现的机会比较少。

感觉这一步优化也是有点点鸡肋,也有可能是因为我的水平不够,没法体会文章的深奥。

https://medium.com/higher-order-functions/golang-six-error-handling-techniques-to-help-you-write-elegant-code-8e6363e6d2b

这篇文章讲的是golang中的六种error处理,个人感觉非常实用,浅浅Review一下下

Align to the left#

简而言之就是将err通过=或者 :=的方式来接收,并返回

但是这样的话会遇到 nested if checks,也就是嵌套的if

// Handling Happy case first - leading to nested if checks...
func example() error {
     err := somethingThatReturnsError()
     if err == nil {
        //Happy processing
        err = somethingElseThatReturnsError()
        if err == nil {
           //More Happy processing
        } else {
           return err
        }
     } else {
        return err
     }
}

所以更推荐遇到error就直接return

func ABetterExample() error {
     err := somethingThatReturnsError()
     if err != nil {
        return err
     }
     
     // Happy processing
     err = somethingElseThatReturnsError()
     if err != nil {
        return err
     }
     // More Happy processing
}

这一点在工程上也是很推荐的,遇到需要的或者error直接就返回,而不是写嵌套的check语句

Retry recoverable Errors#

这里的话就是提到,需要重试可以重试的error,比如I/O那类。

作者主要提到一个包,叫做backoff

这个包有一些比较好用的函数,可以帮助你进行不同类型的retry,比如指数退避进行重试。

Wrapping Errors#

这里的也是提到一个外部包"github.com/pkg/errors",完全兼容golang自带的error,但是会有些很酷的特性,比如errors.Wrap()函数,它可以打印error的调用堆栈信息,可以方便你定位error出现的位置。

func testingError2() error {
   return errors.New("New Error")
}

func testingError(accountNumber string) error {
   err := testingError2()
   if err != nil {
      return errors.Wrap(err, "Error occurred while processing Card Number "+accountNumber)
   }
   return nil
}

func main() {
   err := testingError("Acct1")
   fmt.Printf("%+v", errors.Unwrap(err))
}

稍微把玩了十几分钟,感觉确实很强大,这样我就不需要去特地打印调用栈了。感觉有机会用到我们的工程上啊。这玩意儿不必golang自带的errors强一百倍?

Logging Strategies#

这个主要讲的是如何将error优雅地log出来,官方自带的log就跟记事本一样,除了一些基本功能之外就没什么了。

文章主要讲了三种外部包:

具体有兴趣可以自己学一下这些log的使用。

Error Checks#

这个就是不要把error忽略掉,比如:

func testingError(accoutNumber string) error {
   var err error
   _ = errors.New("errors.New with _")
   errors.New("errors.New not capturing return")
   return err
}

这样的话第三行和第四行的error其实都没有处理,但是当代码很多的时候这个地方就会忽略掉。于是就需要Error检查工具:

https://github.com/kisielk/errcheck

之后你可以通过这样的命令行:

errcheck -blank ./...

获得这样的输出:

temp.go:16:2:   _ = errors.New("Error capturing return using _")
temp.go:18:12:  errors.New("Error not capturing return")

Multiple Errors#

如果你的func 会出现很多error,你又不想要他一出现error就退出,而是把这些error收集起来,再一起返回,那么这个时候就可以使用https://github.com/hashicorp/go-multierror

像下面这样使用即可

func step1() error {
    return errors.New("Step1")
}
func step2() error {
    return errors.New("Step2")
}
func main() {
    var result error
    if err := step1(); err != nil {
        result = multierror.Append(result, err)
    }    if err := step2(); err != nil {
        result = multierror.Append(result, err)
    }
    
    fmt.Println(multierror.Flatten(result))
}

如果多个goroutine的话可以使用这个:https://pkg.go.dev/golang.org/x/sync/errgroup

可以去看官方文档学习


看了这篇文章之后,发现golang的三方库真的好强大啊,怎么好用的东西那么多,感觉还是有必要去好好了解一下,少造点轮子。多学习怎么造轮子。

Share#

https://coolshell.cn/articles/22298.html

https://coolshell.cn/

posted @   ViKyanite  阅读(67)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
历史上的今天:
2019-10-26 计蒜客练习题:水果店(嵌套map)
2019-10-26 计蒜客练习题:蒜头君面试(map + max_element)
点击右上角即可分享
微信分享提示
主题色彩