AnyMath 文档
Notebook

高级文件管理

让我们想象一个情况,我们有几个数据集分布在文件夹中,文件夹中也可能有垃圾(带有有趣浣熊的图片,文本文档)。 我希望能够找到必要的文件,以便以后与他们一起工作。 问题是Julia只实现了文件的基本工作-<https://engee.com/helpcenter/stable/ru-en/julia/base/file.html >,这显然不足以完成这样的任务。

在这篇文章中,我将使用称为树的数据结构制作一个更智能的工具来处理文件。

创建树

让我们观察一下,目录和文件的结构与树非常相似。 编程中的树是描述一种特殊图形的数据结构。 在这样的图中,除了"根"之外的所有节点都有一个父节点。:

image.png

用于图遍历和图修改的最优算法对于这样的图是已知的,并且它们通常已经实现。 我的想法如下:将文件夹及其子文件夹的内容表示为一棵树,其中的每个节点都将是一个文件或文件夹。 每个节点将单独存储创建和修改的路径,名称,扩展名,日期和时间以及文件夹属性。 并且为了组织一个树结构,我将存储这个节点的"后代"。:

In [ ]:
using Dates

struct FileTreeNode
    path::String
    name::String
    ext::String
    isdir::Bool
    created::DateTime
    modified::DateTime
    children::Vector{FileTreeNode}
end

这样,我将能够遍历文件和文件夹树比使用Julia标准库更容易和更快。

要获取文件创建和修改的日期和时间,以及获取文件名,路径和扩展名,我将编写额外的函数:

In [ ]:
function get_metadata(path::String)
    st = stat(path)
    created = unix2datetime(st.ctime)
    modified = unix2datetime(st.mtime)
    return created, modified
end

function split_name_ext(path::String)
    name = basename(path)
    base, ext = splitext(name)
    return base, ext
end
Out[0]:
split_name_ext (generic function with 1 method)

种植一棵树

已经创建了必要的数据结构和辅助功能,您可以继续执行目录结构树的实现。

让我们创建一个函数,该函数将接收文件或文件夹的创建和修改时间,特定路径的名称和扩展名。 接下来,使用 readdir 我们将获得当前文件夹内的文件和文件夹列表。

我们将对每个检测到的文件夹重复相同的操作。 也就是说,我们的函数将调用自己。 这种技术称为递归。

让我们为我们的功能添加一个限制:搜索深度。 此限制将限制文件夹的嵌套级别以爬网并加快树构建速度。

由于这个函数,我们得到了一个树节点。

In [ ]:
function build_tree(path::String; maxdepth=typemax(Int), depth=0)
    is_dir = isdir(path)

    name, ext = split_name_ext(path)
    created, modified = get_metadata(path)
    if is_dir && depth < maxdepth
        entries = readdir(path; join=true)
        children = [
            build_tree(e; maxdepth, depth=depth+1)
            for e in sort(entries)
        ]
    else
        children = FileTreeNode[]
    end

    return FileTreeNode(path, name, ext, is_dir, created, modified, children)
end
Out[0]:
build_tree (generic function with 1 method)

接下来,我们将简化使用AbstractTrees可视化树的任务。jl库。 我们将为我们的树定义两个新函数:

*children()-获取树节点的后代
*printnode()-在屏幕上显示节点

In [ ]:
import Pkg
Pkg.add("AbstractTrees")
import AbstractTrees: children, printnode

children(node::FileTreeNode) = node.children

function printnode(io::IO, node::FileTreeNode)

    if node.isdir
        print(io, "📁 ", node.name)
    else
        print(io, "📄 ", node.name, node.ext)
    end
end
   Resolving package versions...
   Installed FiniteDiff ──────────── v2.29.0
   Installed LazyArrays ──────────── v2.9.5
   Installed LineSearch ──────────── v0.1.6
   Installed DiffEqCallbacks ─────── v4.12.0
   Installed SparseMatrixColorings ─ v0.4.26
   Installed DiffEqNoiseProcess ──── v5.27.0
     Project No packages added to or removed from `~/.project/Project.toml`
    Manifest No packages added to or removed from `~/.project/Manifest.toml`
