web-dev-qa-db-ja.com

どのように、なぜ、またはいつ、Collections Frameworkの代わりに独自のデータ構造を使用しますか?

大手IT企業のほとんどは(インタビュー中に)データ構造を適用して問題を解決するように依頼しますが、そのデータ構造に独自のクラスを定義してもいいですか?

この問題がLinkedListを適用することで解決されることがわかっている場合、LinkedListクラスの独自の実装を作成するか、JavaコレクションAPIによって提供されるものを使用する必要があります。 ?学校でJavaから始めたため、CまたはC++の事前知識がないため、これらのクラスの内部動作がわかりません。

4
Aman Grover

私の経験では、インタビューに関する限り、正確な(言語固有の)実装は実際には問題ではなく、多くの場合、疑似コードで簡単に記述できます。特定の言語でそれを書く必要がある場合、面接担当者は通常、特定の構文であなたを助けてくれるでしょうが、根本的な概念を理解していることを彼らが理解していることを確認してください。

一般的なルールとして、既存のライブラリが同じことを行う場合は、カスタムデータ構造を実装しないでください。これは、作業中および面接時に適用されます(もちろん、他の方法で行うように特に求められている場合を除く)。独自のデータ構造を再作成することは、ほとんどの場合ひどい考えです。

データの特殊な処理を必要とする特定のケースで魅力的な場合があります。ただし、これを行う方法が文字通り他にない場合にのみ、これを行う必要があります。可能であれば、その個別の機能を個別のエンティティとして実装する(または既存のデータ構造をラップする)ことをお勧めします。データ構造を作成した前の開発者を利用し、多くのEdgeケースとバグに遭遇した場合、データ構造を再作成すると、最終的に再作成してしまいます。

要するに、誰かがあなたにそうするように頼んだらデータ構造を実装できるようになるまでデータ構造を理解するべきですが、実際の環境では実際にそれを再作成すべきではありません。

11
yitzih

次の場合に独自のデータ構造をロールバックします。

  1. あなたはコーディングを学んでいて、「既知の」ソリューションを(再)作成する経験が必要です。
  2. あなたは非常に特殊なユースケースを持っており、事前に構築されたソリューションが利用できないと判断するのに十分な調査を行いました。 (誰もがデータ構造を必要とするため、これは比較的まれです。そのため、他の誰かがまだ解決していないユースケースを見つけるのは困難です)
5
Ivan

既存のデータ構造(JDKまたは他のライブラリーによって提供される)が「特定の」問題の解決に不十分な場合は、独自のデータ構造をロールします。

Javaは一般的な言語であり、そのため、たとえば99%またはユースケースに適したデータ構造の実装が見つかります。

ただし、他の1%に該当する「特定の」アプリケーションまたはユースケースがある場合もあります。たとえば、ヒープサイズが64 GB、128 GB、256 GBなどの高スループットの低レイテンシアプリケーションです。これらは、多くのデータをメモリに格納することを伴います。場合によっては、異なるエンティティの関係を表すIDであるintおよびlongも含まれます。たとえば、Map _intまたはlongが必要な場合は、運が悪いです。 JavaでIntegerを使用するには、これらをLongまたはMapにボックス化する必要があります。ケビン・クラインは彼の答えでこれについて述べました。

