为scala地图编写自定义get方法

时间:2018-01-24 21:55:23

标签: scala design-patterns

我有一张类似

的地图
val m = Map("foo" -> "bar", "faz" -> "baz")

我需要编写一个自定义get方法,这样键就可以成为地图中最后带数字的键。

例如:

m.get("foo1") should return "bar"

我正在寻找一个好的scala模式来解决这个问题。

我也是使用yield从for循环生成上面的地图,所以我不能做这样的事情

val m = CustomMap("foo" -> "bar")

任何解决方案都将受到赞赏。

谢谢

4 个答案:

答案 0 :(得分:0)

首先,您可以从for理解生成地图,然后将其转换为CustomMap。你只需要定义一个 def apply(map: Map[String, String]) = CustomMap(map.toSeq :_*)中的CustomMap - 然后您可以执行val m = CustomMap( for { ... } yield ... )

其次,如果它不必被命名为get(它可能不应该是这样),你可以用隐含的方式做这种事情:

 object PimpMyMap { 
    val pref = ".*?(\\d+)".r
    implicit class Pimped[V](val map: Map[String,V]) extends AnyVal {
      def getPrefix(key: String): Option[V] = map.get(key).orElse { key match {
          case pref(k) => map.get(k)
          case _ => None
  }
 }

现在你可以写下这样的内容:

  import PimpMyMap._
  val map = Map("foo" -> 1)
  val one = map.getPrefix("foo123") // Some(1)
  val anotherOne = map.getPrefix("foo") // also Some(1);

答案 1 :(得分:0)

您可以使用implicit classimplicit conversion

执行此操作
import scala.language.implicitConversions

object MapHelpers {
  implicit def optionStringToString(maybeS: Option[String]): String = maybeS.getOrElse("")
  implicit class MapWithIntKey(val m: Map[String, String]) extends Map[String, String] {
    override def get(key: String): Option[String] = {
      val intRegex = """(\d+)""".r
      val keyWithoutInt = intRegex
        .findFirstMatchIn(key)
        .map(int => {
          val idx = key.indexOf(int.toString)
          key.slice(0, idx)
        })
        .getOrElse(key)
      m.get(keyWithoutInt)
    }

    def +[V1 >: String](
        kv: (String, V1)): scala.collection.immutable.Map[String, V1] = m + kv
    def -(key: String): scala.collection.immutable.Map[String, String] = m - key
    def iterator: Iterator[(String, String)] = m.iterator
  }
}

object App {

  import MapHelpers._

  def testMapImplicit(): Unit = {
    val myMap: MapWithIntKey = Map("foo" -> "bar", "faz" -> "baz")
    val result: String = myMap.get("foo1")
    println("result", result) // bar
  }
}

Working Scastie

答案 2 :(得分:0)

如果您有可靠的方法从假密钥中获取真实密钥,则可以使用Map.withDefault执行此操作:

class CustomMap[K, +V] private (underlying: Map[K, Option[V]]) {
  def get(k: K): Option[V] = underlying(k)
}

object CustomMap {
  def apply[K, V](original: Map[K, V], keyReducer: K => K) = new CustomMap(originalMap.
    mapValues(Some(_)).
    withDefault(k => originalMap.get(keyReducer(k))
  )
}

在您的情况下,您可以将其与

一起使用
val stringKeyReducer: String => String = k.reverse.dropWhile(_.isDigit).reverse

删除字符串末尾的数字,所以

CustomMap(Map("foo" -> "bar"), stringKeyReducer).get("foo1") = Some("bar")

答案 3 :(得分:0)

这是结合了两个答案的解决方案。

import scala.language.implicitConversions

object MapHelpers {
  implicit def optionStringToString(maybeS: Option[String]): String = maybeS.getOrElse("")
  implicit class MapWithIntKey(val m: Map[String, String]) extends Map[String, String] {
    override def get(key: String): Option[String] = {
      val prefix = "(.*?)\\d+".r
      m.get(key).orElse{
        key match {
          case prefix(p) => m.get(p)
          case _ => None
        }
      }
    }

    def +[V1 >: String](kv: (String, V1)): scala.collection.immutable.Map[String, V1] = m + kv
    def -(key: String): scala.collection.immutable.Map[String, String] = m - key
    def iterator: Iterator[(String, String)] = m.iterator
  }
}

object App {

  import MapHelpers._

  def testMapImplicit(): Unit = {
    val myMap: MapWithIntKey = Map("foo" -> "bar", "faz" -> "baz")
    println("result - number match ", myMap.get("foo1"))
    println("result - exact match  ", myMap.get("foo"))
  }

}

App.testMapImplicit()

Working Scastie