[英]Best data structure to “group by” and aggregate values in Java?
我創建了一個如下所示的 Array 類型的 ArrayList,
ArrayList<Object[]> csvArray = new ArrayList<Object[]>();
如您所見,ArrayList 的每個元素都是一個數組,如 {Country, City, Name, Age}。
現在我想對國家和城市(組合)進行“分組”,然后取每個國家+城市的平均年齡。
我可以知道實現這一目標的最簡單方法是什么嗎? 或者你們有建議使用比 ArrayList 更好的數據結構來滿足這個“分組依據”和聚合要求?
非常感謝您的回答。
您將在 Java 8 中獲得很多選項。
例子
Stream<Person> people = Stream.of(new Person("Paul", 24), new Person("Mark",30), new Person("Will", 28));
Map<Integer, List<String>> peopleByAge = people
.collect(groupingBy(p -> p.age, mapping((Person p) -> p.name, toList())));
System.out.println(peopleByAge);
如果你可以使用 Java 8 並且沒有使用數據結構的具體原因,你可以通過下面的教程
您可以為此使用 Java 8 流和Collectors.groupingBy
。 例如:
final List<Object[]> data = new ArrayList<>();
data.add(new Object[]{"NL", "Rotterdam", "Kees", 38});
data.add(new Object[]{"NL", "Rotterdam", "Peter", 54});
data.add(new Object[]{"NL", "Amsterdam", "Suzanne", 51});
data.add(new Object[]{"NL", "Rotterdam", "Tom", 17});
final Map<String, List<Object[]>> map = data.stream().collect(
Collectors.groupingBy(row -> row[0].toString() + ":" + row[1].toString()));
for (final Map.Entry<String, List<Object[]>> entry : map.entrySet()) {
final double average = entry.getValue().stream()
.mapToInt(row -> (int) row[3]).average().getAsDouble();
System.out.println("Average age for " + entry.getKey() + " is " + average);
}
您可以查看@duffy356 推薦的系列。 我可以給你一個與java.utils
相關的標准解決方案
我會使用一個通用的Map<Key,Value>
並且是一個特定的HashMap
。
對於鍵,如我所見,您需要一個與國家和城市相關的額外普通對象。 關鍵是創建一個工作equals(Object) : boolean
方法。 我會使用 Eclipse 自動生成器; 對我來說,它給了我以下內容:
class CountryCityKey {
// package visibility
String country;
String city;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((country == null) ? 0 : country.hashCode());
result = prime * result + ((region == null) ? 0 : region.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CountryCityKey other = (CountryCityKey) obj;
if (country == null) {
if (other.country != null)
return false;
} else if (!country.equals(other.country))
return false;
if (region == null) {
if (other.region != null)
return false;
} else if (!region.equals(other.region))
return false;
return true;
}
}
現在我們可以在HashMap<CountryCityKey, MySuperObject>
分組或對象
代碼可能是:
Map<CountryCityKey, List<MySuperObject>> group(List<MySu0perObject> list) {
Map<CountryCityKey, MySuperObject> response = new HashMap<>(list.size());
for (MySuperObject o : list) {
CountryCityKey key = o.getKey(); // I consider this done, so simply
List<MySuperObject> l;
if (response.containsKey(key)) {
l = response.get(key);
} else {
l = new ArrayList<MySuperObject>();
}
l.add(o);
response.put(key, l);
}
return response;
}
你有它:)
你可以使用magicwerk.org的brownies-collections庫( http://www.magicwerk.org/page-collections-overview.html )
他們提供符合您要求的密鑰列表。( http://www.magicwerk.org/page-collections-examples.html )
我會推薦一個額外的步驟。 您從 Object[] 中的 CSV 收集數據。 如果您將數據包裝到一個包含這些數據的類中,java8 集合將很容易為您提供幫助。 (也沒有,但它更具可讀性和可理解性)
這是一個示例 - 它引入了一個類Information
,其中包含您的給定數據(國家、城市、姓名、年齡)。 該類有一個構造函數,通過給定的Object[]
數組初始化這些字段,這可能會幫助您這樣做 - 但是:必須修復這些字段(這對於 CSV 來說很常見):
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CSVExample {
public static void main(String[] args) {
ArrayList<Information> csvArray = new ArrayList<>();
csvArray.add(new Information(new Object[] {"France", "Paris", "Pierre", 34}));
csvArray.add(new Information(new Object[] {"France", "Paris", "Madeleine", 26}));
csvArray.add(new Information(new Object[] {"France", "Toulouse", "Sam", 34}));
csvArray.add(new Information(new Object[] {"Italy", "Rom", "Paul", 44}));
// combining country and city with whitespace delimiter to use it as the map key
Map<String, List<Information>> collect = csvArray.stream().collect(Collectors.groupingBy(s -> (s.getCountry() + " " + s.getCity())));
//for each key (country and city) print the key and the average age
collect.forEach((k, v) -> System.out.println(k + " " + v.stream().collect(Collectors.averagingInt(Information::getAge))));
}
}
class Information {
private String country;
private String city;
private String name;
private int age;
public Information(Object[] information) {
this.country = (String) information[0];
this.city = (String) information[1];
this.name = (String) information[2];
this.age = (Integer) information[3];
}
public Information(String country, String city, String name, int age) {
super();
this.country = country;
this.city = city;
this.name = name;
this.age = age;
}
public String getCountry() {
return country;
}
public String getCity() {
return city;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Information [country=" + country + ", city=" + city + ", name=" + name + ", age=" + age + "]";
}
}
main 為您的問題顯示了一個簡單的輸出。
在 java 8 中,通過使用收集器簡化了基於一個或多個屬性值對集合中的對象進行分組的想法。
首先,我建議你添加一個新類如下
class Info {
private String country;
private String city;
private String name;
private int age;
public Info(String country,String city,String name,int age){
this.country=country;
this.city=city;
this.name=name;
this.age=age;
}
public String toString() {
return "("+country+","+city+","+name+","+age+")";
}
// getters and setters
}
設置infos
ArrayList<Info> infos =new ArrayList();
infos.add(new Info("USA", "Florida", "John", 26));
infos.add(new Info("USA", "Florida", "James", 18));
infos.add(new Info("USA", "California", "Alan", 30));
Map<String, Map<String, List<Info>>>
groupByCountryAndCity = infos.
stream().
collect(
Collectors.
groupingBy(
Info::getCountry,
Collectors.
groupingBy(
Info::getCity
)
)
);
System.out.println(groupByCountryAndCity.get("USA").get("California"));
輸出
[(USA,California,James,18), (USA,California,Alan,30)]
Map<String, Map<String, Double>>
averageAgeByCountryAndCity = infos.
stream().
collect(
Collectors.
groupingBy(
Info::getCountry,
Collectors.
groupingBy(
Info::getCity,
Collectors.averagingDouble(Info::getAge)
)
)
);
System.out.println(averageAgeByCountryAndCity.get("USA").get("Florida"));
輸出:
22.0
/* category , list of cars*/
請使用以下代碼:我從我的示例應用程序中粘貼了它!Happy Coding。
Map<String, List<JmCarDistance>> map = new HashMap<String, List<JmCarDistance>>();
for (JmCarDistance jmCarDistance : carDistanceArrayList) {
String key = jmCarDistance.cartype;
if(map.containsKey(key)){
List<JmCarDistance> list = map.get(key);
list.add(jmCarDistance);
}else{
List<JmCarDistance> list = new ArrayList<JmCarDistance>();
list.add(jmCarDistance);
map.put(key, list);
}
}
最好的數據結構是 Map<Tuple, List>。
元組是關鍵,即按列分組。 List 用於存儲行數據。
在此結構中擁有數據后,您可以遍歷每個鍵,並對數據子集執行聚合。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.