0%

这是场接力赛,你却当成百米赛跑。

async

首先,我们使用async关键字,您可以将它放在函数声明之前,将其转换为async function。异步函数是一个知道怎样预期 await 关键字可用于调用异步代码可能性的函数。

尝试在浏览器的JS控制台中键入以下行:

1
2
function hello() { return "Hello" };
hello();

该函数返回“Hello” ––没什么特别的,对吧?

但是,如果我们将其变成异步函数呢?请尝试以下方法:

1
2
async function hello() { return "Hello" };
hello();

额。。现在调用该函数会返回一个promise。这是异步函数的特征之一 ––它将任何函数转换为promise。
你也可以创建一个异步函数表达式(参见async function expression),如下所示:

1
2
let hello = async function() { return "Hello" };
hello();

你可以使用箭头函数:

1
let hello = async () => { return "Hello" };

这些都基本上是一样的。

要实际使用promise实现时返回的值,因为它返回了一个promise,我们可以使用.then()块:

1
hello().then((value) => console.log(value))

甚至只是简写如

1
hello().then(console.log)

就像我们在上一篇文章中看到的那样
因此,将async关键字添加到函数中以告诉它们返回promise而不是直接返回值。此外,这使同步函数可以避免运行支持使用await时带来的任何潜在开销。通过仅在函数声明为异步时添加必要的处理,JavaScript引擎可以为您优化您的程序。爽!

await

将它与await关键字结合使用时,异步函数的真正优势就变得明显了。这可以放在任何基于异步声明的函数之前,暂停代码在该行上,直到promise完成,然后返回结果值。与此同时,其他正在等待执行机会的代码就有可能如愿执行了。
这是一个简单的示例:

1
2
3
4
5
async function hello() {
return greeting = await Promise.resolve("Hello");
};

hello().then(alert);

关键字 await 让 JavaScript 引擎等待直到 promise 完成(settle)并返回结果
这里的例子就是一个 1 秒后 resolve 的 promise:

1
2
3
4
5
6
7
8
9
10
11
12
async function f() {

let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});

let result = await promise; // 等待,直到 promise resolve (*)

alert(result); // "done!"
}

f();

这个函数在执行的时候,“暂停”在了 (*) 那一行,并在 promise settle 时,拿到 result 作为结果继续往下执行。所以上面这段代码在一秒后显示 “done!”

让我们强调一下:await 字面的意思就是让 JavaScript 引擎等待直到 promise settle,然后以 promise 的结果继续执行。这个行为不会耗费任何 CPU 资源,因为引擎可以同时处理其他任务:执行其他脚本,处理事件等。

相比于 promise.then,它只是获取 promise 的结果的一个更优雅的语法,同时也更易于读写。

编辑器报错

async function is only available in es8

You can create “.jshintrc” file (see jshint.com/docs/) at project root and simply add to it lines given below:

1
2
3
{
"esversion": 8
}

多花一點心思去說溫柔的話,那些話可能改變他人一生

背景

使用net/http同时发起多个简单请求时,偶尔会出现EOF或connect: connection reset by peer的情况。明明就是一个很简单的例子,为何会出现这种情况呢?

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
eq, err := http.NewRequest(method, url, body)
if err !=nil{

return nil, err

}

resp, err := http.DefaultClient.Do(req)
if err !=nil{
return nil, err
}
defer resp.Body.Close()

b, err := ioutil.ReadAll(resp.Body)
if err !=nil{
return nil, err
}

return b,nil

如此简单的几行代码,在我们套上大并发以后,各种异常情况接踵而至,最常见的就是下面的几个:

  • EOF
  • connection reset by peer

探讨

HTTP也是针对TCP的一个封装,go得益于goroutine和channel,与其他语言的实现方式不太一样,它是直接起了两个协程,一个用于读(readLoop),一个用于写(writeLoop)。

conn.Read

  • socket无数据:read阻塞,直到有数据。
  • socket有部分数据:如果socket中有部分数据,且长度小于一次Read操作所期望读出的数据长度,那么Read将会成功读出这部分数据并返回,而不是等待所有期望数据全部读取后再返回。
  • socket有足够数据:如果socket中有数据,且长度大于等于一次Read操作所期望读出的数据长度,那么Read将会成功读出这部分数据并返回。这个情景是最符合我们对Read的期待的了:Read将用Socket中的数据将我们传入的slice填满后返回:n = 10, err = nil
  • 有数据,socket关闭:第一次Read成功读出了所有的数据,当第二次Read时,由于client端 socket关闭,Read返回EOF error;
  • 无数据,socket关闭:Read直接返回EOF error

conn.Write

  • 成功写:Write调用返回的n与预期要写入的数据长度相等,且error = nil;
  • 写阻塞:当发送方将对方的接收缓冲区以及自身的发送缓冲区写满后,Write就会阻塞;
  • 写入部分数据:Write操作存在写入部分数据的情况,没有按照预期的写入所有数据,则需要循环写入。

解决

通过上面的描述,我们大致已经明白了为何会出现EOF了,其实就是readLoop在进行读的时候,检测到socket被关闭了。在HTTP1.1,我们默认都是采用了Keep-Alive的,也就是会启动长连接,当server端断掉了该socket后,我们的EOF就出来了。所以尽量避免该情况发生的话,直接这样req.Close = true

https://dpjeep.com/golangzhi-http-eofxiang-jie/

すぐ目(め)の前(まえ)にあっても、手(て)に入れる事(こと)が出来(でき)ないものがある

应用场景

在cms 开发中,SEO优化 一般需要把文章标题 设置为 img 的alt title 属性,那么内容多个 img 时,手动过于繁琐。

实现原理

根据以上需求,那么使用正则找内容的 img 标签 ,进行添加或者替换即可

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?php
$str ='<div class="wkBody">
<div id="panelSummary" class="abstract">

<span class="font28">摘要:</span>
作为阿根廷种植面积第二大的红葡萄品种,伯纳达的发展潜力越来越大,逐渐成为阿根廷的希望之星。
<div id="pnlSummaryEN" class="abs-en">

<span class="font28">ABSTRACT:</span>
Bonarda is Argentina’s second most widely planted red grape, which may make a big splash in the coming years.

</div>

</div>
<p>
说到阿根廷葡萄酒,大家最先想到的可能是著名的<a href="http://www.wine-world.com/s/grape?q=马尔贝克&amp;token=ba782698-e5be-44f8-8e54-09eac16f02a9&amp;from=user" target="_blank">马尔贝克</a>(Malbec),毫无疑问,这个品种在阿根廷占据着绝对的主导地位。但是,除了马尔贝克以外,阿根廷还有一个发展潜力巨大的葡萄品种,它是阿根廷的希望之星——阿根廷种植面积第二大的葡萄品种伯纳达(Bonarda)。<br>
<br>
 <strong> 1. 特征</strong></p>
<p style="text-align: center;">
<strong><a target="_blank" href="/culture/pic/22571cf7-ee6b-4176-8efb-e02e4e9dd947/0"><img alt="除了马尔贝克,阿根廷还有伯纳达" title="除了马尔贝克,阿根廷还有伯纳达" src="/UserFiles/images/01-bonarda-161114.jpg" style="width: 375px; height: 500px;"></a></strong></p>
<p style="text-align: center;">
<span style="font-size:12px;">图片来源:Wine Folly</span></p>
<p>
伯纳达葡萄酒初闻时果香扑鼻,带有糖渍黑樱桃、新鲜蓝莓和李子的香气。紧接着,香气变得更加复杂,散发出紫罗兰、五香、多香果和牡丹的气息。最后,经过橡木桶的酒还会有轻微的雪茄盒、甜无花果和巧克力等烟熏气息。入口时,伯纳达葡萄酒最先表现的也是果味,酒体中等,鲜酸多汁,单宁较低,余味顺滑,十分易饮。<br>
<br>
  伯纳达与马尔贝克一样拥有饱满的颜色,但伯纳达的单宁含量更低,酸度更高、更多汁。如果你不喜欢使用橡木桶的葡萄酒,那你可能会青睐伯纳达葡萄酒,因为大多数伯纳达葡萄酒少用甚至不用橡木桶。另外,伯纳达葡萄酒的酒精度很少超过13.5%。<br>
