13

就哪些元素保存迭代器与细节元素而言,多维数组结构的最佳实践是什么?

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

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

例如,如果我有一个如下的一维数组:

$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 程序员之外的意见。我认为,由于数组在许多不同的语言中使用,从各种语言的程序员那里获得一些输入会很好。

4

7 回答 7

21

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

多年来,我发现这些做法对我最有效:

数组:总是复数

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

// 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));
}

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

于 2012-07-20T00:44:31.490 回答
3

您想让自己(以及可能的其他用户)更容易理解您的代码在做什么,以及您在编写代码时的想法。想象一下寻求帮助,想象输出调试代码,或者想象在你最后一次接触代码 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'
        }
    }
}

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

于 2012-07-18T03:15:47.983 回答
3

你做对了。您必须意识到 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 背景,所以我当然有偏见)。

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

于 2012-07-20T18:52:50.677 回答
2

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

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

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

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

于 2012-07-24T00:55:20.970 回答
1

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

在 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 或 string),这将有助于防止错误并保持数据完整性,因为会抛出错误(或当您尝试将错误类型的数据分配给字段时,将生成警告。

在 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 类可以处理显示数据,Model 类可以处理提取数据,Check 对象可以验证是否可以保存数据或验证是否可以根据集合中的数据执行操作。

于 2012-07-25T04:39:08.457 回答
1

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

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

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

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

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

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

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

换句话说,除非您碰巧使用遗留代码,否则没有理由再使用 Hashtable。

于 2012-07-25T09:52:05.173 回答
1

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

示例:C 使用行优先,这是通常的做法。当您逐行遍历 N x N 数组时,您可能只会访问内存 N 次,因为每一行都是一起加载的。所以对于第一行的第一个元素,你会去记忆并取回整行。对于第一行的第二个元素,您不需要去记忆,因为该行已经加载。但是,如果您决定逐列查看,则可能存在内存问题。对于第一列的第一个元素,您将加载整行,然后对于第一列的第二个元素,您将加载下一行......等等。一旦缓存中的空间不足,第一个row 可能会被放弃以腾出空间,一旦您开始加载第 2 列中的所有元素,您将不得不重新开始。这不会 在 Fortran 中不是问题;相反,您希望这样做,因为一次加载整个列而不是整行。希望这是有道理的。有关更直观的解释,请参阅我在上面链接的 Wikipedia 文章。

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

于 2012-07-25T12:43:08.880 回答