我有一个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)
}
有没有更好的方法可以做到这一点,而不使用可变数据结构?
答案 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.Statement
。 groupBy
部分可能需要更多的爱才能看起来很好。
修改:添加了请求的更改。
答案 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
(也没有任何好处),并且具有线性查找成本。我想你可以用类似于模仿简单搜索树的方式实现一些东西。
(*)我在这里谈论概念。实际上,价值共享之类的东西可以实现例如可变列表构造,没有内存开销。