else else構造がより適切な時間で仕事をすることができるときに、(関数で)HashMapを使用して(キーに対して)返す値を決定する必要があるのはなぜですか?


9

私が最近大企業で働いていたときに、プログラマーがこのコーディングスタイルに従っていることに気づきました。

入力がAの場合は12、入力がBの場合は21、入力がCの場合は45を返す関数があるとします。

だから私は関数のシグネチャを次のように書くことができます:

int foo(String s){
    if(s.equals("A"))      return 12;
    else if(s.equals("B")) return 21;
    else if(s.equals("C")) return 45;
    else throw new RuntimeException("Invalid input to function foo");
}

しかし、コードレビューで、関数を次のように変更するように求められました。

int foo(String s){
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("A", 12);
    map.put("B", 21);
    map.put("C", 45);
    return map.get(s);
}

2番目のコードが最初のコードよりも優れている理由を私は納得できません。2番目のコードの実行には、間違いなく時間がかかります。

2番目のコードを使用する唯一の理由は、コードが読みやすくなることです。しかし、関数が何度も呼び出される場合、2番目の関数はそれを呼び出すユーティリティの実行時間を遅くしませんか?

これについてどう思う?


4
3つの値の場合、マップはやりすぎのswitchように見えます(より適切なように見えますif-else)。しかし、ある時点で問題が発生します。Mapを使用する主な利点は、ファイルまたはテーブルなどからそれをロードできることです。入力をマップにハードコーディングしている場合、スイッチに多くの値が表示されません。
JimmyJames 2017年

回答:


16

重要なのは、ハッシュマップの作成を関数の外に移動し、それを1回(または他の場合よりも少ない回数)実行することです。

private static final Map<String, Integer> map;
static{
    Map<String, Integer> temp = new HashMap<String, Integer>();
    temp.put("A", 12);
    temp.put("B", 21);
    temp.put("C", 45);
    map = Collections.unmodifiableMap(temp);//make immutable
}

int foo(String s){
    if(!map.containsKey(s))
        throw new RuntimeException("Invalid input to function foo");

    return map.get(s);
}

ただし、java7はスイッチに(最終的な)文字列を含めることができるようになりました。