<br>
 <strong> 2. 配餐</strong></p>
<p style="text-align: center;">
<strong><a target="_blank" href="/culture/pic/22571cf7-ee6b-4176-8efb-e02e4e9dd947/1"><img alt="除了马尔贝克,阿根廷还有伯纳达" title="除了马尔贝克,阿根廷还有伯纳达" src="/UserFiles/images/02-cedar-plank-salmon-161115.jpg" style="width: 550px; height: 333px;"></a><div style="width: 550px;" class="addivstyle"><div id="testdiv" style="width: 550px;" class="adseconddiv"><a href="http://mall.wine-world.com/activity/activitypromotion10" target="_blank"><img src="http://images.wine-world.com/images/wineworldlogo.jpg" style="left:2px;top:2px;width:102px;position: absolute;border: none;background: none;"><span style="position: absolute;left:120px;right:85px;top:10px;color: #FFF;text-align:left;">开抢50元代金券,醉惠中级庄盛宴<br>30款中级庄美酒任君挑选,领券后下单直减50元!</span><span class="qukankanstyle" style="top: 20px;">去看看</span></a></div></div></strong></p>
<p style="text-align: center;">
<span style="font-size:12px;">图片来源:Wine Folly</span></p>
<p>
伯纳达葡萄酒低单宁、高酸的特点使其可以与多种食物搭配,如鸡肉、牛肉、猪肉和鱼排等;而酒中轻微的棕色香料的风味也可以与菠萝、芒果、照烧汁等完美结合。<br>
<br>
<strong>  3. 此伯纳达非彼伯纳达</strong><br>
<br>
  阿根廷的伯纳达与其它地方的伯纳达很可能并不是同一个葡萄品种,也许阿根廷的伯纳达本不该被称为“伯纳达”。DNA检测表明阿根廷的伯纳达与法国萨瓦(Savoie)产区一种叫“Deuce Noir”的稀有葡萄是同一品种,跟纳帕谷(Napa Valley)古老葡萄园中的沙帮乐(Charbono)也是相同的品种。<br>
<br>
  实际上,真正的伯纳达葡萄指的是一组意大利葡萄,至少分为6种,其中最出名的是皮埃蒙特伯纳达(Bonarda Piemontese)。更容易引起混淆的是,意大利伦巴第(Lombardy)的奥尔特莱伯·帕韦斯(Oltrepo Pavese)产区所产的标有“伯纳达”的葡萄酒实际上是用科罗帝纳(Croatina)酿制而成的;<a href="http://www.wine-world.com/s/area?q=皮埃蒙特&amp;token=36d3dd49-745b-43f8-8bce-1b90ce3118b0&amp;from=user" target="_blank">皮埃蒙特</a>(Piedmont)有些酿酒师酿制的标有“伯纳达”的葡萄酒其实是用一种叫茹拉(Uva Rara)的葡萄酿制的。<br>
<br>
  所以,如果下次有人纠正你对阿根廷伯纳达的叫法时,不妨问问他们以上这两种标有“伯纳达”的葡萄酒为什么是用别的葡萄酿制的,他们最好能说出它们之间的联系。<span style="font-size:12px;">(文/Christina)</span></p>

<div id="pnlWineVersion2">

<p>声明:本文版权属于“红酒世界网”,转载请保留版权信息。关注微信号“wine-world”,随时随地了解最新红酒资讯。</p>

</div>
</div>';


$preg = "/<img.*?src=[\"|\'](.*?)[\"|\'].*?>/";
$alt = "1234";
$title = "123456";
$img = '<img src="$1" alt="'.$alt.'"title="'.$title.'">';
$data = preg_replace($preg,$img,$str);
var_dump($data);

https://segmentfault.com/a/1190000007505546

醉后不知天在水,满船清梦压星河

简述

单向链表属于链表的一种,也叫单链表,单向即是说它的链接方向是单向的,它由若干个节点组成,每个节点都包含下一个节点的指针。

其实关于链表,大家完全可以把他和数组进行类比学习,只不过数组由于申请的空间必须是连续地,所以有的时候内存地址可能分配不出足够的内存空间。链表的的内存空间可以不连续的特性就可以解决数组的痛点了。

对于链表与数组的使用场景就是,如果你的数据需要经常的查询,那么数组更快。如果你的数据需要频繁的插入删除,链表比数组更快。

  • 创建单链表时无需指定链表的长度,这个比起数组结构更加有优势,而数组纵使实现成动态数组也是需要指定一个更大的数组长度,而且要把原来的数组元素一个个复制到新数组中。
  • 单链表中的节点删除操作很方便,它可以直接改变指针指向来实现删除操作,而某些场景下数组的删除会导致移动剩下的元素。
  • 单链表中的元素访问需要通过顺序访问,即要通过遍历的方式来寻找元素,而数组则可以使用随机访问,这点算是单链表的缺点。

定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

// 节点的数据结构
type Node struct {
E int
Next *Node
}
// 链表的数据结构
type List struct {
dummyHead *Node
size int
}

func (l List) Head() *Node {
return l.dummyHead
}

func (l List) Size() int {
return l.size
}
// 初始化一个节点
func initNode (e int) *Node {
return &Node{
E:e,
Next:nil,
}
}
// 初始化一个空链表
func InitList () *List {
return &List{
dummyHead:initNode(0),
size:0,
}
}

虽然代码注释里写的是初始化一个空链表,却在里面实实在在地初始化了一个节点出来。这种技术叫做虚拟头结点,他的具体好处如下:

防止单链表是空的而设的,当链表为空的时候,带头结点的头指针就指向头结点。如果当链表为空的时候,单链表没有带头结点,那么它的头指针就为 NULL。

是为了方便单链表的特殊操作,插入在表头或者删除第一个结点。这样就保持了单链表操作的统一性。

单链表加上头结点之后,无论单链表是否为空,头指针始终指向头结点,因此空表和非空表的处理也统一了,方便了单链表的操作,也减少了程序的复杂性和出现 bug 的机会。

对单链表的多数操作应明确对哪个结点以及该结点的前驱。不带头结点的链表对首元结点、中间结点分别处理等;而带头结点的链表因为有头结点,首元结点、中间结点的操作相同,从而减少分支,使算法变得简单,流程清晰。

基本操作

  • 插入操作,其中又分为往一个索引对应的位置插入,往头结点后插入,在链表尾部插入三种插入方法。
  • 删除节点,其中又分为删除一个索引位置的节点,删除一个值对应的节点,删除第一个节点,删除尾节点。
  • 查询节点,同样分为查询某个索引值的节点,查询头部,查询尾部。
  • 修改节点,修改某个索引值对应的节点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 判断链表是否为空
