《高阶Perl》——1.4 层次化数据

简介: 本节书摘来自华章计算机《高阶Perl》一书中的第1章,第1.4节,作者(美)Mark Jason Dominus,译 滕家海,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

1.4 层次化数据

已经介绍的例子显示了递归过程的风采,但是它们遗漏了重要的一点。在介绍汉诺塔问题时,我说过当你想解决的问题可以简化成同样问题的更简形式时,递归是很有用的。但是这样的问题是普遍的这一点可能不清楚。

多数递归的函数是为处理递归的数据结构的建立。一个递归的数据结构就像一个列表、一棵树或者一个堆,它们根据相同数据结构的更小的实例定义而成。最常见的例子可能是文件系统目录结构。文件可以是:

image

文件可以是一个目录,含有一列文件,其中的一些可以是目录,再含有另一些文件,以此类推。处理这样的结构最有效的办法是递归过程。从概念上,每次调用这样一个过程就处理一个单一文件。这个文件可以是普通文件,也可以是目录,程序通过递归调用自身处理这个目录所含有的子文件。如果子文件也是目录,程序将继续递归调用。

下面的例子是一个函数,以目录名作为参数计算它包含的所有文件的总大小,以及它的子目录、子目录的子目录等。

### Code Library: total-size-broken
sub total_size {
  my ($top) = @_;
  my $total = -s $top;

初次调用此函数时,它有一个参数$top,是要检查的文件或目录的名字。函数首先用Perl的-s操作符得到这个文件或目录本身的大小。此操作符产生以字节为单位的文件大小。如果文件是一个目录,此操作符得到目录本身占用的磁盘空间,而不是指目录所含文件所占用的。请记住,目录本身是文件的一个列表,这个列表也占用一些空间。如果顶层文件确实是个目录,此函数会把它的内容的大小累计到总和$total中。

  return $total if -f $top;
  unless (opendir DIR, $top) {
    warn "Couldn't open directory $top: $!; skipping.\n";
    return $total;
  }

-f操作符检查参数是否是普通文件,如果是,那么函数可以直接返回总和。否则,它假设此顶层文件确实是个目录,并尝试用opendir()打开它。如果无法打开目录,函数发出一条警告消息并返回到目前的总值,它包括目录本身的大小,但不包括它所含内容的大小。

  my $file;
  while ($file = readdir DIR) {
    next if $file eq '.' || $file eq '..';
    $total += total_size("$top/$file");
  }

接下来的代码块,while循环,是函数的核心。它每次从目录中读取一个文件名,对后者递归调用自身,把结果累计到总和中。

  closedir DIR;
  return $total;
}

循环结束后,函数关闭目录并返回总和。

在循环中,函数略过了文件名如.和..,这些分别是目录本身和它的父目录的别名,如果函数不这样做,它将永不会结束,因为它将尝试计算许许多多的文件,如././././././fred和dir/../dir/../dir/../dir/fred。

这个函数有个大bug,事实上它根本不能工作。问题在目录句柄,即DIR,是全局的,因此函数是不可重入的。函数会失效,本质上和缺少my的factorial()失效一样。第一次调用进行顺利,但是如果total_size()递归调用自身,即第二次执行将打开同一个目录句柄DIR。最后,第二次调用将到达它的目录的终点,关闭DIR,然后返回,从这以后,第一次调用将尝试继续,发现DIR已经关闭了,那就退出while循环,还未从顶层目录读取所有的文件名。第二次执行递归调用自身时,也将有同样的问题。

结果就是写成的函数只遍历了目录树的第一个分支。如果目录层次有一个这样的结构,

image

那函数将沿着top-a-d路径,看到文件j和k,然后报告top+a+d+j+k的总大小,而没有注意到b、c、e、f、g、h、i或l。

为了修正这个错误,需要使目录句柄DIR成为一个私有变量,像$top和$total一样。这里没有用opendir DIR, $top,而是使用opendir $dir, $top,其中$dir是私有变量。当opendir的第一个参数是一个未定义的变量,opendir会产生一个新的、匿名的目录句柄并把它保存到$dir。

与其这样做:

opendir DIR, $somedir;
print (readdir DIR);
closedir DIR;

不如这样做:

my $dir;
opendir $dir, $somedir;
print (readdir $dir);
closedir $dir;

达到同样的效果。这有很大的区别:DIR是个全局目录句柄,能被程序的任何其他部分读取或关闭;$dir中的目录句柄是私有的,仅能被生成它的函数读取或关闭,或者被一些明确地传递了$dir的值的函数读取或关闭。

有了这个新的技巧,就可以重写total_size()函数以使其正确运行:

### Code Library: total-size
sub total_size {
  my ($top) = @_;
  my $total = -s $top;
  my $DIR;

  return $total if -f $top;
  unless (opendir $DIR, $top) {
    warn "Couldn't open directory $top: $!; skipping.\n";
    return $total;
  }

  my $file;
  while ($file = readdir $DIR) {
    next if $file eq '.' || $file eq '..';
    $total += total_size("$top/$file");
  }
  closedir $DIR;
  return $total;
}

实际上,此处的closedir不是必需的,因为此种方法产生的目录句柄会在含有它的变量离开作用域时自动关闭。当total_size()返回时,它的私有变量销毁了,包括$DIR,它包含了打开的目录句柄对象的最后一个引用。Perl接着销毁目录句柄对象,在此过程中,关闭目录句柄。未来将省略明确的closedir。

这个函数仍然有些问题:它没有正确处理符号链接,如果一个文件在同一目录中有两个名字,它会统计两次。此外,在Unix系统里,一个文件在磁盘上实际所占的空间通常与-s报告的长度不同,因为磁盘空间是每次按1024字节的块分配的。但是函数足够有用了,因此可以期望它很好地应用到一些别的任务中。如果要修正这些问题,将只需在这一处修正它们,而不用在50个不同的应用中50个略微不同的目录遍历函数中修正同样的问题。

相关文章
|
6天前
|
测试技术 索引
MindOpt APL向量化建模语法的介绍与应用(2)
在数据科学、工程优化和其他科学计算领域中,向量和矩阵的运算是核心组成部分。MAPL作为一种数学规划语言,为这些领域的专业人员提供了强大的工具,通过向量式和矩阵式变量声明以及丰富的内置数学运算支持,大大简化了数学建模和优化问题的处理。在本文中,我们将探索MAPL的这些特性,并且通过示例来展示如何有效使用这些工具。
|
6天前
|
机器学习/深度学习 算法 测试技术
MindOpt APL向量化建模语法的介绍与应用(1)
向量化建模是一种高效的数学建模和编程技术,它涉及到对向量、矩阵和更高维数组进行操作,以实现操作的同时性和批量处理。在优化和数据分析等领域,向量化建模可以极大地提高计算效率,特别是当涉及到大量的重复计算时。由于向量化建模具有表述优势、操作优势、计算性能、可扩展性等优势,使得其适合于解决很大一类实际问题
|
10月前
|
Python
线性回归 特征扩展的原理与python代码的实现
在线性回归中,多项式扩展是种比较常见的技术,可以通过增加特征的数量和多项式项的次数来提高模型的拟合能力。 举个例子,多项式扩展可以将一个包含 n 个特征的样本向量 x 扩展为一个包含 k 个特征的样本向量,其中 k 可以是 n 的任意多项式。例如,如果我们使用二次多项式扩展,可以将样本向量[x1, x2]扩展为一个包含原始特征和交叉项的新特征向量,例如 [x1, x2, x1^2, x2^2, x1*x2]。这些新特征可以捕捉到更丰富的特征组合和非线性关系,从而提高模型的拟合能力。
|
机器学习/深度学习 算法 数据挖掘
如何用Python计算特征重要性?(二)
如何用Python计算特征重要性?(二)
380 0
如何用Python计算特征重要性?(二)
|
机器学习/深度学习 算法 数据挖掘
一文归纳Python特征生成方法(全)
创造新的特征是一件十分困难的事情,需要丰富的专业知识和大量的时间。机器学习应用的本质基本上就是特征工程。 ——Andrew Ng
|
机器学习/深度学习 Python
自组织神经网络(SOM)的Python第三方库minisom分类功能实现
自组织神经网络(SOM)的Python第三方库minisom分类功能实现
|
机器学习/深度学习 数据挖掘 索引
自组织神经网络(SOM)的Python第三方库minisom聚类功能实现
自组织神经网络(SOM)的Python第三方库minisom聚类功能实现
自组织神经网络(SOM)的Python第三方库minisom聚类功能实现
|
机器学习/深度学习 资源调度 Python
|
缓存 前端开发 Perl
《高阶Perl》——3.12 速度的好处
本节书摘来自华章计算机《高阶Perl》一书中的第3章,第3.12节,作者(美)Mark Jason Dominus,译 滕家海,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
1697 0
|
缓存 自然语言处理 C语言
《高阶Perl》——3.5 MEMOIZE模块
本节书摘来自华章计算机《高阶Perl》一书中的第3章,第3.5节,作者(美)Mark Jason Dominus,译 滕家海,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
1515 0