Out[0]:
printnode (generic function with 5 methods)

准备好了! 让我们检查一下这棵树:

In [ ]:
tree = build_tree(@__DIR__,maxdepth=1)
using AbstractTrees: print_tree
print_tree(tree)
📁 FileOps
├─ 📁 .git
├─ 📁 DataDepot
├─ 📄 dirprint.ngscript
├─ 📄 filetree_ops.ngscript
└─ 📄 filetree_ops_1.ipynb

把树付诸实践

让我们将位于DataDepot文件夹中的3个数据集粘合在一起。

首先,让我们看看我们的文件夹中有什么。:

In [ ]:
tree = build_tree(joinpath(@__DIR__,"DataDepot"),maxdepth=2)
print_tree(tree)
📁 DataDepot
├─ 📁 S1
│  ├─ 📄 data1.csv
│  └─ 📄 trash.rc
├─ 📁 S2
│  ├─ 📄 data2.csv
│  └─ 📄 notsotrash.dc
└─ 📁 S3
   └─ 📄 data3.csv

然后,我们将得到所有*。csv文件通过绕过上面创建的树。:

In [ ]:
function find_files_by_ext(node::FileTreeNode, ext::String,acc=String[])
    if !startswith(ext,".")
        ext = "."*ext
    end
    if isequal(node.ext,ext)
        push!(acc, node.path)
        println("$(acc)")
    end
    for c in node.children
        accchild = find_files_by_ext(c, ext)
        if ~isempty(accchild)
            append!(acc,accchild)

        end
    end
    return acc
end

csv_files = find_files_by_ext(tree,"csv")
["/user/work/FileOps/DataDepot/S1/data1.csv"]
["/user/work/FileOps/DataDepot/S2/data2.csv"]
["/user/work/FileOps/DataDepot/S3/data3.csv"]
Out[0]:
3-element Vector{String}:
 "/user/work/FileOps/DataDepot/S1/data1.csv"
 "/user/work/FileOps/DataDepot/S2/data2.csv"
 "/user/work/FileOps/DataDepot/S3/data3.csv"

现在让我们下载它们并将它们粘合在一起。:

In [ ]:
using CSV
df_v = Vector{DataFrame}()
for f in csv_files
    df = CSV.read(joinpath(pwd(),f),DataFrame)
    println("从$f文件中读取$(nrow(df))行")
    push!(df_v,df)
end
df_v = reduce(vcat,df_v)
已从/user/work/FileOps/DataDepot/S1/data1读取10行。csv档案
已从/user/work/FileOps/DataDepot/S2/data2读取10行。csv档案
已从/user/work/FileOps/DataDepot/S3/data3读取10行。csv档案
Out[0]:
30×3 DataFrame
5 rows omitted
Rowx1x2x3
Float64Float64Float64
10.8947660.736590.0567356
20.7408720.4223710.447714
30.6577770.8259970.168484
40.8940870.02343830.256112
50.8466650.03092550.895526
60.4627590.8838610.8525
70.1848760.4524160.432112
80.6785190.3619010.00114721
90.07336790.1528410.837117
100.5375860.1761290.0318991
110.8328410.3048720.843521
120.2476430.6342890.639223
130.3063910.0744850.85542
190.7236510.01892840.845076
200.288120.9466720.700205
210.8161040.01173130.145698
220.2942450.2190640.343588
230.3882120.5163990.235681
240.4011220.4960150.420165
250.1296090.7331280.098028
260.659690.1508910.102492
270.9379920.9765670.137387
280.8437420.8598980.522218
290.2764890.880830.271357
300.6450550.9117670.108152

结论

在这篇文章中,我们看了一个使用编程中的经典数据结构来解决实际工程问题的例子。 在随后的出版物中,将考虑促进技术计算任务的其他编程技术。