int foo(String s){
    switch(s){
    case "A":
        return 12;
    case "B": 
        return 21;
    case "C": 
        return 45;
    default: throw new RuntimeException("Invalid input to function foo");
}

1
これがOPの質問にどのように答えるかはわかりません。そこで-1を指定します。ただし、切り替えを提案する場合は+1。
user949300 2017年

実際に意味のあるコーディングスタイルを適切に実装し、パフォーマンスを改善する方法を示しています。それでも3つの選択肢には意味がありませんが、元のコードはおそらくはるかに長くなりました。
フロリアンF

12

2番目の例では、Map冗長な初期化オーバーヘッドを回避するために、をプライベート静的メンバーにする必要があります。

値が多い場合、マップのパフォーマンスが向上します。ハッシュテーブルを使用すると、一定の時間で答えを調べることができます。multiple-if構文は、正しい答えが見つかるまで、入力を各可能性と比較する必要があります。

言い換えると、ifsはO(n)ですが、マップルックアップはO(1)です。nは可能な入力の数です。

マップの作成はO(n)ですが、静的な定数状態の場合は1回だけ行われます。頻繁に実行されるルックアップのif場合、プログラムの起動時(または言語によってはクラスがロードされたとき)の時間が少し長くなりますが、長期的にはマップがステートメントよりもパフォーマンスが高くなります。

そうは言っても、地図は必ずしもこの仕事に適したツールとは限りません。多くの値がある場合や、値がテキストファイル、ユーザー入力、またはデータベース(マップがキャッシュとして機能する場合)を介して構成可能である必要がある場合に適しています。


はい、値が多い場合、マップのパフォーマンスは向上します。ただし、値の量は固定されており、3つです。
RemcoGerlich 2017年

マップの作成はO(N)であり、マップ内の検索はO(1)のみです。
Pieter B

良い点、私は私の答えを明確にしました。

また、マップは自動アンボックス化を必要とし、パフォーマンスにわずかに影響します。
user949300

はい、Javaの@ user949300です。質問のコードはJavaのようです。ただし、どの言語のタグも付けられておらず、このアプローチは複数の言語(C#やC ++など、どちらもボクシングを必要としない)で機能します。

3

ソフトウェアには2つの速度があります。コードの書き込み、読み取り、デバッグにかかる​​時間です。コードの実行にかかる時間。

ハッシュマップ関数が実際にif / then / elseよりも遅い(静的ハッシュマップを作成するようにリファクタリングした後)ことを私(およびコードレビューア)に納得させることができ、実際の作成に十分な回数呼び出されたことを私/レビュアーに納得させることができます違いがある場合は、次に進んでハッシュマップをif / elseに置き換えます。

それ以外の場合、ハッシュマップコードは非常に読みやすくなります。(おそらく)バグのないもの。見ればすぐにわかります。実際に勉強しなければ、if / elseについて同じことを言うことはできません。何百ものオプションがある場合、違いはさらに誇張されます。


3
ええと、代わりにスイッチと比較すると、その反対は崩壊します...
デデュプリケータ2015

また、ifステートメントを1行で記述すると、バラバラになります。
gnasher729 2015

2
ハッシュマップの作成を別の場所に置くと、実際に何が起こっているのかを理解するのが難しくなります。関数の実際の効果を知るには、これらのキーと値を確認する必要があります。
RemcoGerlich 2017年

2

私はHashMapスタイルの答えを大いに好みます。

これには測定基準があります

Cyclomatic Complexityと呼ばれるコード品質メトリックがあります。このメトリックは基本的に、コードを介したさまざまなパスの数をカウントします(循環的複雑度の計算方法)。

考えられるすべての実行パスについて、メソッドの理解と正確性の完全なテストの両方がますます困難になります。

結局のところ、ifs、else、whilesなどの「キーワードを制御すること」は、間違っている可能性のあるブールテストを利用しています。「制御キーワード」を繰り返し使用すると、脆弱なコードが生成されます。

その他のメリット

また、「マップベースのアプローチ」により、開発者は入出力ペアを、実行時に抽出、再利用、操作、テスト、および検証できるデータセットと考えることができます。たとえば、以下は「foo」を書き直して、「A-> 12、B-> 21、C-> 45」に永続的にロックされないようにします。

int foo(String s){
    HashMap<String, Integer> map = getCurrentMapping();
    return map.get(s);
}

rachet_freakは彼の答えでこのタイプのリファクタリングについて言及しています、彼は速度と再利用について主張し、実行時の柔軟性について主張しています(ただし、不変コレクションを使用すると状況に応じて非常に大きなメリットが得られます)


1
実行時の柔軟性を追加することは、優れた前向きなアイデアであるか、または何が起こっているのかを理解することをはるかに困難にする不必要なやりすぎのいずれかです。:-)。
user949300

私にとって@JimmyJamesリンク作品:それは次のとおりです。leepoint.net/principles_and_practices/complexity/...
イワン

1
@ user949300重要なのは、「foo」メソッドをサポートするキー/値データは、何らかの形の明快さに値する個別の概念であることです。マップを分離するために作成するコードの量は、マップに含まれるアイテムの数と、それらのアイテムを変更する必要がある頻度に大きく依存します。
Ivan

1
@ user949300 if-elseチェーンを削除することをお勧めする理由の1つは、if-elseチェーンがグループ内に存在するのを見たことです。似たようなゴキブリ、if-elseチェーンに基づく1つのメソッドがある場合、コードベースの別の場所に、同様の/同一のif-elseチェーンを持つ別のメソッドが存在する可能性があります。同様のルックアップ/スイッチのようなロジック構造を使用する他のメソッドがある可能性があると想定した場合、マップを抽出すると効果があります。
Ivan

私も重複して、ほぼ同じif / elseブロックがいたるところに散らばっているのを見ました。よくわかりました
ジョンチェスターフィールド2017年

1

データはコードより優れています。特に、コードにさらに別のブランチを追加するのは魅力的すぎますが、テーブルに行を追加するのは間違いを犯すのが難しいです。この質問はこれの小さな例です。あなたはルックアップテーブルを書いています。実装を作成するか、条件付きロジックとドキュメントを完備するか、テーブルを作成してからルックアップします。

データのテーブルは、コードよりも常に一部のデータをより適切に表現したものであり、モジュロ最適化パスです。テーブルの表現がいかに難しいかは、言語に依存する可能性があります。Javaについては知りませんが、OPの例よりも簡単にルックアップテーブルを実装できることを願っています。

これはPythonのルックアップテーブルです。これが競合を招くと見なされる場合は、質問にJavaのタグが付いていないこと、リファクタリングは言語に依存しないこと、ほとんどの人がJavaを知らないことを考慮してください。

def foo(s):
    return {
               "A" : 12,
               "B" : 21,
               "C" : 45,
           }[s]

ランタイムを削減するためにコードを再構築するというアイデアにはメリットがありますが、私は自分で行うよりも、一般的なセットアップを引き上げるコンパイラーを使用するほうがはるかに便利です。


-1質問に対する回答ではありません。
Pieter B

どのような意味で?データとコードを分離する必要があることに基づいて、if elseチェーンをマップに置き換えるように作成者に依頼したでしょう。これは、2番目のコードが最初のコードよりも優れていることの明らかな理由ですが、すべてのmap.put呼び出しは残念です。
Jon Chesterfield 2017年

2
@JonChesterfieldこの回答は基本的に「より良い言語を使用する」ため、ほとんど役に立ちません。
ワルペン2017年

@walpenフェアポイント。IvanはJavaを介してほぼ同じ観察をしたので、私は明らかにPythonにドロップする必要はありませんでした。私はそれを少しきれいにすることができるかどうかを確認します
ジョン・チェスターフィールド

いくつかの古いJavaコードでは、非常によく似たものを作成するためにいくつかのマップが必要でした。そのため、2次元配列のN次元配列をマップに変換する小さなユーティリティを作成しました。少しハックですがうまくいきました。この答えは、JSONy表記をシンプルかつ簡単にサポートするPython / JSの力を指摘しています。
user949300
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.