func (this *List) IsEmpty() bool {
return this.size == 0
}
// 插入部分
// 在链表的第index索引个元素后插入元素,索引从0开始
func (this *List) AddIndex (index,e int) {
if index > this.size || index < 0 {
panic("索引越界,不能插入了")
}
prev := this.dummyHead
node := initNode(e)

for i:=0;i<index;i++ {
prev = prev.Next
}
// 这里的步骤一定不能反过来,思考一下为什么
node.Next = prev.Next
prev.Next = node
this.size++

}
// 在链表头添加元素
func (this *List) AddFirst (e int) {
this.AddIndex(0,e)
}
// 在链表尾部添加节点
func (this *List) AddLast (e int) {
this.AddIndex(this.size,e)
}
// 查询部分
// 在链表中查询是否包括元素e
func (this *List) Contains (e int) bool {
cur := this.dummyHead.Next
for cur!=nil {
if cur.E == e{
return true
}
cur = cur.Next
}
return false
}
// 在链表中查询第index个元素
func (this *List) Get (index int) int {
if index > this.size || index < 0 {
panic("索引越界,不能查询")
}
cur := this.dummyHead.Next
for i:=0;i<index;i++ {
cur = cur.Next
}
return cur.E
}
func (this *List) GetFirst () int{
return this.Get(0)
}
func (this *List) GetLast () int{
return this.Get(this.size-1)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package main

import (
"fmt"
"strings"
)

type Node struct {
E int
Next *Node
}

type List struct {
dummyHead *Node
size int
}

func (l List) Head() *Node {
return l.dummyHead
}


func (l List) Size() int {
return l.size
}
func initNode (e int) *Node {
return &Node{
E:e,
Next:nil,
}
}
func InitList () *List {
return &List{
dummyHead:initNode(0),
size:0,
}
}
func (this *List) IsEmpty() bool {
return this.size == 0
}
// 在链表的第index索引个元素后插入元素,索引从0开始
func (this *List) AddIndex (index,e int) {
if index > this.size || index < 0 {
panic("索引越界,不能插入了")
}
prev := this.dummyHead
node := initNode(e)

for i:=0;i<index;i++ {
prev = prev.Next
}
node.Next = prev.Next
prev.Next = node
this.size++

}
// 在链表头添加元素
func (this *List) AddFirst (e int) {
this.AddIndex(0,e)
}
// 在链表尾部添加节点
func (this *List) AddLast (e int) {
this.AddIndex(this.size,e)
}
// 在链表中查询第index个元素
func (this *List) Get (index int) int {
if index > this.size || index < 0 {
panic("索引越界,不能查询")
}
cur := this.dummyHead.Next
for i:=0;i<index;i++ {
cur = cur.Next
}
return cur.E
}
func (this *List) GetFirst () int{
return this.Get(0)
}
func (this *List) GetLast () int{
return this.Get(this.size-1)
}
// 在链表index个位置中放入元素e
func (this *List) Set (index,e int) {
if index > this.size || index < 0 {
panic("索引越界,不能置入")
}
cur := this.dummyHead.Next
for i:=0;i<index;i++ {
cur = cur.Next
}
cur.E = e
}
// 在链表中查询是否包括元素e
func (this *List) Contains (e int) bool {
cur := this.dummyHead.Next
for cur!=nil {
if cur.E == e{
return true
}
cur = cur.Next
}
return false
}
// 在链表中删除元素
func (this *List) Remove (index int) int {
if index > this.size || index < 0 {
panic("索引越界,不能删除")
}
prev := this.dummyHead
for i:=0;i<index;i++ {
prev = prev.Next
}
retNode := prev.Next
prev.Next = retNode.Next
this.size--
return retNode.E
}
func (this *List) RemoveFirst () int{
return this.Remove(0)
}
func (this *List) RemoveLast () int{
return this.Remove(this.size-1)
}
// 删除元素E
func (this *List) RemoveElement (e int) {
prev := this.dummyHead
for prev.Next != nil {
if prev.E == e {
break
}
prev = prev.Next
}
if prev.Next != nil {
DelNode := prev.Next
prev.Next = DelNode.Next
DelNode = nil
}
}
// 在Golang中,如果我们想对自建数据结构自定义在Println的时候打印出什么结果
// 就可以使用这种方式去自己构建打印的字符串格式
func (this *List) String () string {
var builder strings.Builder
cur := this.dummyHead.Next
for cur != nil {
fmt.Fprintf(&builder,"%d -> ",cur.E)
cur = cur.Next
}
fmt.Fprintf(&builder,"NULL")
return builder.String()
}

func main() {
list := InitList()
for i:=0;i<5 ; i++ {
list.AddLast(i)
}
fmt.Println(list)
fmt.Println(list.Contains(0))
fmt.Println(list.Get(3))
fmt.Println(list.RemoveLast())
fmt.Println(list.GetFirst())
// 其他功能可以自行测试。
}

链表的应用

逆序一个链表

给定一个带头结点的单链表,请将其逆序。即如果单链表原来为 head -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 。则逆序后变为 head -> 7 -> 6 -> 5 -> 4- > 3 -> 2 -> 1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"
"strings"
)

// 在这个函数前需要将list.go的代码拷贝到这里

func (this *List)Reverse() {
var pre *Node
var cur *Node
next := this.Head().Next

for next != nil {
cur = next.Next
next.Next = pre
pre = next
next = cur
}
this.Head().Next = pre
}

func main(){
list := InitList()
for i:=0;i<5 ; i++ {
list.AddFirst(i)
}
fmt.Println(list)
list.Reverse()
fmt.Println(list)
}

计算两个单链表所代表的的代数之和

给定两个单链表,链表的每个结点代表一位数, 计算两个数的和。例如 : 输入链表 (3->1->5) 和链表 (5->9->2) ,输出: 8->0->8 ,即 513 + 295 = 808 ,注意个位数在链表头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package main

import (
"fmt"
"strings"
)

// 在这个函数前需要将list.go的代码拷贝到这里


// 把两个用链表表示的整数相加
func AddTwoList(head1,head2 *Node) *List {
// 初始化一个新链表
newHead := InitList()
// 因为有dummyHead,所以head的下一个节点才是真真正的头结点
p1 := head1.Next
p2 := head2.Next
carry := 0
for p1 != nil || p2 != nil {
// 如果p1到头就只需要添加p2,反之同理
if p1 == nil {
newHead.AddLast(p2.E+ + carry)
p2 = p2.Next
}
if p2 == nil {
newHead.AddLast(p1.E + carry)
p1 = p1.Next
}
// 计算本位的值
temp := (p1.E + p2.E + carry )%10
newHead.AddLast(temp)
// 记录进位
carry = (p1.E + p2.E + carry )/10
p1 = p1.Next
p2 = p2.Next
}
return newHead
}

// 测试代码和测试结果
func main(){
list := InitList()
list2 := InitList()
for i:=0;i<6 ; i++ {
list.AddFirst(i)
}
for i:=0;i<6 ; i++ {
list2.AddFirst(i+1)
}
fmt.Println(list)
fmt.Println(list2)
newlist := AddTwoList(list.Head(),list2.Head())
fmt.Println(newlist)
}

忽忆故人今总老。贪梦好。茫然忘了邯郸道。

简述

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

队列的先进先出,其实就对应了我们生活中的先到先得。比如你在食堂打饭,肯定是队头的人先打饭。还有在银行的叫号系统,那其实也是一个队列。

定义

队列肯定要装不只一个对象,所以我们需要一个切片作为容器。并且,我们还需要两个标记位,一个指向队列的头 front ,一个指向队列的尾 rear 。并且我们也需要一个队列的容量 size 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

