13

どの要素が反復子と詳細要素を保持するかという観点から、多次元配列構造のベストプラクティスは何ですか?

私のプログラミング経験の大部分 (そして私は主に楽しみのためにやっています) は、Google のチュートリアルに従うことから来ています。

多次元配列を作成する必要があるときはいつでも、私の名前付けでは常に最初の要素にカウンターが配置されています。

たとえば、次のような 1 次元配列があるとします。

$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];
}

編集: PHP プログラマ以外の意見を聞きたかったので、この投稿に c# および Java のタグを付けました。配列は非常に多くの異なる言語で使用されているため、さまざまな言語のプログラマーから意見を得ることができればよかったと思います。

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

上記のコード スニペットは単なる提案であることを覚えておく必要があります。深い多次元配列に固執する場合は、あなたと一緒に働いている人にとって意味のある要素の順序付けを考え出す必要があります。 6 か月後にセットアップで問題が発生する場合は、チャンスがあるうちにより良い戦略を実装することをお勧めします。

于 2012-07-20T00:44:31.490 に答える
3

あなたはそれを正しくやっています。PHP には実際の多次元配列がないことを認識しておく必要があります。あなたが見ているのは、それぞれが1次元の配列の配列です。主要な配列には、ポインターが格納されています (「イテレーター」と言います)。このため、行優先が妥当な唯一の方法です。

特定の例では、2 次元配列にオブジェクトのコレクションが含まれていると考えることができます。各オブジェクトには'year', 'month','measure'(および主キー'owner') の値があります。メジャー インデックスを入力すると、次のように 2 次元配列の各行を参照できます$myArray[$owner]。そのような各値は、キー'year', 'month',とを持つ 3 つの要素の配列'measure'です。つまり、同じ情報に対する元の 1 次元データ構造と同じです。テーブルの 1 行だけを処理する関数に渡すことができます。行を簡単に並べ替えることができます$myArray

インデックスを逆にすると、個々のレコードを回復する方法はありません。2 次元配列の「列」全体を取得できる「スライス」表記はありません。

ここで、少し広い視野で次のように説明します。

行と列の観点から質問したので、「行」インデックスを最初に配置すると、配列が行列演算と互換性を持つことに注意してください。行列を使用して計算を行う必要がある場合、これは大きなメリットです。データベース表記法もレコードを行に配置するため、逆方向に行うと不必要に複雑になります。

C には、実数の 2 次元配列ポインターの配列があります。ポインターの配列は、PHP とまったく同じように機能します (ただし、数値インデックスのみが許可されます)。同じ理由で、メジャー インデックスはポインターの配列から選択され、マイナー インデックスは単に、ポイント先の配列のインデックスです。C の 2 次元配列も同じように機能します。メジャー インデックスは左側にあり、メモリ内の隣接する位置は、マイナー(2 番目の) インデックスの 1 つの値だけ異なります (もちろん、行の末尾を除きます)。これにより、単一のインデックスを使用して 2 次元配列の行を参照できるため、ポインター配列と互換性があります。たとえば、次のとおり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 には実数の 2 次元配列がありますが、右側にメジャー インデックスがあります。メモリ内の隣接する位置は、左側(最初) のインデックスの 1 つの値だけ異なります。同じように 1 次元配列に還元される部分式がないため、これは首の痛みでした。(しかし、私は C のバックグラウンドを持っているので、確かに偏見があります)。

要するに、あなたはそれを正しく行っています。おそらく偶然ではなく、適切に記述されたコードを見て学習したためです。

于 2012-07-20T18:52:50.677 に答える
3

自分自身 (および場合によっては他のユーザー) が、自分のコードが何を行っているか、またそれを書いたときに何を考えているかを簡単に理解できるようにしたいと考えています。助けを求めたり、デバッグ コードを出力したり、最後にコードに触れてから 12 か月後に戻ってバグを修正したりすることを想像してみてください。あなた/他の人が理解するのに最適で最速なのはどれですか?

コードで「毎年、データを表示する」ことが必要な場合は、最初のほうがより論理的です。「すべての測定値をまとめて処理する必要がある」と考えている場合は、2 番目のオプションに進みます。年ごとに再注文する必要がある場合は、最初に行ってください。

上記の例に基づいて、私が上記を処理する方法はおそらく次のとおりです。

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

特定の「メジャー」要素は必要ありません。または、月に 2 つの要素がある場合:

$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 に答える
2

私の見解では、これは配列の命名規則に関する問題ではなく、データを構造化する方法に関する問題です。意味: どの情報が一緒に属しているか、またその理由は? この質問に答えるには、可読性とパフォーマンスの両方を考慮する必要があります。

Java 開発者の観点から (そして Java についても質問にタグを付けました)、私は多次元配列の友人ではありません。多次元配列は、大量のネストされた for ループでエラーが発生しやすいインデックス アクロバットになる傾向があるためです。配列の 2 番目の次元を取り除くには、1 つの列の情報を囲む追加のオブジェクトを作成します。

ここで決定するのは、この外側のオブジェクトにどのデータを埋め込むかです。あなたの場合、答えは簡単です: 1 人のユーザーに関するデータのコレクションは理にかなっていますが、通常、多数の任意のユーザーの 1 つのプロパティの相関しない値のリストは意味がありません。

データをオブジェクトにカプセル化せず、代わりに多次元配列を使用することを好む場合でも、これらの考えを念頭に置いて、配列の最後の次元 (この場合は 1 列) をカプセル化オブジェクトと同等と見なす必要があります。データ構造はすべての抽象化レベルで役立つはずです。これは通常、一緒に使用されるものをまとめることを意味します。

于 2012-07-24T00:55:20.970 に答える
1

