如何将list <sometype>转换为映射,以保留所有重复的键和值

时间:2019-01-20 21:29:42

标签: java data-structures collections java-8

我的列表如下:

List<Address> address;

其中

Address:
city
country
state

我想将其转换为类似

Map <String,String> convertedMap=list.stream().collect
(Collectors.toMap(Address:getCity+Address:getCountry ,Address:getState)); 

我要在生成的映射中保留所有重复的键和值,如下所示。

(key=City1country1, value= state1) ,(key=City1country1, value=state2),(key=City1country1,value= state1) ;

4 个答案:

答案 0 :(得分:1)

如评论中所述,地图不存储重复的键,因此您必须使用Map<String, List<String>>代替Map<String, String>

总而言之,您只需要使用带有Collectors.toMap参数的mergeFunction方法即可处理重复的键。每次出现相同的键时都会调用此操作。在这种情况下,我们只需将两个列表合并为一个即可。看看下面的代码(与JDK 11一起编译),我相信它完全可以满足您的需要并打印出预期的结果(当然使用List<String>)。

import java.util.*;
import java.util.stream.Collectors;

public class ListToMapWithDuplicatedKeysSample {

    public static void main(String[] args) {
        List<Address> addresses = List.of(
                new Address("City1", "country1", "state1"),
                new Address("City1", "country1", "state2"),
                new Address("City1", "country1", "state1"),
                new Address("City3", "country3", "state3")
        );
        Map<String, List<String>> result = addresses.stream()
                .collect(
                        Collectors.toMap(
                                address -> address.getCity() + address.getCountry(),
                                address -> Collections.singletonList(address.getState()),
                                ListToMapWithDuplicatedKeysSample::mergeEntriesWithDuplicatedKeys
                        )
                );
        System.out.println(result);
    }

    private static List<String> mergeEntriesWithDuplicatedKeys(List<String> existingResults, List<String> newResults) {
        List<String> mergedResults = new ArrayList<>();
        mergedResults.addAll(existingResults);
        mergedResults.addAll(newResults);
        return mergedResults;
    }

    private static class Address {

        private final String city;
        private final String country;
        private final String state;

        public Address(String city, String country, String state) {
            this.city = city;
            this.country = country;
            this.state = state;
        }

        String getState() {
            return state;
        }

        String getCountry() {
            return country;
        }

        String getCity() {
            return city;
        }
    }
}

答案 1 :(得分:1)

这类事情是许多编码挑战的普遍要求,如果有时间限制,这是一种完成所需内容的快速方法,我选择使用集合而不是列表来查找时间为O (1)而不是O(N):

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

class MapExample {
  public static void main(String[] args) {
    List<String> addressesList = new ArrayList<>();
    addressesList.add("Portland USA ME");
    addressesList.add("Portland USA OR");
    addressesList.add("Boston USA MA");
    System.out.println(cityToStatesMap(addressesList));
  }

  private static Map<String, Set<String>> cityToStatesMap(List<String> addressesList) {
    Map<String, Set<String>> result = new HashMap<>();
    if (addressesList == null || addressesList.size() == 0) {
      return result;
    }
    for (String address : addressesList) {
      String[] addressComponents = address.split(" ");
      String city = addressComponents[0];
      String country = addressComponents[1];
      String state = addressComponents[2];
      String key = city + country;
      Set<String> states = result.computeIfAbsent(key, k -> new HashSet<>());
      states.add(state);
      result.put(key, states);
    }
    return result;
  }
}

输出:

{PortlandUSA=[ME, OR], BostonUSA=[MA]}

注意:我认为您应该使用,之类的符号作为分隔符而不是空格,这样可以更轻松地处理带有多个单词的城市,例如纽约,旧金山等

答案 2 :(得分:1)

您不能使用Collectors.toMap,因为它需要唯一的密钥。相反,您想要的是groupingBy,它返回每个键的收集值:

class Location {
    public final String city;
    public final String country;

    Location(Address address) {
        this.city = address.getCity();
        this.country = address.getCountry();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Location) {
            Location other = (Location) obj;
            return Objects.equals(this.city, other.city) &&
                   Objects.equals(this.country, other.country);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hash(city, country);
    }
}

Map<Location, Set<String>> statesByLocation = addresses.stream().collect(
    Collectors.groupingBy(Location::new,
        Collectors.mapping(Address::getState, Collectors.toSet())));

可以使用各种技巧来组合城市和国家(例如两个字符串的列表),但是您实际上应该像上面所示那样制作一个密钥类。该代码将更容易使用,特别是如果您(或另一个开发人员)应该有理由在六个月内重新使用它。

关于收集器方法:

  • Collectors.groupingBy(Location::new等效于address -> new Location(address),并在地图中创建每个键。
  • Collectors.mapping(Address::getState, Collectors.toSet())意味着对应于特定键的每个Address都将被调用getState(),并且所产生的字符串将被汇总到Set中以形成Map值。

我个人认为我会选择简单的for循环而不是难以理解的Stream方法:

Map<Location, Set<String>> statesByLocation = new HashMap<>();
for (Address address : addresses) {
    statesByLocation.computeIfAbsent(new Location(address),
        k -> new HashSet<String>()).add(address.getState());
}

答案 3 :(得分:0)

使用具有以下方法签名的toMap

   toMap(Function k, Function v,BinaryOperator m)

BinaryOperator 会通知JVM如果遇到重复的密钥该怎么办。在您的情况下,如果遇到重复项,则应将它们合并。

因此 s1 +“,” + s2

因此您的解决方案变为

  Map<String, String> map = list.stream().
            collect(Collectors.toMap((address -> {
                String y = address.getCity() + address.getCountry();
                return y + s;
            }), Address::getState, (s1, s2) -> s1 + "," + s2));

输出

     country3City3  state3
     country1City1  state1,state2,state1

      Process finished with exit code 0