更好的数据结构,用于哈希映射的多个映射

时间:2013-06-16 08:43:12

标签: java data-structures

我选择的数据结构设计证明执行起来非常尴尬,所以我不希望你的专家意见如何执行它,我希望你能为我想要的东西建议一个更自然的数据结构做,如下。我在读数据行。每列都是一个变量(Animal,Color,Crop,... - 其中有45个)。每行数据都有一个该列变量的值 - 您事先不知道值或行数。

Animal  Color   Crop    ...
-------------------------------------
cat     red     oat
cat     blue        hay
dog     blue        oat
bat     blue        corn
cat     red     corn
dog     gray        corn
...     ...     ...

当我完成阅读时,它应该捕获每个变量,变量所采用的每个值,以及该变量获取该值的次数,如下所示:

Animal [cat, 3][dog,2][bat, 1]...
Color [blue, 3][red,2][gray,1]...
Crop [corn,3][oat, 2][hay,1]...
...

我尝试了几种方法,最接近的是使用GUAVA多地图的哈希映射,如下所示:

Map<String, Integer> eqCnts = new HashMap<String, Integer>();
Multimap<String, Map> ed3Dcnt = HashMultimap.create();
for (int i = 0; i + 1 < header.length; i++) {
    System.out.format("Got a variable of %s\n", tmpStrKey = header[i]);
    ed3Dcnt.put(tmpStrKey, new HashMap<String, Integer>());
 }

似乎我已经创造了我想要的东西,但是使用起来非常笨拙和乏味,并且它的行为也是神秘的(尽管“ed3Dcnt.put()”插入了一个HashMap,相应的“.get()”不会返回HashMap,而是返回一个Collection,它会创建一组全新的问题。)请注意,我想对值进行排序,从最高到最低,但我认为我可以很容易地做到这一点。

如果您愿意,建议更好地选择数据结构设计?如果没有明显更好的设计选择,我如何使用.get()返回的Collection,当我想要的只是我放在那个插槽中的单个HashMap时?

非常感谢 - Ed

3 个答案:

答案 0 :(得分:3)

您可以将Map<String, Integer>替换为Multiset来消除一些奇怪现象。

A multiset (or a bag)是一个允许重复元素的集合 - 并对它们进行计数。你又扔了一个苹果,一个梨子和一个苹果。它记得它有两个苹果和一个梨。基本上,这是您在刚刚使用的Map<String, Integer>下所想象的。

Multiset<String> eqCounts = HashMultiset.create();

  

相应的“.get()”不会返回HashMap,而是返回一个   集合

这是因为您使用了通用的“Multimap”界面。文档说:

  

但您很少直接使用Multimap界面;更经常的   您将使用ListMultimapSetMultimap,它们将键映射到List或a   分别设置。


所以,坚持你原来的设计:

  • 每列都是Multiset<String>,用于存储和计算您的值。
  • 您将拥有Map<String, Multiset<String>>(键是标题,值是列),您可以将列放在这样的列中:

    Map<String, Multiset<String>> columns = Maps.newHashMap();
    for (int i = 0; i < headers.length; i++) {
        System.out.format("Got a variable of %s\n", headers[i]);
        columns.put(headers[i], HashMultiset.<String>create());
    }
    

读取一行并将值放在它们所属的位置:

String[] values = line.split(" ");
for (int i = 0; i < headers.length; i++) {
    columns.get(headers[i]).add(values[i]);
}

所有这一切,你可以看到外部HashMap是多余的,整个事情仍然可以改进(尽管它已经足够好了,我认为)。要进一步改进,您可以尝试以下方法:

  1. 使用Multiset而不是HashMap的数组。毕竟,你事先知道列数。
  2. 如果您对创建通用数组感到不舒服,请使用List.
  3. 可能是最好的:像这样创建一个类Column

    private static class Column {
        private final String header;
        private final Multiset<String> values;
    
        private Column(String header) {
            this.header = header;
            this.values = HashMultiset.create();
        }
    }
    

    而不是使用String[]标题和Map<String, Multiset<String>>作为其值,而是使用Column[]。您可以创建此数组来代替创建headers数组。

答案 1 :(得分:1)

在我看来,最合适的是:

HashMap<String, HashMap<String, Integer>> map= new HashMap<String, HashMap<String, Integer>>();

现在,添加标题内部地图:

for (int i = 0; i + 1 < header.length; i++) {
    System.out.format("Got a variable of %s\n", tmpStrKey = header[i]);
    map.put(tmpStrKey, new HashMap<String, Integer>());
}

并增加内部地图中的值:

//we are in some for loop
for ( ... ) {
    String columnKey = "animal"; //lets say we are here in the for loop
    for ( ... ) {
        String columnValue = "cat"; //assume we are here
        HashMap<String, Integer> innerMap = map.get(columnKey);

        //increment occurence
        Integer count = innerMap.get(columnValue);
        if (count == null) {
            count = 0;
        }
        innerMap.put(columnValue, ++count);
    }
}

答案 2 :(得分:1)

1)多图中的地图通常称为基数图。为了从值集合中创建基数映射,我通常使用来自Apache Commons Collections的CollectionUtils.getCardinalityMap,虽然这不是一般化的,因此您需要一个不安全(但已知是安全的)强制转换。如果你想使用Guava构建地图我认为你应该首先将变量的值放在Set<String>中(以获取唯一值的集合),然后对每个值使用Iterables.frequency()来获取计数。 (编辑:甚至更简单:使用ImmutableMultiset.copyOf(collection)将基数地图设为Multiset)无论如何,生成的基数地图是Map<String, Integer,例如您已经在使用。

2)我不明白你为什么需要Multimap。毕竟你想将每个变量映射到基数图,所以我使用Map<String, Map<String, Integer>>。 编辑:或者如果您决定使用Multiset作为基数地图,请使用Map<String, Multiset<String>>