type Queue struct {
// 用来装元素的切片
container []int
// 队首标记位
front int
// 队尾标记位
rear int
// 容量限制
size int
}

// 初始化时,要传入容量限制
func NewQueue(size int) *Queue {
return &Queue{
container: make([]int,size),
// 在队列中已经装了多少元素我们可以用size表示
// 想一想为什么不能用rear-front表示队列当前长度呢?
front: 0,
rear: 0,
size: 0,
}
}

队列的基本操作

队列的基本操作包括如下几种

  • 初始化队列:InitQueue(size) 操作结果:初始化一个空队列。
  • 判断队列是否为空:IsEmpty() 操作结果:若队列为空则返回 true,否则返回 false。
  • 判断队列是否已满:IsFull()。 操作结果:若队列为满则返回 true,否则返回 false。
  • 入队操作:EnQueue(data) 操作结果:在队列的队尾插入 data。
  • 出队操作:DeQueue() 操作结果:将队列的队头元素出队,并返回出队元素的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package main

import "fmt"

type Queue struct {
// 用来装元素的切片
container []int
// 队首标记位
front int
// 队尾标记位
rear int
// 容量限制
size int
}

// 初始化时,要传入容量限制
func NewQueue(size int) *Queue {
return &Queue{
container: make([]int,size),
// 在队列中已经装了多少元素我们可以用size表示
// 想一想为什么不用 rear - front 表示呢
front: 0,
rear: 0,
size: 0,
}
}
// 队列是否为空
func (q *Queue) IsEmpty () bool {
if q.size == 0 {
return true
}
return false
}

func (q *Queue) IsFull () bool {
if q.size == len(q.container) {
return true
}
return false
}
// 新元素入队
func (q *Queue) EnQueue (a int) bool {
if q.IsFull() {
return false
}
q.container[q.rear] = a
q.rear++
q.size++
return true
}
// 队首元素出队
func (q *Queue) Dequeue () (bool,int) {
if q.IsEmpty() {
return false,0
}
ret := q.container[q.front]
q.front++
q.size--
return true,ret
}

func main() {
queue := NewQueue(10)

for i := 0; i < 5 ; i++ {
queue.EnQueue(i)
}

for i := 0; i < 6; i++ {
fmt.Println(queue.Dequeue())
}
}

循环队列

我们在每次让元素出队的时候,队列的头指针是不断的在往后移的。总有一天,队头指向的索引值比我们的容器长度还要大,那种情况出现的时候要怎么办呢?
既然在使用过程中,front 和 rear 都只能往后移导致有用的空间被浪费了。那么能不能去做一个可以再利用,不浪费一点空间的队列呢?

答案就是我们可以做一个循环队列。循环队列,顾名思义,它长得像一个环。原本数组是有头有尾的,是一条直线。现在我们把首尾相连,扳成了一个环。

判断队列的状态

用 size 表示队列的实际长度,而不用 rear - front 呢?其实就是为了循环队列。各位想一想,在循环时,rear 和 front 是说不清谁大谁小的,有可能减出一个负数。而我们使用 size 就不会出现这种问题,因为只有入队操作才会让 size + 1, 也只有出队操作能让 size - 1。所以这里我们的判断队列状态的函数可以直接不变就拿来使用。解决了循环队列中的最难点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** 检查循环队列是否为空. */
func (this *Queue) IsEmpty() bool {
if this.size == 0 {
return true
}
return false
}

/** 检查循环队列是否为满 */
func (this *Queue) IsFull() bool {
if this.size == len(this.arr) {
return true
}
return false
}

计算索引

解决了判断状态问题,我们可以继续解决索引值的计算问题了。首先是入队操作,在入队时。我们只要对 rear 先对队列的容量取余,计算出的索引值就是我们要插入的位置。

对于出队操作,我们要出队的元素是 front 对队列容量取余所指向的元素,取出之后下一个 front 的值需要先对队列的容量取余再加 1。 到这里我们就可以写出新的队列的完整代码了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package main

import (
"fmt"
)

type Queue struct {
container []int
front int
rear int
size int
}


func NewQueue (k int) *Queue {
return &Queue{
make([]int, k),
0,
0,
0,
}
}


func (this *Queue) EnQueue(value int) bool {
if this.container == nil || this.IsFull() {
return false
}
this.container[this.rear%len(this.container)] = value
this.rear = this.rear%len(this.container) + 1
this.size++
return true
}


func (this *Queue) DeQueue() (bool,int) {
if this.container == nil || this.IsEmpty() {
return false,0
} else {
ret := this.container[this.front%len(this.container)]
this.front = this.front%len(this.container) + 1
this.size--
return true,ret
}
}


func (this *Queue) IsEmpty() bool {
if this.size == 0 {
return true
}
return false
}

func (this *Queue) IsFull() bool {
if this.size == len(this.container) {
return true
}
return false
}

func main() {
queue := NewQueue(5)
// 循环3次,每次添加5个元素,再出队三个元素
for i:=0; i<3; i++{
for j:=0;j<5;j++ {
queue.EnQueue(j)
}
for k:=0;k<3;k++ {
fmt.Println(queue.DeQueue())
}
}
}

东风夜放花千树,更吹落,星如雨。

简述

栈是一种操作受限制的线性表,将允许进行插入、删除的一端称为栈顶,另一端称为栈底。看到这里你可能会觉得有点绕,其实数据结构很多的定义都很抽象,这是很正常的,下面我将类比一个生活中的常见实例帮助大家理解。

我们在日常生活中洗盘子的时候,摞起来的盘子就是一个典型的栈结构。不管取还是放,总是要放在盘子堆的最上面操作。如果你想从一堆盘子的中间强行取一个盘子,那就有可能酿成大祸。

定义

我们在日常生活中,一摞盘子肯定有很多个而且是连续的,所以我们首先需要一个切片,并且我们日常生活中的盘子是不能无限的摞的,这点在计算机里也是同样的,计算机里也会有 Stack Overflow 的问题。 所以我们还需要一个容量的限制元素。 并且我们还需要频繁的对栈顶元素进行操作,所以我们还需要一个标记位 top 来标记栈顶元素的索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

type Stack struct {
// 用来装元素的切片
container []int
// 栈顶标记位
top int
// 容量限制
size int
}

// 初始化时,要传入容量限制
func NewStack(size int) *Stack {
return &Stack{
container: make([]int,size),
// 栈顶指针初始可以指向-1,也可以指向0,想一想为什么。
top: 0,
size: size,
}
}

初始化时要注意,我们这里返回的是一个 Stack 的指针,引用传递在系统中的开销比较小

栈的基本操作

对于一个栈来说,其基本操作分为以下四种。

  • Push(e E) , 将一个数据类型为 E 的元素 e 放到栈顶。
  • Pop() , 将栈顶元素取出。
  • IsFul() , 栈是否满了。
  • IsEmpty() , 栈是否空了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package main

import "fmt"

type Stack struct {
// 用来装元素的切片
container []int
// 栈顶标记位
top int
// 容量限制
size int
}

// 初始化时,要传入容量限制
func NewStack(size int) Stack {
return Stack{
container: make([]int, size),
top: 0,
size: size,
}
}

func (s *Stack) Push(e int) bool {
if s.IsFull() {
return false
}
// 把盘子摞上去
s.container[s.top] = e
// 下一个能摞盘子的位置
s.top++
return true
}

func (s *Stack) Pop() (flag bool, ret int) {
// 如果栈空了,你就无法拿到新盘子,所以flag此时为false
if s.IsEmpty() {
return false, ret
}
// 取出盘子
ret = s.container[s.top-1]
// 下一个能取盘子的位置
s.top--
return true, ret
}

