最佳做法:如何构建数组 - 标准和命名约定


Best Practice: How to Structure Arrays - Standards and Naming Conventions

维数组结构中关于哪些元素包含迭代器与细节元素的最佳实践是什么?

的大部分编程经验(我主要是为了好玩(来自谷歌上的教程,所以如果这似乎是一个非常愚蠢的问题,我提前道歉 - 但我确实想开始改进我的代码。

每当我需要制作一个多维数组时,我的命名总是将计数器放在第一个元素中。

例如,如果我有一个单维数组,如下所示:

$myArray['year']=2012;
$myArray['month']='July';
$myArray['measure']=3;
// and so on.

但是,如果我想使相同的数组保留一些历史记录所有者,我会添加另一个维度并将其格式化如下:

$myArray[$owner]['year']=2012;
$myArray[$owner]['month']='July';
$myArray[$owner]['measure']=3;

编辑:为了确保我的例子不会令人反感或朝着正确的方向前进,我基本上遵循以下结构:

$myArray[rowOfData][columnOfData]

现在,我的问题是关于公认的惯例。我应该执行以下操作吗?

$myArray['year'][$owner]=2012;
$myArray['month'][$owner]='July';
$myArray['measure'][$owner]=3;
编辑:

使用上面的编辑,应该是:

$myArray[columnOfData][rowOfData]

我已经搜索了数组命名约定,但不断点击争论是否将数组命名为复数的文章。我命名它们的方式似乎更合乎逻辑,我认为它遵循一个更类似于对象的结构,即 object->secondaryLevel->detail但就我所知,我一直在做这件事。随着我越来越喜欢编程,如果他们错了,我宁愿改变我的习惯。

是否有公认的标准,或者只是数组的任何东西?如果您正在查看其他人编写的代码,会期望什么格式?我知道任何有意义/直观的结构都被接受。

同样从迭代的角度来看,以下哪一项更直观?

for($i=0;$i<$someNumber;$i++)
{
    echo $myArray[$i]['year'];
    // OR
    echo $myArray['year'][$owner];
}

编辑:我确实将这篇文章标记为c#和Java,因为我想在PHP程序员之外获得一些意见。我认为由于数组在许多不同的语言中使用,因此最好从各种语言的程序员那里获得一些输入。

你的问题是主观的,因为每个人可能对你所说的情况有不同的看法,你甚至明智地提出这个问题;如何最好地命名变量、类等。可悲的是,我花的时间比我愿意承认的要多,以确定有意义并满足要求的最佳变量名称。我的最终目标是编写"自我记录"的代码。通过编写自记录代码,您会发现在出现缺陷时添加功能或修复缺陷要容易得多。

多年来,我发现这些做法最适合我:

数组:始终复数

我这样做是为了让循环控制结构更具语义意义,并且更容易使用。

// With a plural array it's easy to access a single element
foreach ($students as $student) {}
// Makes more sense semantically
do {} (while (count($students) > 0);

对象数组>深度多维数组

在您的示例中,您的数组开始膨胀为 3 元素深度的多维数组,并且与 Robbie 的代码片段一样正确,它展示了迭代多维数组所需的复杂性。相反,我建议创建可以添加到数组中的对象。请注意,以下代码仅供演示,我始终使用访问器。

class Owner
{
    public $year;
    public $measure;
    public $month;
}
// Demonstrative hydration 
for ($i = 1 ; $i <= 3 ; $i++) {
    $owner = new Owner();
    $owner->year = 2012;
    $owner->measure = $i;
    $owner->month = rand(1,12);
    $owners[] = $owner;
}

现在,您只需遍历平面数组即可访问所需的数据:

foreach ($owners as $owner) {
    var_dump(sprintf('%d.%d: %d', $owner->month, $owner->year, $owner->measure));
}

这种对象数组方法的酷之处在于添加增强功能非常容易,如果要添加所有者名称怎么办?没问题,只需将成员变量添加到您的类中并稍微修改一下水合:

class Owner
{
    public $year;
    public $measure;
    public $month;
    public $name;
}
$names = array('Lars', 'James', 'Kirk', 'Robert');
// Demonstrative hydration 
for ($i = 1 ; $i <= 3 ; $i++) {
    $owner = new Owner();
    $owner->year = 2012;
    $owner->measure = $i;
    $owner->month = rand(1,12);
    $owner->name = array_rand($names);
    $owners[] = $owner;
}
foreach ($owners as $owner) {
    var_dump(sprintf('%s: %d.%d: %d', $owner->name, $owner->month, $owner->year, $owner->measure));
}

你必须记住,上面的代码片段只是建议,如果你宁愿坚持使用深度多维数组,那么你将不得不找出一个对你和一起工作的人有意义的元素排序安排,如果你认为你会在六个月后的设置中遇到麻烦, 那么最好在有机会的时候实施更好的策略。

您希望自己(以及潜在的其他用户(能够轻松理解您的代码正在做什么,以及您在编写代码时在想什么。想象一下寻求帮助,想象输出调试代码,或者想象在你上次接触代码 12 个月后回来修复一个错误。对于您/其他人来说,哪个是最好和最快的理解?

如果您的代码要求您"每年显示数据",那么第一个更合乎逻辑。如果您的想法是"我需要将所有措施收集在一起,然后我会处理这些措施",那么请选择第二种选择。如果您需要按年份重新订购,请选择第一个。

根据您上面的例子,尽管我处理上述问题的方式可能是:

$array[$year][$month] = $measure;

您不需要特定的"度量"元素。或者,如果您每月有两个元素:

$array[$year][$month] = array('measure' => $measure, 'value'=>$value);
or
$array[$year][$month]['measure'] = $measure;
$array[$year][$month]['value'] = $value;

然后你可以去:

for($year = $minYear; $year <= $maxYear; $year++) {  // Or "foreach" if consecutive
    for ($month = 1; $month <= 12; $month++) {
        if (isset($array[$year][$month])) {
             echo $array[$year][$month]['measure'];  // You can also check these exist using isset if required
             echo $array[$year][$month]['value'];
        } else {
             echo 'No value specified'
        }
    }
}

希望对你的思考有所帮助。

你做对了。你必须意识到PHP没有真正的多维数组;你看到的是一个数组数组,每个数组都是一维的。主数组存储指针(如您所说的"迭代器"(。因此,行优先是唯一合理的方法:

在您的特定示例中,您可以将二维数组视为包含对象的集合,每个对象都有 'year', 'month','measure' 的值(加上主键 'owner' (。通过填写主索引,可以这样引用二维数组的每一行:$myArray[$owner] .每个这样的值都是一个三元素数组,其中键'year', 'month','measure'。换句话说,它与相同信息的原始一维数据结构相同!您可以将其传递给仅处理表的一行的函数,可以轻松地对$myArray行进行排序,等等。

如果将索引放在相反的位置,则无法恢复单个记录。没有"切片"符号可以为您提供二维数组的整个"列"。

现在从更广泛的角度来看:

既然您从行和列的角度提出了问题,请注意,将"行"索引放在第一位会使您的数组与矩阵算术兼容。如果您必须使用矩阵进行计算,这是一个巨大的胜利。数据库表示法还将记录放在行中,因此向后执行会不必要地使事情复杂化。

C 有真正的二维数组指针数组。指针数组的工作方式与 PHP 中完全相同(尽管只允许使用数字索引(,并且出于同样的原因:主要索引从指针数组中进行选择,次要索引只是指向数组的索引。C 的二维数组的工作方式相同:主要索引位于左侧,内存中的相邻位置相差次要(第二个(索引的一个值(当然,行尾除外(。这使得它们与指针数组兼容,因为可以使用单个索引引用二维数组的一行。例如,a[0] abcd

        a[.][0] a[.][1] a[.][2] a[.][3]
a[0]:      a       b       c       d    
a[1]:      e       f       g       . 
a[2]:      .       .       .       .    
a[3]:      .       .       .       .    

系统无缝工作,因为主要(行(索引排在第一位。Fortran 具有真正的二维数组,但主要索引位于右侧:内存中的相邻位置相差左(第一个(索引的一个值。我发现这很痛苦,因为没有子表达式以同样的方式简化为一维数组。(但我有C背景,所以我当然有偏见(。

简而言之:你做对了,这可能不是偶然的,而是因为你通过查看编写良好的代码来学习。

在我看来,这不是一个关于数组命名约定的问题,而是一个关于如何构建数据的问题。含义:哪些信息属于一起 - 为什么?要回答这个问题,您必须同时考虑可读性和性能。

从 Java 开发人员的角度来看(您也为 Java 标记了这个问题(,我不是多维数组的朋友,因为它们往往会在大量嵌套的 for 循环中导致容易出错的索引杂技。为了摆脱数组中的第二维,可以创建包含一列信息的附加对象。

现在要做出的决定是,哪些数据应该嵌入到这个封闭对象中。在您的情况下,答案很简单:有关一个用户的数据集合有意义,而多个任意用户的一个属性的不相关值列表通常没有意义。

即使您没有将数据封装到对象中,而是更喜欢使用多维数组,您也应该牢记这些想法,并将数组的最后一维(在本例中为一列(视为等效于封装对象。您的数据结构应该在其所有抽象级别上都很有用,这通常意味着您将使用的内容放在一起。

我相信,当您使用要作为集合体保存的数据时,最好将其存储在单个数据集(例如关联数组或对象(中。以下是可用于存储数据集的结构示例。

在 PHP 中,作为关联数组:

$owner = array(
    'year' => 2012, 'month' => 'July', 'measure' => 3
);

在 C# 中,作为哈希表:

Hashtable owner = new Hashtable();
owner.Add("year", 2012);
owner.Add("month", "July");
owner.Add("measure", 3);

在 Java 中,作为哈希表:

Hashtable owner = new Hashtable();
owner.put("year", new Integer(2012));
owner.put("month", new String("July"));
owner.put("measure", new Integer(3));

在 C#/Java 中,作为对象:

public class Owner {
    public int year;
    public string month;
    public int measure;
}
Owner o = new Owner();
o.year = 2012;
o.month = "July";
o.measure = 3;

在 C# 和 Java 中,使用对象而不是哈希表的优点是可以为每个字段/变量声明变量类型(即 int 或字符串(,这将有助于防止错误并保持数据完整性,因为当您尝试将错误类型的数据分配给字段时,将引发错误(或将生成警告(。

在 PHP 中,我发现在存储数据集合时对象与数组相比没有真正的优势,因为类型提示不允许标量变量,这意味着需要额外的代码来检查/限制在属性中输入的数据,您必须编写额外的代码来声明类,您可能会通过拼写错误的名称意外地将值分配给错误的属性(与数组相同(因此,它无助于数据完整性(,并且属性的迭代/操作也需要额外的代码。我还发现在 PHP 中与 JSON 转换时,使用关联数组

更容易。

根据问题中的代码,当您需要通过其中一个字段或某些其他条件(例如字段的组合或字段将映射到的数据(访问数据时,通常需要向数组添加另一个维度。

这就是哈希表和关联数组对每种语言更有用的地方。例如,如果要根据年份和月份将所有者组织成组,则可以创建关联数组(或哈希表(来执行此操作。

下面的 PHP 示例使用 PDO 和 fetchAll 从数据库中获取信息:

$sth = $dbh->prepare("SELECT year, month, measure FROM owners");
$sth->execute();
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
$data = array();
foreach ($rows as $row) {
    $year = $row['year'];
    $month = $row['month'];
    if (!isset($data[$year])) {
        $data[$year] = array();
    }
    if (!isset($data[$year][$month])) {
        $data[$year][$month] = array();
    }
    array_push($data[$year][$month], $row);
}

此数据可能如何显示为代码的示例如下:

$data = array(
    2011 => array(
        'July' => array(
            array('year' => 2011, 'month' => 'July', 'measure' => 1),
            array('year' => 2011, 'month' => 'July', 'measure' => 3)
        ),
        'May' => array(
            array('year' => 2011, 'month' => 'May', 'measure' => 9),
            array('year' => 2011, 'month' => 'May', 'measure' => 4),
            array('year' => 2011, 'month' => 'May', 'measure' => 2)
        )
    ),
    2012 => array(
        'April' => array(
            array('year' => 2012, 'month' => 'April', 'measure' => 7)
        )
    )
);

然后,您可以使用密钥访问数据。

$data[2011]['July'];
// array(
//     array('year' => 2011, 'month' => 'July', 'measure' => 1),
//     array('year' => 2011, 'month' => 'July', 'measure' => 3)
// )

此外,我尝试在创建表示数据集合的对象时将其保持在最小值。如果要将集合存储在对象中,则当您开始添加对集合执行操作的函数时,将有更多的代码需要维护。有时这是必要的,例如,如果您需要限制可以通过 setter 存储哪些值,但是如果您所做的只是将数据从用户传递到数据库并再次显示在屏幕上,那么通常不需要数据收集中的高级功能,并且在其他地方管理该功能可能更有意义。例如,View 类可以处理显示数据,模型类可以处理提取数据,Check 对象可以验证是否可以保存数据或验证是否可以基于集合中的数据执行操作。

我很少在 Java 中使用数组,这同样适用于 C#。它们是面向对象的语言,从长远来看,如果您使用类和对象而不是数组等原始结构,您通常会获得更好的灵活性。

您的示例看起来像一种称为关联数组的交叉引用或查找。你把元素放在哪里,实际上取决于你将如何使用它。这是特定于问题的设计决策。你需要问问自己,你将从什么开始,你想以什么结束?

看起来您想根据要查找的内容检索不同的类型?月份是字符串,年份是整数等。这使得数组或 HashMap 成为一个糟糕的选择,因为调用代码需要再次猜测它正在检索的数据类型。更好的设计是将此数据包装在类型安全的对象中。这就是使用Java和C#的全部意义所在。

在这种情况下,使用对象会给你灵活性来检查月份是否真的是一个实际值。例如,您可以创建一个包含月份的枚举,并在getMonth方法中返回此实例。

另外,我没有足够的代表点发表评论,但我想回复戴夫F的答案。Hashtable 是 Java 早期的一个遗留类,因此应该避免使用。HashMap 是推荐的替代品,两者都实现了 Map 接口。

在Java的早期,集合类是同步的,使它们是线程安全的(Vector,Hashtable等(。这使得这些基本类不必要地变慢并妨碍了性能。如果你现在需要一个同步的地图,有一个HashMap的包装器。

Map m = Collections.synchronizedMap(new HashMap(...)); 

换句话说,没有理由再使用Hashtable,除非你碰巧正在使用遗留代码。

实际上,如果我们推广到所有编程语言,这不仅仅是一个主观问题。 如果程序员使用不正确的索引,则许多编程语言的数组遍历性能会受到影响,因为编程语言并不总是以相同的方式将其数组存储在内存中。 据我所知,大多数语言要么是行主修,要么是列主修。 如果您要编写需要高性能数字运算的程序,您需要知道它是哪个。

示例:C 使用行主,这是通常的做法。 当您逐行遍历 N x N 个数组时,您可能只会访问内存 N 次,因为每一行都是一起加载的。 因此,对于第一行的第一个元素,您将转到内存并返回整行。 对于第一行的第二个元素,您无需转到内存,因为该行已加载。 但是,如果您决定逐列进行,则可能会出现内存问题。 对于第一列的第一个元素,您将加载整行,然后对于第一列的第二个元素,您将加载下一行...等。 一旦缓存中的空间不足,第一行可能会被丢弃以腾出空间,一旦您开始加载第 2 列中的所有元素,您将不得不重新开始。 这在 Fortran 中不是问题;相反,您希望以这种方式执行此操作,因为会立即加载整个列而不是整行。希望这是有道理的。 请参阅我上面链接到的维基百科文章以获得更直观的解释。

对于大多数程序,开发人员的首要任务应该是干净的代码。 但是,在性能是关键的情况下,了解该语言如何处理内存可能是必不可少的。 问得好。