2017-06-07 31 views
9

Po znalezieniu powtórzonego wpisu klucza podczas Collectors.toMap() wywoływana jest funkcja scalania (o1, o2).Jak zdobyć klucz w funkcji scalania Collectors.toMap?

Pytanie: w jaki sposób mogę uzyskać klucz, który spowodował duplikowanie?

String keyvalp = "test=one\ntest2=two\ntest2=three"; 

Pattern.compile("\n") 
    .splitAsStream(keyval) 
    .map(entry -> entry.split("=")) 
    .collect(Collectors.toMap(
     split -> split[0], 
     split -> split[1], 
     (o1, o2) -> { 
      //TODO how to access the key that caused the duplicate? o1 and o2 are the values only 
      //split[0]; //which is the key, cannot be accessed here 
     }, 
    HashMap::new)); 

Wewnątrz funkcji scalania chcę decydować na podstawie kluczowego który jeśli mogę anulować mapowanie lub kontynuować i wziąć na tych wartościach.

+0

Możesz później filtrować połączone wartości, czy naprawdę trzeba je filtrować podczas łączenia? –

+0

Czy możesz podać przykład? Podczas scalania muszę zdecydować, które z wartości przyjąć (o1 lub o2). Decyzję należy podjąć na ** kluczu **. Ale klucz nie zawsze występuje dwa razy.Czasem tylko w takim przypadku trzeba zdecydować o fuzji. – membersound

+1

ok, widzę. Możesz stworzyć oddzielną mapę i uruchomić '.map (entry -> entry.split (" = ")). ForEach()' i sprawdzić dla każdego wpisu, czy wartość jest już w Mapie. Jeśli nie - dodaj, w przeciwnym razie - sprawdź, czy wymienić lub nie. –

Odpowiedz

5

Musisz użyć niestandardowego kolektora lub zastosować inne podejście.

Map<String, String> map = new Hashmap<>(); 
Pattern.compile("\n") 
    .splitAsStream(keyval) 
    .map(entry -> entry.split("=")) 
    .forEach(arr -> map.merge(arr[0], arr[1], (o1, o2) -> /* use arr[0])); 

Pisanie niestandardowego kolektora jest bardziej skomplikowane. Potrzebujesz TriConsumer (klucz i dwie wartości) jest podobny, co nie jest w JDK, dlatego jestem prawie pewien, że nie ma wbudowanej funkcji, która używa. ;)

4

Funkcja scalania nie ma szansy na uzyskanie klucza, który jest tym samym problemem, wbudowana funkcja ma, gdy pominąć funkcję scalania.

Rozwiązaniem jest użycie innego toMap realizacji, które nie opierają się na Map.merge:

public static <T, K, V> Collector<T, ?, Map<K,V>> 
    toMap(Function<? super T, ? extends K> keyMapper, 
      Function<? super T, ? extends V> valueMapper) { 
    return Collector.of(HashMap::new, 
     (m, t) -> { 
      K k = keyMapper.apply(t); 
      V v = Objects.requireNonNull(valueMapper.apply(t)); 
      if(m.putIfAbsent(k, v) != null) throw duplicateKey(k, m.get(k), v); 
     }, 
     (m1, m2) -> { 
      m2.forEach((k,v) -> { 
       if(m1.putIfAbsent(k, v)!=null) throw duplicateKey(k, m1.get(k), v); 
      }); 
      return m1; 
     }); 
} 
private static IllegalStateException duplicateKey(Object k, Object v1, Object v2) { 
    return new IllegalStateException("Duplicate key "+k+" (values "+v1+" and "+v2+')'); 
} 

(jest to w zasadzie co Implementacja Javy 9 z dnia toMap bez funkcji scalania zrobi)

Wszystko, co musisz zrobić w swoim kodzie, to przekierowanie połączenia toMap i pominięcie funkcji scalania:

String keyvalp = "test=one\ntest2=two\ntest2=three"; 

Map<String, String> map = Pattern.compile("\n") 
     .splitAsStream(keyvalp) 
     .map(entry -> entry.split("=")) 
     .collect(toMap(split -> split[0], split -> split[1])); 

(lub ContainingClass.toMap jeśli jej nie w tej samej klasy, ani importu statyczne) < \ sup>

Kolektor obsługuje równoległego przetwarzania jak oryginalne toMap kolektora, chociaż nie jest to bardzo prawdopodobne, aby uzyskać korzyści z przetwarzania równoległego tutaj , nawet z większą ilością elementów do przetworzenia.

przypadku, gdybym cię prawidłowo, wystarczy tylko chcieć, aby wybrać albo, starszej lub nowszej wartości w funkcji korespondencji seryjnej w oparciu o rzeczywistą klucza, można to zrobić za pomocą klucza Predicate jak ta

public static <T, K, V> Collector<T, ?, Map<K,V>> 
    toMap(Function<? super T, ? extends K> keyMapper, 
      Function<? super T, ? extends V> valueMapper, 
      Predicate<? super K> useOlder) { 
    return Collector.of(HashMap::new, 
     (m, t) -> { 
      K k = keyMapper.apply(t); 
      m.merge(k, valueMapper.apply(t), (a,b) -> useOlder.test(k)? a: b); 
     }, 
     (m1, m2) -> { 
      m2.forEach((k,v) -> m1.merge(k, v, (a,b) -> useOlder.test(k)? a: b)); 
      return m1; 
     }); 
} 
Map<String, String> map = Pattern.compile("\n") 
     .splitAsStream(keyvalp) 
     .map(entry -> entry.split("=")) 
     .collect(toMap(split -> split[0], split -> split[1], key -> condition)); 

Istnieje kilka sposobów, aby dostosować ten kolektor ...

+1

Nie mam pojęcia, ile razy dziennie to słyszysz, ale dziękuję ... – Eugene

0

jest, oczywiście, prosty i banalny trik - zapisywania klucza w funkcji „klucz odwzorowujący” i coraz klucz w „scalenia” funkcjonować. Kod może wyglądać następująco (zakładając, że klucz jest liczbą całkowitą):

final AtomicInteger key = new AtomicInteger(); 
...collect(Collectors.toMap( 
    item -> { key.set(item.getKey()); return item.getKey(); }, // key mapper 
    item -> ..., // value mapper 
    (v1, v2) -> { log(key.get(), v1, v2); return v1; } // merge function 
); 

Uwaga: nie nadaje się do przetwarzania równoległego.