func (s *Stack) IsEmpty() bool {
if s.top == 0 {
return true
}
return false
}

func (s *Stack) IsFull() bool {
if s.top == s.size {
return true
}
return false
}

func main() {
stack := NewStack(3)
// 先测试栈为空的时候能否Pop
fmt.Println(stack.Pop())
// 测试Push是否正常
stack.Push(1)
stack.Push(2)
stack.Push(3)
// 如果栈为正常的,这里Pop打印顺序应该是3,2,1
fmt.Println(stack.Pop())
fmt.Println(stack.Pop())
fmt.Println(stack.Pop())
}

栈的常见应用

浏览器中的前进后退

假设你现在是 N 年前的 Chrome 浏览器工程师, 你现在很苦恼,有的网页在打开下一个页面后就回不去上一级了,你现在急迫的想要一个后退的功能,请问要怎么样实现呢?

仔细分析之后不难发现,所谓的后退,撤销等操作,其实就是一个栈的 Pop 操作,我们每次点击的网址, 或者进行的操作,都是被程序 Push 到了一个栈中,所以一旦我们点击撤销或后退时,总是可以返回我们最近一次的操作。这就是栈的最广泛的应用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package main

import "fmt"

type Stack struct {
// 用来装元素的切片
container []string
// 栈顶标记位
top int
// 容量限制
size int
}

// 初始化时,要传入容量限制
func NewStack(size int) Stack {
return Stack{
container: make([]string, size),
top: 0,
size: size,
}
}

func (s *Stack) Push(e string) bool {
if s.IsFull() {
return false
}
s.container[s.top] = e
s.top++
return true
}

func (s *Stack) Pop() (flag bool, ret string) {
// 如果栈是空的,那么就不能继续 Pop 了
if s.IsEmpty() {
return false, ret
}
ret = s.container[s.top-1]
s.top--
return true, ret
}

func (s *Stack) IsEmpty() bool {
if s.top == 0 {
return true
}
return false
}

func (s *Stack) IsFull() bool {
if s.top == s.size {
return true
}
return false
}

func main() {
back := NewStack(3)
// 模拟每次点击网页时,浏览器会自push你的网址到栈中
back.Push("www.baidu.com")
back.Push("www.bing.com")
back.Push("www.goole.com")
// 每次点击后退时,就相当于是从栈中Pop了一个网址
fmt.Println(back.Pop())
fmt.Println(back.Pop())
fmt.Println(back.Pop())
}

括号匹配

在现代的 IDE 中,我们的编辑器环境是十分智能的,我少打了一个括号,IDE 就自动给我画了一条红线。是不是非常的神奇。其实这个功能也是用栈实现的。下面来说一下思路:
若遇到左括号入栈,遇到右括号时将栈顶左括号出栈,则遍历完所有括号后 stack 仍然为空;如果遍历之后 stack 不为空,那么说明有多余的左括号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package main

import "fmt"

type Stack struct {
// 用来装元素的切片
container []byte
// 栈顶标记位
top int
// 容量限制
size int
}

// 初始化时,要传入容量限制
func NewStack(size int) Stack {
return Stack{
container: make([]byte,size),
top: 0,
size: size,
}
}

func (s *Stack) Push (e byte) bool {
if s.IsFull() {
return false
}
s.container[s.top] = e
s.top++
return true
}


func (s *Stack) Pop () (flag bool,ret byte) {
// 如果栈是空的,那么就不能继续 Pop 了
if s.IsEmpty() {
return false,ret
}
ret = s.container[s.top-1]
s.top--
return true,ret
}


func (s *Stack) IsEmpty () bool {
if s.top == 0 {
return true
}
return false
}


func (s *Stack) IsFull () bool {
if s.top == s.size {
return true
}
return false
}

func IsValid(s string) bool {
stack := NewStack(100)
// 遍历括号字符串
for _,v := range s {
if v == '(' {
// 由于golang中的字符串默认是unicode编码,所以我们要做一个强制类型转换
stack.Push(byte(v))
}
if v == ')' {
// 如果flag不为true,说明栈已经到底了,可以直返回false
if flag,top := stack.Pop(); flag == true &&top == '(' {
continue
} else {
return false
}
}
}
// 字符串遍历完后如果栈也空了,说明括号匹配
if stack.IsEmpty() {
return true
}
// 如果栈不空,说明栈里还有多余的左括号
return false
}

func main() {
test1 := "()()())"
test2 := "((()"
test3 := "()()()()"
fmt.Println(IsValid(test1))
fmt.Println(IsValid(test2))
fmt.Println(IsValid(test3))
}

终有一天 我有属于我的天

Goのプログラミングでは、どんな型の引数でも受け取れる関数を作るのにinterface{}を使います。

1
2
3
4
5
6
func v(x interface{}){
fmt.Println(x)
}

// v(1) => 1
// v("abc") => abc

しかし、interface{}型な変数を、たとえばintを引数にとる関数に渡すと、エラーになります。

1
2
3
4
5
6
7
8
9
func main(){
var x interface{} = 1
fmt.Println( add2(x) )
//=> cannot use x (type interface {}) as type int in argument to add2: need type assertion
}

func add2(n int) int{
return n + 2
}

このようなことをしたい場合は、castするか、switch式を使うことで、型を変換して関数に渡すことができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main(){
var x interface{} = 1
var y interface{} = 5

// cast
if xi, ok := x.(int); ok{
fmt.Println( add2(xi) ) //=> 3
}

//switch
switch yi := y.(type){
case int:
// ここに入ってきた時は、yiの型はintとして扱われる
fmt.Println( add2(yi) ) //=> 5
case int64, int32, int8:
// ここに入ってきた時は、yiの型はint64, int32, int8のうち適切な型が選ばれている
}
}

func add2(n int) int{
return n + 2
}

パッケージ的なものを自作していると、

  • どんな型の変数も渡せる
  • 特定の型の関数を渡せる

ような関数が欲しくなることがあります。具体的には以下のように使える関数です

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func exec(
v interface{}, // value
f interface{}, // valueを引数に実行するfunc
) interface{} {
fv := reflect.ValueOf(f)
if fv.Kind() != reflect.Func{
panic("2'nd argument is not func.")
}
rv := reflect.ValueOf(v)
return fv.Call([]reflect.Value{rv})[0]
}

// 以下のように使える
exec(5, func(i int) int{ return i * ( i + 1) })
exec("Tom", func(s string) string{ return "hello! " + s })

// 第一引数と、第二引数の引数の型が違うと、buildはできるが実行時にエラーが起きる
exec("Tom", func(i int) int{ return i * ( i + 1) })

build時に、エラーを出してほしいのですが、なかなかそうもいかない…

https://qiita.com/umanoda/items/07887d33ef1155b26ed2

琴声何在 生死难猜 用一生去等待

文字列を結合する

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
a := "Hello"
b := a + " World"
fmt.Println(b)
}

繰り返し文字列を生成する

1
2
3
4
5
6
7
8
9
package main

import "fmt"
import "strings"

func main() {
s := "Hey "
fmt.Println(strings.Repeat(s, 3))
}

大文字・小文字に揃える

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"
import "strings"

func main() {
s := "I love GoLang"
fmt.Println(strings.ToUpper(s))
fmt.Println(strings.ToLower(s))
}

大文字と小文字の入れ替え

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"
import "strings"

