本文目录一览:
- golang如何创建目录
- golang变量(二)——map和slice详解
- 怎么将数据库中存的树转化为树形列表
- 怎么用递归实现1-10的求和 golang
- golang结构体组合与“多态” 2021-08-06
- Linq To Sql 如何实现递归查询 树形结构
golang如何创建目录
golang中关于目录与文件名等操作都在os
这个包中,具体的创建目录都是通过Mkdir
和MkdirAll
这两个函数来实现的,这两个函数用法一致:
os.Mkdir(dirName string, perm FileMode)
dirName
即要创建的目录(文件夹路径),可以是绝对路径,也可以是相对路径(相对于GOPATH)perm
表示创建的目录的权限,如0777
(读r权限值为4,写权限w值为2,执行权限x值为1) 例如:我要在/data/program/goapp
这个目录下创建一个golang
子目录,示例如下:
package main
import (
"os"
"fmt"
)
func main() {
err := os.Mkdir("/data/program/goapp/golang", 0666)
if err != nil {
fmt.Println(err)
}
}
注:Mkdir和MkdirAll的区别
Mkdir
创建目录,它的父级目录必须是存在的,否则创建会失败MkdirAll
可以递归创建目录,即只要根目录存在即可,如下:
err := os.MkdirAll("/data/program/goapp/golang/test/hello", 0766)
if err != nil {
fmt.Println(err)
}
在本例中:/data/program/goapp
是已经存在的目录,而子目录golang/test/hello
是不存在的,此时要使用MkdirAll
来创建。
golang变量(二)——map和slice详解
衍生类型,interface{}
、map
、[]
、struct
等。
map
类似于 Java 的 HashMap
,Python 的 dict
,PHP 的哈希数组。
常规的 for
循环可以用 for k, v := range m {}
。但在清空时有一个需要注意的坑:
著名的 map[string]*struct
副本问题。
结果:
Go 中不存在引用传递,所有的参数传递都是值传递,而 map
是等同于指针类型的,所以在把 map
变量传递给函数时,函数对 map
的修改,也会实质改变 map
的值。
slice
类似于其他语言的数组(list、array),slice
初始化和 map
一样,这里不再重复。
除了 Pointer
数组外,len
表示使用长度,cap
是总容量,make([]int, len, cap)
可以预申请比较大的容量,这样可以减少容量拓展的消耗,前提是要用到。
cap
是计算切片容量,len
是计算变量长度的,两者不一样。具体例子如下:
结果:
分析:cap
是计算当前 slice
已分配的容量大小,采用的是预分配的伙伴算法(当容量满时,拓展分配一倍的容量)。
append
是 slice
非常常用的函数,用于添加数据到 slice
中,但如果使用不好,会有下面的问题:
预期是 [1 2 3 4 5 6 7 8 9 10]
,[1 2 3 4 5 6 7 8 9 10 11 12]
,但实际结果是:
注意 slice
是值传递,修改一下:
输出如下:
==
只能用于判断常规数据类型,无法用于 slice
和 map
判断,用于判断 map
和 slice
可以使用 reflect.DeepEqual
,这个函数用了递归来判断每层的键值是否一致。
当然还有其他方式,比如转换成 JSON,但小心有一些异常的 bug,比如 HTML 编码,具体这个 JSON 问题,待后面再分析。
怎么将数据库中存的树转化为树形列表
树状结构的数据保存在数据库中的常用方法有以下两种:
- 邻接表(adjacency list model)
- 预排序遍历树算法(modified preorder tree traversal algorithm) 用以下的例子讨论这两种方法的差异: 现有一棵树如下:
邻接表模式:
这种模式我们经常用到,很多的教程和书中也介绍过。我们通过给每个节点增加一个属性 parent
来表示这个节点的父节点,从而将整个树状结构通过平面的表描述出来。根据这个原则,例子中的数据可以转化成如下的表:
我们看到 Pear
是 Green
的一个子节点,Green
是 Fruit
的一个子节点。而根节点 Food
没有父节点。为了简单地描述这个问题,这个例子中只用 name
来表示一个记录。在实际的数据库中,你需要用数字的 id
来标示每个节点,数据库的表结构大概应该像这样:id
, parent_id
, name
, description
。
以下是代码:
<?php
// $parent 是我们想查看的子节点的父节点
// $level 是我们进入树的深度,用于显示缩进的树
function display_children($parent, $level) {
// 获取父节点 $parent 的所有子节点
$result = mysql_query('SELECT name FROM tree '.
'WHERE parent="'.$parent.'";');
// 显示每个子节点
while ($row = mysql_fetch_array($result)) {
// 缩进显示节点名称
echo str_repeat(' ', $level).$row['name']."\n";
// 再次调用这个函数显示子节点的子节点
display_children($row['name'], $level+1);
}
}
?>
对整个结构的根节点(Food)使用这个函数就可以打印出整个多级树结构,由于 Food 是根节点它的父节点是空的,所以这样调用: display_children('', 0)
。将显示整个树的内容:
Food
Fruit
Red
Cherry
Yellow
Banana
Meat
Beef
Pork
如果你只想显示整个结构中的一部分,比如说水果部分,就可以这样调用:display_children('Fruit', 0);
几乎使用同样的方法我们可以知道从根节点到任意节点的路径。比如 Cherry 的路径是 "Food ; Fruit ; Red"。为了得到这样的一个路径,我们需要从最深的一级 "Cherry" 开始,查询得到它的父节点 "Red" 把它添加到路径中,然后我们再查询 Red 的父节点并把它也添加到路径中,以此类推直到最高层的 "Food"。
以下是代码:
<?php
// $node 是那个最深的节点
function get_path($node) {
// 查询这个节点的父节点
$result = mysql_query('SELECT parent FROM tree '.
'WHERE name="'.$node.'";');
$row = mysql_fetch_array($result);
// 用一个数组保存路径
$path = array();
// 如果不是根节点则继续向上查询
// (根节点没有父节点)
if ($row['parent']!='') {
// the last part of the path to $node, is the name
// of the parent of $node
$path[] = $row['parent'];
// we should add the path to the parent of this node
// to the path
$path = array_merge(get_path($row['parent']), $path);
}
// return the path
return $path;
}
?>
如果对 "Cherry" 使用这个函数:print_r(get_path('Cherry'))
,就会得到这样的一个数组:
Array
(
[0] => Food
[1] => Fruit
[2] => Red
)
接下来如何把它打印成你希望的格式,就是你的事情了。
缺点:
这种方法很简单,容易理解,好上手。但是也有一些缺点。主要是因为运行速度很慢,由于得到每个节点都需要进行数据库查询,数据量大的时候要进行很多查询才能完成一个树。另外由于要进行递归运算,递归的每一级都需要占用一些内存,所以在空间利用上效率也比较低。
预排序遍历树算法
现在让我们看一看另外一种不使用递归计算,更加快速的方法,这就是预排序遍历树算法(modified preorder tree traversal algorithm)。这种方法大家可能接触的比较少,初次使用也不像上面的方法容易理解,但是由于这种方法不使用递归查询算法,有更高的查询效率。
我们首先将多级数据按照下面的方式画在纸上,在根节点 Food
的左侧写上 1
,然后沿着这个树继续向下在 Fruit
的左侧写上 2
,然后继续前进,沿着整个树的边缘给每一个节点都标上左侧和右侧的数字。最后一个数字是标在 Food
右侧的 18
。在下面的这张图中你可以看到整个标好了数字的多级结构。
这些数字标明了各个节点之间的关系,"Red" 的号是 3
和 6
,它是 "Food" 1-18
的子孙节点。同样,我们可以看到所有左值大于 2
和右值小于 11
的节点都是 "Fruit" 2-11
的子孙节点。
这样整个树状结构可以通过左右值来存储到数据库中。继续之前,我们看一看下面整理过的数据表。
注意:由于 "left" 和 "right" 在 SQL 中有特殊的意义,所以我们需要用 "lft" 和 "rgt" 来表示左右字段。另外这种结构中不再需要 "parent" 字段来表示树状结构。也就是说下面这样的表结构就足够了。
SELECT * FROM tree WHERE lft BETWEEN 2 AND 11;
看到了吧,只要一个查询就可以得到所有这些节点。为了能够像上面的递归函数那样显示整个树状结构,我们还需要对这样的查询进行排序。用节点的左值进行排序:
SELECT * FROM tree WHERE lft BETWEEN 2 AND 11 ORDER BY lft ASC;
那么某个节点到底有多少子孙节点呢?很简单,子孙总数 = (右值 - 左值 - 1)/2
。
添加同一层次的节点的方法如下:
LOCK TABLE nested_category WRITE;
SELECT @myRight := rgt FROM nested_category WHERE name = 'Cherry';
UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft + 2 WHERE lft > @myRight;
INSERT INTO nested_category(name, lft, rgt) VALUES('Strawberry', @myRight + 1, @myRight + 2);
UNLOCK TABLES;
添加树的子节点的方法如下:
LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft FROM nested_category WHERE name = 'Beef';
UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myLeft;
UPDATE nested_category SET lft = lft + 2 WHERE lft > @myLeft;
INSERT INTO nested_category(name, lft, rgt) VALUES('charqui', @myLeft + 1, @myLeft + 2);
UNLOCK TABLES;
每次插入节点之后都可以用以下 SQL 进行查看验证:
SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;
删除节点的方法,稍微有点麻烦是有个中间变量,如下:
LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
FROM nested_category WHERE name = 'Cherry';
DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;
UNLOCK TABLES;
这种方式有点难理解,但适合数据量很大的场景使用。查看所有的结构只需要两条 SQL 语句就可以完成,在添加节点和删除节点的时候略显麻烦,不过相对于效率来说还是值得的。
怎么用递归实现1-10的求和 golang
- 打出主函数
defs(n)
:如果n == 1
。 - 在打出条件函数
return 1 else
:result = n + s(n-1)
,return result
。 - 最后结尾返回函数的值
result = s(10)
,print(result)
。
golang结构体组合与“多态” 2021-08-06
如:
核心思想就是,外层实现接口,通过递归嵌套将被实现的接口实例置于内层,从而达到外层定义,内层使用的效果:
BaseBase
和 Derived
都是外层结构体,在它们这一层实现了 F2()
。ori_impl_1
以及 ori_impl_2
都是外层结构体实现的 B
接口实例,置于内层完成调用。
struct
中的字段可以不用给名称,这时称为匿名字段。匿名字段的名称强制和类型相同。例如:
如果 struct
中嵌套的 struct
类型是自己的指针类型,可以用来生成链表或二叉树等数据结构。
例如,定义一个单链表数据结构:
Linq To Sql 如何实现递归查询 树形结构
构造测试数据:只作演示用
CREATE TABLE [dbo].[Tim_LinqTable](
[Id] int PRIMARY KEY IDENTITY(1,1) NOT NULL,
[Name] [varchar](50) NOT NULL,
[Parent] int NOT NULL,
)
GO
INSERT INTO [Tim_LinqTable]
SELECT 'A',0 UNION ALL
SELECT 'A1',1 UNION ALL
SELECT 'A2',1 UNION ALL
SELECT 'B1',2 UNION ALL
SELECT 'B2',3 UNION ALL
SELECT 'C1',4 UNION ALL
SELECT 'C2',4 UNION ALL
SELECT 'D1',5 UNION ALL
SELECT 'D2',5 UNION ALL
SELECT 'D3',5
GO
WITH temp
AS
(
SELECT * FROM [Tim_LinqTable] WHERE Parent = 3
UNION ALL
SELECT m.* FROM [Tim_LinqTable] AS m
INNER JOIN temp AS child ON m.Parent = child.Id
)
SELECT * FROM temp
GO
查询 Parent=3
的所有子数据结果如下:
Id Name Parent
----------- -------------------------------------------------- -----------
5 B2 3
8 D1 5
9 D2 5
10 D3 5
(4 row(s) affected) 好,下边来看看用 C# 怎么实现上边的 SQL 语句吧:
void Main()
{
var query = GetClassID(3);
Console.WriteLine("Id\tName\tParent");
query.ToList().ForEach(q => Console.WriteLine("{0}\t{1}\t{2}", q.Id, q.Name, q.Parent));
/*
Id Name Parent
5 B2 3
8 D1 5
9 D2 5
10 D3 5
*/
}
public IEnumerable<Tim_LinqTable> GetClassID(int p_id)
{
var query = from c in this.Tim_LinqTables
where c.Parent == p_id
select c;
return query.ToList().Concat(query.ToList().SelectMany(t => GetClassID(t.Id)));
}