RxJava2 – group results of objects processing into the Map<String, List>

I have a list of REST URIs and there’s a method that returns some value for the passed URI:

String searchForValueBy(RestURI uri);

For instance, we have the following list:

List<RestURI> restUriList = List.of(uri1, uri2, uri3, uri4, uri5);

And searchForValueBy returns the following values for each URI:

uri1 : "1"
uri2 : "2"
uri3 : "1"
uri4 : "1"
uri5 : "2"

What I want to achieve is to group URIs and values into the following map Map<String, List<RestURI>>.

I did the following implementation, but it uses two different approaches – traditional and reactive ones:

Map<String, List<RestURI>> valueToUriMap = new HashMap<>();
Observable.fromIterable(restUriList)
            .flatMapCompletable(uri -> searchForValueBy(uri))
                    .flatMapCompletable(value -> {
                        valueToUriMap.computeIfAbsent(value, key -> new ArrayList<>()).add(uri);
                        return Completable.complete();
                    }))
            .andThen(/*work with valueToUriMap*/)

Is it possible to achieve the same with some sort of grouping like groupBy?

Here is the working example to copy and play:

public static void main(String[] args) {
        final List<RestUri> restUriList = List.of(
                new RestUri("uri1", "1"),
                new RestUri("uri2", "2"),
                new RestUri("uri3", "1"),
                new RestUri("uri4", "1"),
                new RestUri("uri5", "2"));

        Map<String, List<RestUri>> valueToUriMap = new HashMap<>();
        Observable.fromIterable(restUriList)
                .flatMapCompletable(uri -> searchForValueBy(uri)
                        .flatMapCompletable(value -> {
                            valueToUriMap.computeIfAbsent(value, key -> new ArrayList<>()).add(uri);
                            return Completable.complete();
                        })).subscribe();
//                .andThen(/*work with valueToUriMap*/)

        valueToUriMap.keySet().forEach(key -> System.out.println("Key: " + key + ", Value: " + valueToUriMap.get(key)));
    }

    public static Single<String> searchForValueBy(RestUri uri) {
        return Single.just(uri.getValue());
    }

    static class RestUri {
        String uri;
        String value;

        public RestUri(String uri, String value) {
            this.uri = uri;
            this.value = value;
        }

        public String getUri() {
            return uri;
        }

        public String getValue() {
            return value;
        }

        @Override
        public String toString() {
            return uri;
        }
    }

Updated after @akarnokd comment

It seems that toMultimap does the trick, but at the end it appears to be a List of single entry maps instead of Map with a bunch of key:value pairs:

Observable<Single<Map<String, Collection<RestUri>>>> result = Observable.fromIterable(restUriList)
            .map(uri -> searchForValueBy(uri)
                    .toMultimap(key -> key, value -> uri));

Answer

Try this:

Observable.fromIterable(restUriList)
          .flatMapSingle(uri -> 
                searchForValueBy(uri)
                .map(v -> new RestUri(uri.getUri(), v))
          )
          .toMultimap(RestUri::getValue, RestUri::getUri))