実際、すべてのプログラミング言語に一般化すると、これは単なる主観的な問題ではありません。 多くのプログラミング言語では、配列を常に同じ方法でメモリに格納するとは限らないため、プログラマが不適切なインデックスを使用すると、配列トラバーサルのパフォーマンスが低下します。私の知る限り、ほとんどの言語は行優先または列優先です。高性能の数値計算を必要とするプログラムを作成する場合は、それがどれであるかを知る必要があります。

例: C は行優先を使用します。これは通常の方法です。N x N 配列を行ごとに反復する場合、各行が一緒にロードされるため、メモリにアクセスするのはおそらく N 回だけです。したがって、最初の行の最初の要素については、メモリに移動して行全体を取得します。最初の行の 2 番目の要素については、行が既に読み込まれているため、メモリにアクセスする必要はありません。ただし、列ごとに移動することにした場合は、メモリの問題が発生する可能性があります。最初の列の最初の要素の場合は行全体をロードし、次に最初の列の 2 番目の要素の場合は次の行をロードします... など。キャッシュ内のスペースがなくなると、最初の行はおそらくスペースを空けるために捨てられ、列 2 のすべての要素をロードし始めると、最初からやり直す必要があります。これはだろう」Fortran では問題になりません。むしろ、行全体ではなく列全体が一度にロードされるため、この方法で行うことをお勧めします。それが理にかなっていることを願っています。より視覚的な説明については、上記でリンクしたウィキペディアの記事を参照してください。

ほとんどのプログラムでは、開発者の最優先事項はコードをきれいにすることです。しかし、その言語がメモリを処理する方法に関する知識は、パフォーマンスが重要な場合に不可欠です。良い質問。

于 2012-07-25T12:43:08.880 に答える
1

Java で配列を使用することはめったにありません。同じことが C# にも当てはまります。それらはオブジェクト指向言語であり、配列などのプリミティブな構造の代わりにクラスとオブジェクトを使用すると、一般的に長期的にははるかに優れた柔軟性が得られます。

あなたの例は、連想配列として知られる一種の相互参照またはルックアップのように見えます。要素をどのように配置するかは、それをどのように使用するかによって異なります。これは、問題に固有の設計上の決定です。何から始めて、何に行き着きたいかを自問する必要があります。

探しているものに応じて、さまざまなタイプを取得したいようですね。Month は String、year は Integer などです。これにより、配列または HashMap は不適切な選択になります。これは、呼び出し元のコードが取得するデータの種類を 2 番目に推測する必要があるためです。より良い設計は、このデータをタイプ セーフなオブジェクトにラップすることです。これが、Java と C# を使用する要点のようなものです。

この場合、オブジェクトを使用すると、月が実際の値であることを柔軟に確認できます。たとえば、月を含む列挙型を作成し、getMonth メソッドでこのインスタンスを返すことができます。

また、コメントを残すのに十分な担当者ポイントがありませんが、Dave F の回答に返信したかったのです。Hashtable は Java の初期のレガシー クラスであるため、避ける必要があります。HashMap が推奨される代替品であり、どちらも Map インターフェースを実装しています。

Java の初期の頃、コレクション クラスはスレッドセーフにするために同期されていました (Vector、Hashtable など)。これにより、これらの重要なクラスが不必要に遅くなり、パフォーマンスが低下しました。最近、同期されたマップが必要な場合は、HashMap へのラッパーがあります。

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

つまり、たまたまレガシー コードを使用しない限り、Hashtable を使用する理由はもうありません。

于 2012-07-25T09:52:05.173 に答える
1

I believe that when you're using data that is meant to be kept as a collective, it's better to store it in a single data set (such as an associative array or object). The following are examples of structures that can be used to store data sets.

In PHP, as associative arrays:

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

In C#, as hash tables:

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

In Java, as hash tables:

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

In C#/Java, as objects:

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;

In C# and Java, the advantage to using objects over hash tables is that variable types (ie. int or string) can be declared for each field/variable, which will help to prevent errors and maintain data integrity since errors will be thrown (or warnings will be generated) when you attempt to assign the wrong type of data to a field.

In PHP, I find that objects have no real advantages over arrays when storing collections of data because type hinting isn't allows for scalar variables, which means that additional code is required to check/restrict what data is entered in a property, you have to write extra code to declare the class, you can accidentally assign your values to the wrong property by misspelling the name (just the sames as can be done with arrays so it doesn't help with data integrity), and iteration/manipulation of properties requires extra code as well. I also find it easier to work with with associative arrays when converting to/from JSON in PHP.

Based on the code in the question, most often you'll need to add another dimension to the array when you need to access the data via one of the fields or some other criteria (such as a combination of fields or data that the field would map onto).

This is where hash tables and associative arrays are more useful for each of the languages. For example, if you want to organize the owners into groups based on year and month, you can create associative arrays (or hash tables) to do this.

The following PHP example uses PDO and fetchAll to get the information from a database:

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

An example of how this data might look as code is:

$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)
        )
    )
);

You can then access the data using the keys.

$data[2011]['July'];

// array(
//     array('year' => 2011, 'month' => 'July', 'measure' => 1),
//     array('year' => 2011, 'month' => 'July', 'measure' => 3)
// )

Furthermore, I attempt to keep my objects that represent collections of data minimal when I create them. If you're storing your collections in objects, as you begin to add functions that perform operations on the collections, there will be more code to maintain. There are times when this is necessary, for example if you need to restrict what values can be stored via setters, but if all you're doing is passing data from a user into a database and displaying it on the screen again, then there usually isn't a need for advanced functionality in the data collection and it may make more sense to have the functionality managed elsewhere. For example, View classes can handle displaying data, Model classes can handle extracting data, and Check objects can verify that data can be saved or verify whether or not operations can be performed based on the data in the collection.

于 2012-07-25T04:39:08.457 に答える