3
\$\begingroup\$

I have a function that parses geographic coordinates through a list of different providers. In this case I want to look up two addresses. The main problem of this question is - given a List of of Geocode Service -> Resolved Address mappings, find the first successful pair that has a valid resolved address.

So each address results in a map(provider -> result). I'm given a list of them, and need to find the a successful pair from the list e.g provider1 from the first address and provider1 from the second address. I can't compare the resolved addresses if the providers do not match.

For example, the best scenario is that the addresses are perfect. I can use the head map entry of the first address and query the map entry for the second address. Then compare their values. However, it's rarely that simple.

I started out with a foldLeft, but I feel that can be inefficient if it matches successfully immediately.

val result: List[Map[GeoProviders.GeoProvider, Either[GeoCodeErrors.GeocodeError, PostalAddress]]]
                  result match {
                    case firstAddressOption :: secondAddressOption :: Nil => {
                      firstAddressOption.foldLeft[Option[Double]](None)((result, address1: (GeoProvider, Either[GeocodeError, PostalAddress])) => {
                        result match {
                          case Some(_) => result
                          case None =>
                            val address2 = secondAddressOption.get(address1._1)
                            (address1._2, address2) match {
                              case  (Right(successAddr1), Some(Right(successAddr2))) => {
                                (successAddr1.coordinates, successAddr2.coordinates) match {
                                  case (Some(firstCoord), Some(secondCoord)) => {
                                    Some(haversineDistance((firstCoord.latitude, firstCoord.longitude), (secondCoord.latitude, secondCoord.longitude)))
                                  }
                                  case _ => None
                                }
                              }
                            }
                        }
                      })
\$\endgroup\$
2
+50
\$\begingroup\$

You can use find to avoid folding over an entire map, something like:

map1.find {
  case (k, Right(addr1)) =>
    map2.get(k) match {
      case Some(Right(addr2)) => true
      case _ => false
    }
  case _ => false
} match {
  case Some((k, Right(addr1))) =>
    map2.get(k) match {case Some(Right(addr2)) => Some(haversineDistance(addr1, addr2))}
  case _ => None
}

(I renamed first/secondAddressOption to map1/map2 to save some typing)

An alternative approach, which doesn't necessarily improve performance but does make the code shorter and more reusable, is to create a method that combines two maps by key:

def combineMaps[K, V](map1: Map[K, V], map2: Map[K, V]): Map[K, List[V]] =
  // To improve performance, compare the sizes of map1 and map2
  // and fold over the smaller map.
  map1.foldLeft(map2.mapValues(List(_))) {
    case (map2, (k, x)) => map2.updated(k, x :: map2.get(k).toList.flatten)
  }

Then use collectFirst on the combined map:

combineMaps(map1, map2).collectFirst {
  case (_, Right(addr1) :: Right(addr2) :: Nil) => haversineDistance(addr1, addr2)
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.