func main() {
//うーん、かなりイマイチ
s := "i lOVE gOLANG"
sr := ""
for _, x := range s {
xs := string(x)
u := strings.ToUpper(xs)
l := strings.ToLower(xs)
if u == xs {
sr += l
} else if l == xs {
sr += u
} else {
sr += xs
}
}
fmt.Println(sr)
}

コマンドの実行結果を文字列に

1
2
3
4
5
6
7
8
9
package main

import "fmt"
import "os/exec"

func main() {
out, _ := exec.Command("date").Output()
fmt.Println(string(out))
}

複数行の文字列を作成する

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
s := `
This is a test.

GoLang, programming language developed at Google.
`
fmt.Println(s)
}

ヒアドキュメントの終端文字列をインデントする

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"
import . "github.com/MakeNowJust/heredoc/dot"

func main() {
s := D(`
This is a test.

GoLang, programming language developed at Google.
`)
fmt.Println(s)
}

複数行のコマンドの実行結果を文字列に設定する

exec.Command()を使います。 (echoはシェル組み込みコマンドのせいか実行されない。。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"
import "os/exec"

func main() {
s := D(`
date
echo "-----------------------------"
ps
`)
outs := ""
for _, s = range strings.Split(s, "\n") {
out, _ := exec.Command(s).Output()
outs += string(out)
}
fmt.Println(outs)
}

部分文字列を取り出す

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
s := "Apple Banana Orange"
fmt.Println(s[0:5]) // => Apple
fmt.Println(s[6:(6 + 6)]) // => Banana
fmt.Println(s[0:3]) // => App
fmt.Println(s[6]) // => 66
fmt.Println(s[13:19]) // => Orange
}

部分文字列を置き換える

(うーんいまいち。。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"
import "strings"

func main() {
s := "Apple Banana Orange"
ss := strings.Split(s, "")
srep := strings.Split("Vine ", "")
for i, _ := range srep {
ss[i] = srep[i]
}

fmt.Println(strings.Join(ss, ""))
}

文字列中の式を評価し値を展開する

intなら%dでもよいですが、型推定をよさげに行うのなら%vが使えます。

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
value := 123
fmt.Printf("value is %v\n", value)
}

文字列を1文字ずつ処理する

こちらによると、 string[n]だとbyte単位、rangeで回すとrune(utf-8文字)単位らしいです。

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
sum := 0
for _, c := range "Golang" {
sum = sum + int(c)
}
fmt.Println(sum)
}

文字列の先頭と末尾の空白文字を削除する

空白ならTrimSpace, 任意の文字でやりたければTrimでやれます。

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"
import "strings"

func main() {
s := " Hello, Golang! "
s = strings.TrimSpace(s)
fmt.Println(s)
}

文字列を整数に変換する (to_i)

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"
import "strconv"

func main() {
i := 1
s := "999"
si, _ := strconv.Atoi(s)
i = i + si
fmt.Println(i)
}

文字列を浮動小数点に変換する (to_f)

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"
import "strconv"

func main() {
s := "10"
sf, _ := strconv.ParseFloat(s, 64) //64 bit float
fmt.Println(sf)
}

8進文字列を整数に変換する

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"
import "strconv"

func main() {
s := "010"
so, _ := strconv.ParseInt(s, 8, 64) // base 8, 64bit
fmt.Println(so)
}

16進文字列を整数に変換する

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

import "strconv"

func main() {
s := "ff" // 0xは含んではいけません。
sh, _ := strconv.ParseInt(s, 16, 64)
fmt.Println(sh)

s = "0xff" // 0xを含んでも良い
sh, _ = strconv.ParseInt(s, 0, 64)
fmt.Println(sh)

}

ASCII文字をコード値に(コード値をASCII文字に)変換する

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import "fmt"

func main() {
s := "ABC"
fmt.Println(s[0])
fmt.Println(string(82))
}
```

### 文字列を中央寄せ・左詰・右詰する
```go
package main

import "fmt"
import "strings"

func main() {
//右詰、左詰めはformat文字列が対応しています。±で指定。
s := "Go"
fmt.Printf("%10s\n", s)
fmt.Printf("%-10s\n", s)

//センタリングはなさそうです。
l := 10
ls := (l - len(s)) / 2
cs := strings.Repeat(" ", ls) + s + strings.Repeat(" ", l-(ls+len(s)))
fmt.Println(cs)
}

“次”の文字列を取得する

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package main

import "fmt"

func main() {
fmt.Println(succ("9")) // => "10"
fmt.Println(succ("a")) // => "b"
fmt.Println(succ("AAA")) // => "AAB"
fmt.Println(succ("A99")) // => "B00"
fmt.Println(succ("A099")) // => "A100"
}

// 文字列を反転して返す
func reverse(s string) string {
ans := ""
for i, _ := range s {
ans += string(s[len(s)-i-1])
}
return string(ans)
}

// "次"の文字列を取得する
func succ(s string) string {
r := reverse(s)
ans := ""
carry := 1
lastLetter := string(r[0])
for i, _ := range r {
lastLetter = string(r[i])
a := lastLetter
if carry == 1 {
if lastLetter == "z" {
a = "a"
carry = 1
} else if lastLetter == "Z" {
a = "A"
carry = 1
} else if lastLetter == "9" {
a = "0"
carry = 1
} else {
if r[i] == 0 {
a = "1"
} else {
a = string(r[i] + 1)
carry = 0
}
}
}
ans += a
}
if carry == 1 {
if lastLetter == "9" {
ans += "1"
} else if lastLetter == "z" {
ans += "a"
} else if lastLetter == "Z" {
ans += "A"
}
}
return reverse(ans)
}

文字列を暗号化する

とりあえずMD5あたりの例を書いておきます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import "fmt"
import "crypto/md5"
import "io"
import "bufio"

func main() {
h := md5.New()
io.WriteString(h, "hogehoge")

fmt.Print("input password >")
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()

h2 := md5.New()
io.WriteString(h2, scanner.Text())

if h2.Sum(nil)[0] == h.Sum(nil)[0] {
fmt.Println("right")
} else {
fmt.Println("wrong")
}

}

文字列中で指定したパターンにマッチする部分を置換する

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"
import "strings"

func main() {
s := "Apple Banana Apple Orange"
s = strings.Replace(s, "Apple", "Pine", 1) // 最後の引数は回数
fmt.Println(s)
s = strings.Replace(s, "Apple", "Pine", -1) // <0で無制限
fmt.Println(s)
}

文字列中に含まれている任意文字列の位置を求める

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"
import "strings"

func main() {
s := "Apple Banana Apple Orange"
fmt.Println(strings.Index(s, "Apple")) // => 0
fmt.Println(strings.Index(s, "Banana")) // => 6
// 途中から検索する方法は無いのでスライスで渡す
fmt.Println(strings.Index(s[6:], "Apple") + 6) // => 13
// 後方検索はLastIndex()
fmt.Println(strings.LastIndex(s, "Apple")) // => 13
fmt.Println(strings.Index(s[:(len(s)-6)], "Apple")) // => 0
}

文字列の末端の改行を削除する

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"
import "strings"

func main() {
s := "Hello, Golang!\n"
s = strings.TrimRight(s, "\n")
fmt.Println(s)
}

カンマ区切りの文字列を扱う

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"
import "strings"

func main() {

s := "001,ASHITANI Tatsuji,Yokohama"
slice := strings.Split(s, ",")
for _, x := range slice {
fmt.Println(x)
}
}

任意のパターンにマッチするものを全て抜き出す

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"
import "regexp"

