String
sの概要と、Map<String, List<String>>
:の値でのそれらの出現を提供する次のメソッドを実装しました。
public static Map<String, Long> getValueItemOccurrences(Map<String, List<String>> map) {
Map<String, Long> occurrencesOfValueItems = new HashMap<>();
map.forEach((key, value) -> {
value.forEach(item -> {
if (occurrencesOfValueItems.containsKey(item)) {
occurrencesOfValueItems.put(item, occurrencesOfValueItems.get(item) + 1);
} else {
occurrencesOfValueItems.put(item, 1L);
}
});
});
return occurrencesOfValueItems;
}
単一のJUnitテストでテストしましたが、テストは成功しました。ここにあります(現在はインポートも含まれています):
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
class TryoutTest {
static Map<String, List<String>> items = new HashMap<>();
static List<String> largeList = new ArrayList<String>();
static List<String> mediumList = new ArrayList<String>();
static List<String> smallList = new ArrayList<String>();
static List<String> differentLargeList = new ArrayList<String>();
static List<String> differentSmallList = new ArrayList<String>();
static List<String> anotherList = new ArrayList<String>();
static List<String> someList = new ArrayList<String>();
static List<String> justAList = new ArrayList<String>();
@BeforeAll
static void setup() {
largeList.add("Alfred");
largeList.add("Bakari");
largeList.add("Christian");
largeList.add("Dong");
largeList.add("Etienne");
largeList.add("Francesco");
largeList.add("Guido");
largeList.add("Henrik");
largeList.add("Ivan");
largeList.add("Jos");
largeList.add("Kumar");
largeList.add("Leonard");
largeList.add("Marcin");
largeList.add("Nico");
largeList.add("Olof");
items.put("fifteen-01", largeList);
mediumList.add("Petar");
mediumList.add("Quentin");
mediumList.add("Renato");
mediumList.add("Sadio");
mediumList.add("Tomislav");
mediumList.add("Ulrich");
mediumList.add("Volkan");
mediumList.add("Wladimir");
items.put("eight-01", mediumList);
smallList.add("Xavier");
smallList.add("Yves");
smallList.add("Zinedine");
smallList.add("Alfred");
items.put("four-01", smallList);
differentLargeList.add("Bakari");
differentLargeList.add("Christian");
differentLargeList.add("Dong");
differentLargeList.add("Etienne");
differentLargeList.add("Francesco");
differentLargeList.add("Xavier");
differentLargeList.add("Yves");
differentLargeList.add("Wladimir");
differentLargeList.add("Jens");
differentLargeList.add("Hong");
differentLargeList.add("Le");
differentLargeList.add("Leigh");
differentLargeList.add("Manfred");
differentLargeList.add("Anders");
differentLargeList.add("Rafal");
items.put("fifteen-02", differentLargeList);
differentSmallList.add("Dario");
differentSmallList.add("Mohammad");
differentSmallList.add("Abdul");
differentSmallList.add("Alfred");
items.put("four-02", differentSmallList);
anotherList.add("Kenneth");
anotherList.add("Hong");
anotherList.add("Bakari");
anotherList.add("Ulrich");
anotherList.add("Henrik");
anotherList.add("Bernd");
anotherList.add("Samuel");
anotherList.add("Ibrahim");
items.put("eight-02", anotherList);
someList.add("Kumar");
someList.add("Konrad");
someList.add("Bakari");
someList.add("Francesco");
someList.add("Leigh");
someList.add("Yves");
items.put("six-01", someList);
justAList.add("Bakari");
items.put("one-01", justAList);
}
@Test
void valueOccurrencesTest() {
Map<String, Integer> expected = new HashMap<>();
expected.put("Abdul", 1);
expected.put("Alfred", 3);
expected.put("Anders", 1);
expected.put("Bakari", 5);
expected.put("Bernd", 1);
expected.put("Christian", 2);
expected.put("Dario", 1);
expected.put("Dong", 2);
expected.put("Etienne", 2);
expected.put("Francesco", 3);
expected.put("Guido", 1);
expected.put("Henrik", 2);
expected.put("Hong", 2);
expected.put("Ibrahim", 1);
expected.put("Ivan", 1);
expected.put("Jens", 1);
expected.put("Jos", 1);
expected.put("Kenneth", 1);
expected.put("Konrad", 1);
expected.put("Kumar", 2);
expected.put("Le", 1);
expected.put("Leigh", 2);
expected.put("Leonard", 1);
expected.put("Manfred", 1);
expected.put("Marcin", 1);
expected.put("Mohammad", 1);
expected.put("Nico", 1);
expected.put("Olof", 1);
expected.put("Petar", 1);
expected.put("Quentin", 1);
expected.put("Rafal", 1);
expected.put("Renato", 1);
expected.put("Sadio", 1);
expected.put("Samuel", 1);
expected.put("Tomislav", 1);
expected.put("Ulrich", 2);
expected.put("Volkan", 1);
expected.put("Wladimir", 2);
expected.put("Xavier", 2);
expected.put("Yves", 3);
expected.put("Zinedine", 1);
assertThat(FunctionalMain.getValueItemOccurrences(items), is(expected));
}
}
メソッドの実装をに変更すると
public static Map<String, Long> getValueItemOccurrences(Map<String, List<String>> map) {
return map.values().stream()
.flatMap(Collection::stream)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}
テストケースは失敗し、結果のマップが期待されるマップと等しくないことを示します。この日食のスクリーンショットを参照してください。これは、明らかに、要素の順序によってテストが失敗することを示しています。
本当にそれだけですか?私は、HashMap
sは一般的にキーの順序を保証しないことを読んだと思います。
私の(かなり長い)質問は次のとおりです。ストリームAPI呼び出しでテストに合格する結果を生成するにはどうすればよいですか、それともテストケースを変更する必要がありますか、別のアサーションを使用する必要がありますか?
いくつかのサブ質問は次のとおりです。
Map
順序が重要な場合(TreeMap
多分)、特定の実装を返す必要がありますか?TL; DRテストが壊れています、修正してください。
まず第一に、これは次の方法でより簡単に再現できます。
List<String> list = ImmutableList.of("Kumar", "Kumar", "Jens");
public static Map<String, Long> getValueItemOccurrences1(List<String> list) {
return list
.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}
public static Map<String, Long> getValueItemOccurrences2(List<String> list) {
Map<String, Long> occurrencesOfValueItems = new HashMap<>();
list.forEach(item -> {
if (occurrencesOfValueItems.containsKey(item)) {
occurrencesOfValueItems.put(item, occurrencesOfValueItems.get(item) + 1);
} else {
occurrencesOfValueItems.put(item, 1L);
}
});
return occurrencesOfValueItems;
}
問題は、内部HashMap::hash
(再ハッシュとも呼ばれる)と、選択するバケットを決定するときに実際に重要な最後のビットを取得した後、それらが同じ値を持つことです。
System.out.println(hash("Kumar".hashCode()) & 15);
System.out.println(hash("Jens".hashCode()) & 15);
簡単に言うと、HashMap
はエントリの場所に基づいてエントリを配置する場所を決定します(バケットが選択されhashCode
ます)。まあ、ほとんどの場合、hashCode
が計算されると、内部で別の処理がhash
行われます-エントリをより適切に分散させるためです。の最終int
値はhashCode
、バケットを決定するために使用されます。16
(new HashMap
たとえば)のデフォルト容量でHashMapを作成する場合、エントリの移動先は最後の4ビットのみが重要です(そのため& 15
、最後の4ビットを確認するためにそこで作成しました)。
はどこhash
ですか:
// xor first 16 and last 16 bits
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
ここで、上記のアルゴリズムが適用された後、["Kumar" and "Jens"]
または ["Xavier", "Kenneth", "Samuel"]
同じ最後の4桁(最初のケースでは3桁、2番目のケースでは1桁)であることがわかります。
この情報がわかったので、これは実際にはさらに単純化できます。
Map<String, Long> map = new HashMap<>();
map.put("Kumar", 2L);
map.put("Jens", 1L);
System.out.println(map); // {Kumar=2, Jens=1}
map = new HashMap<>();
map.computeIfAbsent("Kumar", x -> 2L);
map.computeIfAbsent("Jens", x -> 1L);
System.out.println(map); // {Jens=1, Kumar=2}
これが内部で使用さmap.computeIfAbsent
れているものでCollectors.groupingBy
あるため、私は使用しました。
これは、ことが判明したput
とcomputeIfAbsent
、中の要素を入れてHashMap
別の方法を使用しました。マップには順序がないため、これは完全に許可されます。これらの要素は、インポート部分である同じバケットに入れられます。したがって、コードをキーごとにテストします。以前のテストコードは壊れていました。
あなたが望むなら、これはさらに楽しい読書です:
HashMap::put
Linked
(Tree
エントリが作成されるまで)ある方法で要素を追加するため、1つの要素が存在する場合、他のすべての要素は次のように追加されます。
one --> next --> next ... so on.
要素はend of this queue
、put
メソッドに入るときにに追加されます。
一方computeIfAbsent
、少し異なり、キューの先頭に要素を追加します。上記の例をとると、最初Xavier
に追加されます。次に、Kenneth
が追加されると、最初になります。
Kenneth -> Xavier // Xavier was "first"
ときにSamuel
追加され、それが最初に次のようになります。
Samuel -> [Kenneth -> Xavier]
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加