3

我选择的数据结构设计执行起来非常尴尬,所以与其就如何执行它征求你的专家意见,我希望你能为我正在尝试做的事情提出一个更自然的数据结构,如下。我正在读取数据行。每列都是一个变量(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?

非常感谢 - 埃德

4

3 回答 3

3

您可以通过将您的替换Map<String, Integer>Multiset来消除一些奇怪之处。

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

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

对应的“.get()”不返回一个HashMap,而是一个Collection

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

但是,您很少直接使用 Multimap 界面;更多时候你会使用ListMultimapor SetMultimap,它们分别将键映射到 List 或 Set 。


因此,要坚持您的原始设计:

  • 每列都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[]for 标头和 aMap<String, Multiset<String>>作为它们的值,使用 a Column[]。您可以创建此数组来代替创建headers数组。

于 2013-06-16T09:49:22.237 回答
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);
    }
}
于 2013-06-16T09:45:42.173 回答
1

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

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

于 2013-06-16T10:13:58.520 回答