Search code examples
javalambdarx-javacollect

How to insert two records in map at a time using RxJava and Lambda expressions


I have a list of records, where each record has two primary keys Primary Id and Alternate Id. I want to make a map through which I can access the processed records using either Primary Id or the Alternate Id using RxJava operations.

Current implementation:

ImmutableMap.Builder<String, Record> mapBuilder = new ImmutableMap.Builder<>();
      fetchRecords()
          .forEach(
              record -> {
                parsedRecord = dosomething(record);
                mapBuilder.put(parsedRecord.getPrimaryId(), parsedRecord);
                mapBuilder.put(parsedRecord.getAlternativeId(), parsedRecord);
              });
      return mapBuilder.build();

How I want it to look like:

fetchRecords().stream()
    .map(doSomething)
    .collect(Collectors.toMap(RecordData::getPrimaryId, Function.identity()));
// Need to add this as well
.collect(Collectors.toMap(RecordData::getAlterantiveId, Function.identity()));

Just wanted to know if there is a way that I can add the secondary id to record mapping as well in a single pass over fetchRecords().


Solution

  • I'm not familiar with rx-java but this might provide a starting point. Create a custom collector to add both keys. This is a very basic collector and does not handle duplicate keys other than using the last one provided. I simply used the new record feature introduced in Java 15 to create an immutable "class". This would work the same way for a regular class.

    record ParsedRecord(String getPrimaryId,
            String getAlternativeId, String someValue) {
        @Override
        public String toString() {
            return someValue;
        }
    }
    
    Map<String, ParsedRecord> map = records.stream()
            .collect(twoKeys(ParsedRecord::getPrimaryId,
                    ParsedRecord::getAlternativeId, p -> p));
    
    map.entrySet().forEach(System.out::println);
    

    Prints the following:

    A=value1
    B=value1
    C=value2
    D=value2
    

    Here is the collector. It is essentially a simplified version of toMap that takes two keys instead of one.

    private static <T, K, V> Collector<T, ?, Map<K, V>> twoKeys(
            Function<T, K> keyMapper1, Function<T, K> keyMapper2,
            Function<T, V> valueMapper) {
    
        return Collector.of(
            () -> new HashMap<K, V>(),
            (m, r) -> {
            V v = valueMapper.apply(r);
            m.put(keyMapper1.apply(r),v);
            m.put(keyMapper2.apply(r),v);
        }, (m1, m2) -> {
            m1.putAll(m2);
            return m1;
        }, Characteristics.UNORDERED);
    }
    
    

    Or just keep it simple and efficient.

    Map<String, ParsedRecord> map = new HashMap<>();
    for(ParsedRecord pr : records) {
        map.put(pr.getPrimaryId(), pr);
        map.put(pr.getAlternativeId(), pr);
    }