メモリに数百万のアイテムのMapが保存されている場合、すべてのビットがカウントされます。あちこちに数ビットを保存し、それに数百万のエンティティを掛けると、かなりのメモリを節約できます。そうすることで、64 GBのヒープ領域を使用する代わりに、たとえば48 GBを使用します。リソースが少ない(したがって安価な)サーバーが必要であり、十分なメモリがあればそれを維持しているため、スケーリングが容易になります。また、 " stop the world "数秒間、あなたの素敵な小さなアプリケーションです。たとえば、 [〜#〜] trove [〜#〜] は、プリミティブを高速で効率的に格納できるようにデータ構造が実装された例です。

内部について学び、インタビューの質問に答えるには、さまざまなデータ構造を実装できるはずですが、他の答えが指摘するように、問題が「特定」でない限り、ほとんどの場合にすでに存在し、機能する解決策が見つかります。

2
Bogdan

私のドメインはデータ構造の要件の点でかなり集中的ですが、私はこれを最も頻繁に、最も基本的で一般的なコンテナであっても認めています。

私の場合の問題は、これらの一般化された構造は、同じ要件を満たすためにそれらを一致させようとするのがばかである方法でそれらが何をするかで非常に効率的ですが、それらの一般性は、通常、あなたは、目の前の問題の特定のニュアンスと要件に合わせてそのソリューションを調整します。

一般的なソリューションと合わせたソリューション

たとえば、C++のstd::vector(JavaのArrayListに類似)を、可変長のランダムアクセスシーケンスとしての汎用的な目的で使用したり、一致させたりするのは完全にばかです。 0から億兆の要素まで保存できるように設計されています。しかし、一般的なケースのシナリオで32要素以下のシーケンスを頻繁に構築するようなユースケースでは、(他の誰もそうであるように)汗をかかすことなく打ち勝つことができます。たとえば、これらの一般的なケースでヒープの使用を回避する小さなバッファー最適化を使用した数十行のコード。

汎用メモリアロケータでも同様です。私の意見では、さまざまな種類の懸念を処理しながら可変長の割り当て要求を満たすように設計された独自の実装でmallocをCで打ち負かそうとするのは非常に愚かであり、私は世界の私が試した場合、この1つの問題だけに多くの時間と注意を注いだ最高の人。しかし、必要なのは、同じタイプ(同じチャンクサイズ)のN個の要素を単一のスレッドに割り当てることだけである場合、問題は指数関数的に簡単に解決でき、ソリューションを設計するだけで簡単にmallocを打ち負かすことができますこれを非常に特殊なタイプのニーズを解決します。

そのため、微妙なニュアンスはあるものの、まだ少し一般的(少し再利用するのに十分なほど)で、特定の問題に合わせた一般的な解決策が見つかることがよくあります。そして、空間インデックスの代わりにstd::hash_mapを使用して、競争力のある生産品質のパストレーサーやリアルタイム物理エンジンを実装できるようなものではありません。たとえば、サードパーティの実装を使用することはもちろんですが、私のドメインは必然的にそれはあなたのソフトウェアに競争上の優位性(または私たちが失敗した場合はその欠如)とユニークなパフォーマンス特性*を与えるものだからです。

これらの領域では、常に「より良い」または「より悪い」とは限りません。ソリューションによって提供されるパフォーマンスには長所と短所があり、ソリューションを競争力のあるものにして際立っているのは、常にすべてにおいて完全に優れているわけではなく、一部において優れているということです。たとえば、競合他社の中には、標準パストレースの非常に効率的なGPU実装(BDPTまたは他の手法とは対照的)を備えているため、ビーフなGPUで途方もない数の光線/秒を処理できます。ただし、それらは時々トリッキーな照明シナリオで苦しみ、それらの場合でもノイズのない画像に収束するのに数時間かかる可能性があります。私たちのものは、ハイブリッドシステムとデータ構造のほうが多く、ブルートフォースパワーに欠けていますが、トリッキーなライティングセットアップにはるかに速く収束できます。これにより、建築の視覚化で人気が高まる傾向があります。 。

実用的な例

実用的な例と同じように、SOの多くは、空間インデックス(クワッドツリー、オクツリー、BVH、グリッド、空間ハッシュなど)を効率的に実装するのに苦労しており、常に目にする最大の問題の1つです。各ノードで標準ライブラリから汎用コンテナを保存する誘惑です。これは、メモリ内で爆発的になりすぎて、そのような構造の単一のノード/セルのような領域でこのような一般化されたソリューションに到達できなくなるだけです。

彼らの構造は、ほんの数千のエージェントで30 FPSを引き出すこともしばしば困難でした。 100 FPSを超えるシングルスレッドで50万を処理することになりました(衝突クエリを行わずにフレームの描画に費やす時間の約3分の1で、1秒あたりの構造への5,000万を超える挿入+削除+検索クエリ)。私が意図的にあまり最適化しないように2時間でホイップアップしたシングルスレッドソリューション(コードは他の人に教え、できるだけシンプルで簡単になるように意図されていました):

enter image description here

そしてこれは自慢を意図したものではなく、私はこれらのタイプのことを(非常に豪華で高価なプロファイラーとともに)いくつかの経験を持っていますが、達成するための最初かつ最大のキー(しかし、同様の結果を求める誰もが適用できる重要なキー)そのような解決策は、すべての単一ノードに一般化されたコンテナーを格納するという考えから離れることです。このような一般的な考え方から離れて問題を解決する必要がある場合もあります。このような一部の厳しいケースでは、標準のツールボックスに手を伸ばすのではなく、メモリにデータを配置して効率的にデータにアクセスする方法について考え始めます。このデータを、標準のコンテナが提供するものよりも細かいニュアンスで表すことができない場合は不可能です。また、この考え方を実践しても、ツリーレベルでは一般化されたコンテナーを使用できますが、空間インデックスの単一ノードのレベルでは使用できません。

生産性の維持

私が難しい方法で学んだことの1つは、これらのより調整されたニュアンスのあるソリューションを一般化しすぎないことです(そうしないと、記述してテストしなければならない手作業のコードの量が、ほんの少しの代わりに大幅に増える可能性があります)。私はそれらを再利用可能性と親しみやすさを最大化するために標準ライブラリと同一のインターフェースを提供する程度までそれらを一般化しようとしました、そしてそれでも私はそれが私の邪魔にならないようにする必要がないときにもそれをしようとしますが、それが行われるときにもはやそれをしません。それは、より一般化されたものよりもパフォーマンスの利点がではないから一般化されていないソリューションを一般化しようとするのに非常に多くの時間を前もって必要とすることになっただけであり、このような標準への準拠は、ほとんど使用されていません。つまり、2つの矛盾する要件のバランスをとろうとすることと、時間を大幅に削減することのようなものでした。したがって、最近では、これらの「調整された」ソリューションをあまり一般化しようとしないでください。それがほとんど手間がかからない場合は、標準的な使い慣れたインターフェースに準拠させるようにします。それ以外の場合、私はこれらのソリューションの適用範囲がやや狭いことを受け入れません。

それはさておき、可能な限りいつでも言語に組み込まれた一般化されたコンテナーに頼り(チームメンバーはそれらの標準コンテナーに既に精通しているので、チームにとってそれが増えるという大きな利点があります)、多くの場合、最初のドラフト実装を使用して実装します私が遭遇する新しくてなじみのない問題のためのそれら(非常に馴染みのあるもののためのソリューションのライブラリを少し構築しましたが)。プロファイリングセッションで後からホットスポットを見つけ始め、それを出荷の必要性と釣り合わせる場合にのみ、私は他の何かに手を差し伸べることを検討しますか?.

2
Dragon Energy

コレクションフレームワーク構造は、オブジェクトへの参照のコレクションのみを格納できます。これは、各データをオブジェクトに「ボックス化」する必要があるため、数値データの大きな構造を格納するための空間と時間の点で非常に非効率になります。

1
kevin cline