func main() {
s := "hoge:045-111-2222 boke:045-222-2222"
re, _ := regexp.Compile("(\\S+):([\\d\\-]+)")
ans := re.FindAllStringSubmatch(s, -1) // [マッチした全体,1個目のカッコ,2個目のカッコ,..]の配列
fmt.Println(ans)
}

漢字コードを変換する

下記はEUCの例です。 指定するコーディングは、EUCJP,ISO2022JP,ShiftJISのどれかです。
こちらでご指摘いただいた点を 修正しました。’EUCJP’は’japanese.EUCJP’と呼びます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package main

import "fmt"
import "golang.org/x/text/encoding/japanese"
import "golang.org/x/text/transform"
import "strings"
import "io"
import "os"
import "bytes"

func main() {
KconvFwrite() // ファイル書き込み
KconvFread() // ファイル読み出し
KconvToBuffer() // バッファに書き込み
KconvFromBuffer() // バッファから読み出し
}

// ファイル書き込み
func KconvFwrite() {
s := "漢字です" // UTF8
f, _ := os.Create("EUC.txt")
r := strings.NewReader(s)
w := transform.NewWriter(f, japanese.EUCJP.NewEncoder()) // Encoder->f
io.Copy(w, r) // r -> w(->Encoder->f)
f.Close()
}

// ファイル読み出し
func KconvFread() {
f, _ := os.Open("EUC.txt")
b := new(bytes.Buffer)
r := transform.NewReader(f, japanese.EUCJP.NewDecoder()) // f -> Decoder
io.Copy(b, r) // (f->Decoder)->b
fmt.Println(b.String())
f.Close()
}

// バッファに書き込み
func KconvToBuffer() {
s := "漢字です" // UTF8
b := new(bytes.Buffer)
r := strings.NewReader(s)
w := transform.NewWriter(b, japanese.EUCJP.NewEncoder()) // Encoder->f
io.Copy(w, r) // r -> w(->Encoder->f)

st := b.String()
for i := 0; i < len(st); i++ {
fmt.Println(st[i])
}
fmt.Println(b.String())

}

// バッファから読み出し
func KconvFromBuffer() {
str_bytes := []byte{180, 193, 187, 250, 164, 199, 164, 185}
s := bytes.NewBuffer(str_bytes).String() // "漢字です" in EUC

sr := strings.NewReader(s)
b := new(bytes.Buffer)
r := transform.NewReader(sr, japanese.EUCJP.NewDecoder()) // sr -> Decoder
io.Copy(b, r) // (sr->Decoder)->b
fmt.Println(b.String())
}

マルチバイト文字の数を数える

こちらが詳しいです。 len()だとbyteカウント、[]runeに変換するとutf-8カウント。

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
s := "日本語"
fmt.Println(len(s)) // => 9
fmt.Println(len([]rune(s))) // => 3
}

マルチバイト文字列の最後の1文字を削除する

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
s := "日本語"
sc := []rune(s)
fmt.Println(string(sc[:(len(sc) - 1)])) // => "日本"
}

https://ashitani.jp/golangtips/tips_string.html#string_Split

我害怕,看到你,独自一人绝望; 更害怕,看不到你,不能和你一起迷惘……

go言語の encoding/csv パッケージの使い方。
CSVファイルの読み書きと、オプション、エラーハンドリングについて書いてます。

一行ずつ読み込む(Read)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
"encoding/csv"
"fmt"
"io"
"log"
"strings"
)

func main() {
s := `名前,年齢,身長,体重
Tanaka,31,190cm,97kg
Suzuki,46,180cm,79kg
Matsui,45,188cm,95kg
`
r := csv.NewReader(strings.NewReader(s))

for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}

// recordは配列
fmt.Printf("%#v\n", record)

// []string{"名前", "年齢", "身長", "体重"}
// []string{"Tanaka", "31", "190cm", "97kg"}
// []string{"Suzuki", "46", "180cm", "79kg"}
// []string{"Matsui", "45", "188cm", "95kg"}
}
}

ファイルから読み込む

ファイルから読み込む場合は csv.NewReader*os.File を渡す

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"encoding/csv"
"log"
"os"
)

func main() {
f, err := os.Open("file.csv")
if err != nil {
log.Fatal(err)
}

r := csv.NewReader(f)

for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
fmt.Println(record)
}
}

csv.Readerのオプション

Comma: 区切り文字を変更
Comment: コメント行の先頭になる文字を指定
FieldsPerRecord: 各行のフィールド数を指定
LazyQuotes: ダブルクオートを厳密にチェックするかどうか
TrimLeadingSpace: 先頭の空白文字を無視する
ReuseRecord: スライスの再利用
TrailingComma: Deprecated(非推奨)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
"encoding/csv"
"fmt"
"io"
"log"
"strings"
)

func main() {
s := `名前;年齢;身長;体重
# コメント行
Tanaka;31;190cm;97kg
# コメント行
Suzuki;46;180cm;79kg
# コメント行
Matsui;45;188cm;95kg
`

r := csv.NewReader(strings.NewReader(s))
r.Comma = ';' // 区切り文字を , から ; に変更
r.Comment = '#' // 先頭が # の場合はコメント行として扱う
r.FieldsPerRecord = 4 // 各行のフィールド数。多くても少なくてもエラーになる
r.LazyQuotes = true // true の場合、"" が値の途中に "180"cm のようになっていてもエラーにならない
r.TrimLeadingSpace = true // true の場合は、先頭の空白文字を無視する
r.ReuseRecord = true // true の場合は、Read で戻ってくるスライスを次回再利用する。パフォーマンスが上がる

for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}

// recordは配列
fmt.Printf("%#v\n", record)

// []string{"名前", "年齢", "身長", "体重"}
// []string{"Tanaka", "31", "190cm", "97kg"}
// []string{"Suzuki", "46", "180cm", "79kg"}
// []string{"Matsui", "45", "188cm", "95kg"}
}
}

エラーハンドリング(csv.ParseError)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
"encoding/csv"
"fmt"
"log"
"strings"
)

func main() {
s := `名前,年齢,身長,体重
Tanaka,31,190cm,97kg
Suzuki,46,180cm,79kg
Matsui,45,188cm,95kg
`
r := csv.NewReader(strings.NewReader(s))

records, err := r.ReadAll()
if err != nil {
if e, ok := err.(*csv.ParseError); ok {
n := 0
switch e.Err {
case csv.ErrBareQuote:
// ダブルクオート途中で使用されていて LazyQuotes を true にしていない場合のエラー
// 例えば、 Tan"aka,31,190cm,97kg のように 途中に " がある場合
n = 1
case csv.ErrQuote:
// 先頭がダブルクオートで始まっていて、末尾がダブルクオートになっていない場合のエラー
// 例えば、 "Tanaka,31,190cm,97kg のように閉じるための " がない場合
n = 2
case csv.ErrFieldCount:
// FieldsPerRecordで指定した数と異なる場合のエラー
n = 3
}
log.Fatal("\nエラー: ", n, "\n", e.Err,
"\nStartLine:", e.StartLine, "\nLine:", e.Line, "\nColumn:", e.Column)
}
log.Fatal(err)
}

fmt.Println(records)
}

読み込んだShift-JISファイルをUTF8にする

encoding/japanesetext/transform が必要なのでダウンロードする

1
$ go get golang.org/x/text/encoding/japanese golang.org/x/text/transform
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"encoding/csv"
"fmt"
"log"
"os"

"golang.org/x/text/encoding/japanese"
"golang.org/x/text/transform"
)

func main() {
f, err := os.Open("file-sjis.csv")
if err != nil {
log.Fatal(err)
}

r := csv.NewReader(transform.NewReader(f, japanese.ShiftJIS.NewDecoder()))

for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}

