从数据库表中填充Scala不可变映射

时间:2011-02-07 22:12:58

标签: scala functional-programming

我有一个SQL数据库表,其结构如下:

create table category_value (
  category varchar(25),
  property varchar(25)
);

我想将其读入Scala Map[String, Set[String]],其中地图中的每个条目都是同一类别中所有属性值的集合。 我想以“功能”方式进行,没有可变数据(数据库结果集除外)。

继Clojure loop构造之后,我想出了以下内容:

def fillMap(statement: java.sql.Statement): Map[String, Set[String]] = {
    val resultSet = statement.executeQuery("select category, property from category_value")

    @tailrec
    def loop(m: Map[String, Set[String]]): Map[String, Set[String]] = {
      if (resultSet.next) {
        val category = resultSet.getString("category")
        val property = resultSet.getString("property")
        loop(m + (category -> m.getOrElse(category, Set.empty)))
      } else m
    }

    loop(Map.empty)
}

有没有更好的方法可以做到这一点,而不使用可变数据结构?

5 个答案:

答案 0 :(得分:8)

如果您愿意,可以尝试一下

def fillMap(statement: java.sql.Statement): Map[String, Set[String]] = {
  val resultSet = statement.executeQuery("select category, property from category_value")
  Iterator.continually((resultSet, resultSet.next)).takeWhile(_._2).map(_._1).map{ res =>
    val category = res.getString("category")
    val property = res.getString("property")
    (category, property)
  }.toIterable.groupBy(_._1).mapValues(_.map(_._2).toSet)
}

未经测试,因为我没有合适的sql.StatementgroupBy部分可能需要更多的爱才能看起来很好。

修改:添加了请求的更改。

答案 1 :(得分:5)

这个问题有两个部分。

将数据从数据库中取出并放入行列表中。

我会使用Spring SimpleJdbcOperations进行数据库访问,这样即使在幕后更改ResultSet,事物也至少会显示功能。

首先,进行一些简单的转换,让我们使用闭包来映射每一行:

implicit def rowMapper[T<:AnyRef](func: (ResultSet)=>T) = 
  new ParameterizedRowMapper[T]{
    override def mapRow(rs:ResultSet, row:Int):T = func(rs)
  }

然后让我们定义一个数据结构来存储结果。 (你可以使用一个元组,但是定义我自己的case类有利于对事物名称稍微清楚一些。)

case class CategoryValue(category:String, property:String)

现在从数据库中选择

val db:SimpleJdbcOperations = //get this somehow
val resultList:java.util.List[CategoryValue] = 
  db.query("select category, property from category_value",
    { rs:ResultSet => CategoryValue(rs.getString(1),rs.getString(2)) } )

将行列表中的数据转换为您实际需要的格式

import scala.collection.JavaConversions._
val result:Map[String,Set[String]] =
  resultList.groupBy(_.category).mapValues(_.map(_.property).toSet)

(你可以省略类型注释。我已经将它们包含在内,以明确发生了什么。)

答案 2 :(得分:1)

Builders就是为此而建的。通过所需的集合类型伴侣获取一个,例如HashMap.newBuilder[String, Set[String]]

答案 3 :(得分:1)

此解决方案与我的其他解决方案基本相同,但它不使用Spring,将ResultSet转换为某种列表的逻辑比Debilski的解决方案简单。

def streamFromResultSet[T](rs:ResultSet)(func: ResultSet => T):Stream[T] = {
   if (rs.next())
      func(rs) #:: streamFromResultSet(rs)(func)
   else
      rs.close()
      Stream.empty
}

def fillMap(statement:java.sql.Statement):Map[String,Set[String]] = {
   case class CategoryValue(category:String, property:String)

   val resultSet = statement.executeQuery("""
        select category, property from category_value
   """)

   val queryResult = streamFromResultSet(resultSet){rs =>
      CategoryValue(rs.getString(1),rs.getString(2))
   }

   queryResult.groupBy(_.category).mapValues(_.map(_.property).toSet)
}

答案 4 :(得分:1)

我能想到的只有一种方法,不包括可变状态或广泛复制*。这实际上是我在第一学期学习CS时学到的一种非常基本的技巧。在这里,从数据库中抽象出来:

def empty[K,V](k : K) : Option[V] = None

def add[K,V](m : K => Option[V])(k : K, v : V) : K => Option[V] = q => {
  if ( k == q ) {
    Some(v)
  }
  else {
    m(q)
  }
}

def build[K,V](input : TraversableOnce[(K,V)]) : K => Option[V] = {
  input.foldLeft(empty[K,V]_)((m,i) => add(m)(i._1, i._2))
}

用法示例:

val map = build(List(("a",1),("b",2)))

println("a " + map("a"))
println("b " + map("b"))
println("c " + map("c"))

> a Some(1)
> b Some(2)
> c None

当然,生成的函数没有类型Map(也没有任何好处),并且具有线性查找成本。我想你可以用类似于模仿简单搜索树的方式实现一些东西。

(*)我在这里谈论概念。实际上,价值共享之类的东西可以实现例如可变列表构造,没有内存开销。