多维数组结构中哪些元素包含迭代器与细节元素的最佳实践是什么?
我的大部分编程经验(我主要是为了好玩而做)来自谷歌的以下教程,所以如果这似乎是一个特别愚蠢的问题,我提前道歉 - 但我确实想开始改进我的代码。 / p>
每当我需要创建一个多维数组时,我的命名总是将计数器放在第一个元素中。
例如,如果我有一个单维数组,如下所示:
$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程序员之外得到一些意见。我认为,由于数组被用于许多不同的语言,所以从各种语言的程序员那里得到一些输入会很好。
答案 0 :(得分:21)
你的问题是主观的,因为每个人对你所陈述的情况都有不同的看法,你甚至可以提出这个问题。如何最好地命名变量,类等等。不幸的是,我花了比我愿意承认确定最佳变量名有意义和满足要求更多的时间。我的最终目标是编写&#39; self documenting&#39;的代码。通过编写自我记录代码,您会发现添加功能或修复缺陷会更容易。
多年来,我发现这些做法最适合我:
数组:始终为复数
我这样做是为了让循环控制结构更具语义感,并且更易于使用。
// 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);
对象数组&gt;深度多维数组
在你的例子中,你的数组开始成为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));
}
你必须记住,上面的代码片段只是建议,如果你坚持使用深度多维数组,那么你必须找出一个对你有意义的元素排序安排。和你一起工作的人,如果你认为你将在六个月后遇到设置问题,那么最好在你有机会的时候实施更好的策略。
答案 1 :(得分: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'
}
}
}
希望这有助于你的思考。
答案 2 :(得分:3)
你做得对。你必须意识到PHP没有真正的多维数组;你正在看的是一组数组,每个都是一维的。主要数组存储指针(“iterator”,就像你所说的那样)。因此,行优先是唯一合理的方式:
在您的特定示例中,您可以将二维数组视为包含对象集合,每个对象都具有'year', 'month',
和'measure'
的值(加上主键{{1} }})。通过填写主索引,您可以像这样引用二维数组的每一行:'owner'
。每个这样的值都是一个带有键$myArray[$owner]
和'year', 'month',
的三元素数组。换句话说,对于相同的信息,它与原始的一维数据结构相同!您可以将其传递给只处理表格一行的函数,您可以轻松地对'measure'
行等进行排序。
如果您反过来放置索引,则无法恢复个人记录。没有“切片”符号可以为您提供二维数组的整个“列”。
现在有一些更广阔的视角:
由于您在行和列方面提出了问题,请注意首先放置“行”索引会使您的数组与矩阵运算兼容。如果你必须用矩阵进行计算,这是一个巨大的胜利。数据库表示法也会将记录放在行中,因此向后执行会不必要地使事情变得复杂。
C具有真正的二维数组和指针数组。指针数组与PHP完全相同(尽管只允许使用数字索引),原因相同:主索引从指针数组中选择,而次要索引只是指向数组的索引。 C的二维数组的工作方式相同:主要索引位于左侧,内存中的相邻位置相差次(第二)索引的一个值(行尾除外,当然)。这使得它们与指针数组兼容,因为它可以通过使用单个索引来引用二维数组的行。例如,$myArray
为a[0]
:
abcd
系统无缝运行,因为主要(行)索引是第一个。 Fortran具有真正的二维数组,但右侧有主要索引:内存中的相邻位置相差 left (first)索引的一个值。我发现颈部疼痛,因为没有子表达以同样的方式缩小为一维阵列。 (但我有C背景,所以我当然有偏见)。
简而言之:你做得对,这可能不是偶然的,而是因为你通过查看编写良好的代码来学习。
答案 3 :(得分:2)
从我的角度来看,这不是关于数组命名约定的问题,而是关于如何构建数据的问题。含义:哪些信息属于一起 - 为什么?要回答这个问题,你必须同时考虑可读性和性能。
从Java开发人员的角度来看(你也为Java标记了这个问题)我不是多维数组的朋友,因为它们往往会导致大量嵌套for循环中容易出错的索引杂技。要摆脱数组中的第二个维度,可以创建包含一列信息的其他对象。
现在做出决定,哪些数据应嵌入此封闭对象中。在您的情况下,答案很简单:有关一个用户的数据集合是有意义的,一个任意用户的一个属性的不相关值列表通常不会。
即使你没有将数据封装到对象中而更喜欢使用多维数组,你应该牢记这些想法,并将数组的最后一个维度(在本例中为一列)视为与封装等效宾语。您的数据结构在所有抽象级别中应该是有用的,这通常意味着您将所使用的内容组合在一起。
答案 4 :(得分:1)
我相信当您使用要作为集体保存的数据时,最好将其存储在单个数据集(例如associative array或对象)中。以下是可用于存储数据集的结构示例。
在PHP中,作为关联数组:
$owner = array(
'year' => 2012, 'month' => 'July', 'measure' => 3
);
在C#中,为hash tables:
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中,我发现在存储数据集合时,对象与数组相比没有真正的优势,因为type hinting isn't allows for scalar variables,这意味着需要额外的代码来检查/限制在属性中输入的数据,你必须编写额外的代码来声明类,你可能会通过拼写错误的名称而意外地将你的值分配给错误的属性(只是与数组一样,因此它对数据完整性没有帮助)和iteration/manipulation of properties requires extra code as well。我还发现在PHP中转换为/从JSON转换时,使用关联数组更容易。
根据问题中的代码,当您需要通过其中一个字段或其他一些条件(例如字段或数据的组合)访问数据时,通常需要向数组添加另一个维度。该字段将映射到)。
这是哈希表和关联数组对每种语言更有用的地方。例如,如果要根据年份和月份将所有者组织到组中,则可以创建关联数组(或哈希表)来执行此操作。
以下PHP示例使用PDO and 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)
// )
此外,我尝试在创建数据集时保持表示数据集合的对象最小化。如果要将集合存储在对象中,当您开始添加对集合执行操作的函数时,将会有更多代码需要维护。有时这是必要的,例如,如果您需要限制可以通过setters存储的值,但是如果您正在做的只是将数据从用户传递到数据库并再次在屏幕上显示那么通常不需要数据集合中的高级功能,并且在其他地方管理功能可能更有意义。例如,View类可以处理显示数据,Model类可以处理提取数据,Check对象可以验证是否可以保存数据,或者验证是否可以根据集合中的数据执行操作。
答案 5 :(得分:1)
我很少在Java中使用数组,同样适用于C#。它们是面向对象的语言,如果你使用类和对象而不是像数组这样的原始构造,你通常可以获得更好的灵活性。
您的示例看起来像一种称为关联数组的交叉引用或查找。你把这些元素放在哪一方面取决于你将如何使用它。这是针对该问题的设计决策。你需要问问自己,你将从什么开始,到底想要什么?
看起来你想根据你正在查找的内容检索不同的类型? Month是String,year是Integer等。这使得数组或HashMap成为一个糟糕的选择,因为调用代码需要再次猜测它正在检索的数据类型。更好的设计是将这些数据包装在一个类型安全的对象中。这就是使用Java和C#的重点。
在这种情况下,使用对象会为您提供灵活性,以检查月份实际上是否为实际值。例如,您可以创建一个包含月份的枚举,并在getMonth方法中返回此实例。
另外,我没有足够的回复点发表评论,但我想回复Dave F的回答。 Hashtable是Java早期的遗留类,因此应该避免使用。 HashMap是推荐的替代品,都实现了Map接口。
在Java的早期阶段,集合类被同步以使它们成为线程安全的(Vector,Hashtable等)。这使得这些必不可少的课程变得非常缓慢并且妨碍了表现。如果你现在需要一个同步的地图,那么就有一个HashMap的包装器。
Map m = Collections.synchronizedMap(new HashMap(...));
换句话说,除非你碰巧使用遗留代码,否则没有理由再使用Hashtable。
答案 6 :(得分:1)
实际上,如果我们推广到所有编程语言,这不仅仅是一个主观问题。 如果程序员使用不正确的索引,许多编程语言的数组遍历性能都会受到影响,因为编程语言并不总是以相同的方式将其数组存储在内存中。据我所知,大多数语言都是 row-major or column major 。如果要编写需要高性能数字运算的程序,您需要知道它是什么。
示例:C使用row-major,这是执行此操作的常用方法。当您逐行遍历N x N数组时,您可能只访问内存N次,因为每行都加载在一起。因此,对于第一行的第一个元素,您将转到内存并返回整行。对于第一行的第二个元素,您不需要转到内存,因为该行已经加载。但是,如果您决定逐列,可能会出现内存问题。对于第一列的第一个元素,您将加载整行,然后对于第一列的第二个元素,您将加载下一行...等。一旦缓存中的空间不足,第一个行可能会被抛弃以腾出空间,一旦你开始加载第2列中的所有元素,你将不得不重新开始。这在Fortran中不是问题;相反,你想这样做,因为整列都是一次加载而不是整行。希望有道理。请参阅我上面链接的维基百科文章,以获得更直观的解释。
对于大多数程序,开发人员最优先考虑的应该是干净的代码。但是,在性能是关键的情况下,知道该语言如何处理内存是必不可少的。好问题。