fmt.Println(record)
}
}

一度にすべて読み込む(ReadAll)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"encoding/csv"
"fmt"
"log"
"strings"
)

func main() {
s := `名前,年齢,身長,体重
Tanaka,31,190cm,97kg
Suzuki,46,180cm,79kg
Matsui,45,188cm,95kg
`
r := csv.NewReader(strings.NewReader(s))

record, err := r.ReadAll()
if err != nil {
log.Fatal(err)
}

fmt.Printf("%#v\n", record)
// [][]string{
// []string{"名前", "年齢", "身長", "体重"},
// []string{"Tanaka", "31", "190cm", "97kg"},
// []string{"Suzuki", "46", "180cm", "79kg"},
// []string{"Matsui", "45", "188cm", "95kg"},
// }
}

一行ずつ書き込む(Write)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
"encoding/csv"
"log"
"os"
)

func main() {
records := [][]string{
[]string{"名前", "年齢", "身長", "体重"},
[]string{"Tanaka", "31", "190cm", "97kg"},
[]string{"Suzuki", "46", "180cm", "79kg"},
[]string{"Matsui", "45", "188cm", "95kg"},
}

f, err := os.Create("file.csv")
if err != nil {
log.Fatal(err)
}

w := csv.NewWriter(f)

// オプション
w.Camma = ',' // デフォルトはカンマ区切りで出力される。変更する場合はこの rune 文字を変更する
w.UseCRLF = true // 改行文字を CRLF(\r\n) にする

for _, record := range records {
if err := w.Write(record); err != nil {
log.Fatal(err)
}
}

w.Flush() // バッファに残っているデータをすべて書き込む

if err := w.Error(); err != nil {
log.Fatal(err)
}
}

書き込み結果

1
2
3
4
5
$ cat file.csv
名前,年齢,身長,体重
Tanaka,31,190cm,97kg
Suzuki,46,180cm,79kg
Matsui,45,188cm,95kg

すべて一度に書き込む(WriteAll)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"encoding/csv"
"log"
"os"
)

func main() {
records := [][]string{
[]string{"名前", "年齢", "身長", "体重"},
[]string{"Tanaka", "31", "190cm", "97kg"},
[]string{"Suzuki", "46", "180cm", "79kg"},
[]string{"Matsui", "45", "188cm", "95kg"},
}

f, err := os.Create("file.csv")
if err != nil {
log.Fatal(err)
}

w := csv.NewWriter(f)

w.WriteAll(records)

w.Flush()

if err := w.Error(); err != nil {
log.Fatal(err)
}
}

CSVを構造体にマッピングする

以下のように書いてみたけど、reflect 使うとさらに面倒そう。
なので gocsv (gocsvのgodocページ) を使えば、読み込んだCSVを構造体にマッピングできて、 その逆で構造体からCSVに変換するのも楽にできそう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package main

import (
"encoding/csv"
"fmt"
"io"
"log"
"strconv"
"strings"
)

type People struct {
Name string
Age int
Height string
Weight string
}

func main() {
s := `名前,年齢,身長,体重
Tanaka,31,190cm,97kg
Suzuki,46,180cm,79kg
Matsui,45,188cm,95kg
`
r := csv.NewReader(strings.NewReader(s))

var p []People

for i := 0; ; i++ {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
if i == 0 {
continue
}

var people People
for i, v := range record {
switch i {
case 0:
people.Name = v
case 1:
people.Age, err = strconv.Atoi(v)
if err != nil {
log.Fatal(err)
}
case 2:
people.Height = v
case 3:
people.Weight = v
}
}
p = append(p, people)
}

fmt.Println(p)
}

https://golang.hateblo.jp/entry/golang-encoding-csv

当我们搬开别人架下的绊脚石时,也许恰恰是在为自己铺路。

Basically, using laravel pipelines you can pass an object between several classes in a fluid way to perform any type of task and finally return the resulting value once all the “tasks” have been executed.

The most clear example about how pipelines works resides in one of the most used components of the framework itself. I’m talking about middlewares.

Middleware provide a convenient mechanism for filtering HTTP requests entering your application… This is how a basic middleware looks like:

1
2
3
4
5
6
7
8
<?php
app(Pipeline::class)
->send($content)
->through($pipes)
->via(‘customMethodName’) // <---- This one :)
->then(function ($content) {
return Post::create(['content' => $content]);
});

These “middlewares” are in fact just pipes by where the request is going to be sent thru, in order to perform any needed task. Here you can check if the request is an HTTP request, a JSON request, if there is any user authenticated, etc.

If you take a quick look to the Illuminate\Foundation\Http\Kernel class, you’ll see how the middlewares are executed by using a new instance of the Pipeline class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}

You can read in the code something like: a new pipeline that sends the request through a list of middlewares and then dispatch the router.

Don’t worry if this seems a little overwhelmed to you. Let’s try to clarify the concept with the follow example.

Working on a class that requires to run multiple tasks

Consider this situation. Let’s say you are building a forum where people can create threads and leave comments. But your client ask you to auto-remove tags or edit them on every piece of content when it’s created.

So this is what you are asked to do:

  • Replace link tags with plain text.
  • Replace bad words with “*”
  • Remove script tags entirely from the content

Probably you end up creating classes to handle each of these “tasks”.

1
2
3
4
5
6

$pipes = [
RemoveBadWords::class
ReplaceLinkTags::clas
RemoveScriptTags::class
];

What we are going to do is to pass the given “content” to each task and then return the result to the next one. We can do this using a pipeline.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
public function create(Request $request)
{
$pipes = [
RemoveBadWords::class,
ReplaceLinkTags::clas,
RemoveScriptTags::class
];
$post = app(Pipeline::class)
->send($request->content)
->through($pipes)
->then(function ($content) {
return Post::create(['content' => 'content']);
});
// return any type of response
}

Each “task” class should have a “handle” method to perform the action. Maybe it would be a good idea to have a contract to be implemented by each class:

1
2
3
4
5
6
7
<?php
namespace App;
use Closure;
interface Pipe
{
public function handle($content, Closure $next);
}
1
2
3
4
5
6
7
8
9
10
11
12
<?php
namespace App;
use Closure;
class RemoveBadWords implements Pipe
{
public function handle($content, Closure $next)
{
// Here you perform the task and return the updated $content
// to the next pipe
return $next($content);
}
}

The method used to perform the task should receive two parameters, the first one would be the passable object, and the second one would be a closure where the object is going to be redirected to after running the last pipe.

You can use a custom method name instead of ‘handle’. Then you need to specify the method name to be used by the pipeline, like so

1
2
3
4
5
6
7
8
<?php
app(Pipeline::class)
->send($content)
->through($pipes)
->via(‘customMethodName’) // <---- This one :)
->then(function ($content) {
return Post::create(['content' => $content]);
});

What happens at the end ?

What should happen here is that the post content is going to be modified by each one of the $pipes and at the end, this resulting content is going to be stored.

1
2
3
4
5
6
$post = app(Pipeline::class)
->send($request->all())
->through($pipes)
->then(function ($content) {
return Post::create(['content' => $content]);
});

Final words

Remember, there are tons of ways you can approach this type of issues. What you decide to do it’s up to you. But it is good to know that you have this tool in your arsenal to be used if necessary. I hope this example gives you a better understanding of what these “laravel pipelines” are and how to use them. You can also take a look at api laravel documents if you want to know more about how this

https://jeffochoa.me/understanding-laravel-pipelines