Заполнение неизменяемой карты Scala из таблицы базы данных - PullRequest
4 голосов
/ 08 февраля 2011

У меня есть таблица базы данных 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 ]

8 голосов
/ 08 февраля 2011

Если хотите, вы можете попробовать что-нибудь около

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, возможно, потребуется немного любви, чтобы выглядеть красиво.

Редактировать : добавлены запрошенные изменения.

5 голосов
/ 08 февраля 2011

Эта проблема состоит из двух частей.

Вывод данных из базы данных в список строк.

Я бы использовал 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 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)

(Вы можете опустить аннотации типов. Я включил их, чтобы прояснить, что происходит.)

1 голос
/ 08 февраля 2011

Есть только один подход, который я могу придумать, который не включает ни изменяемое состояние, ни обширное копирование *.На самом деле это очень базовая техника, которую я изучил в первый семестр изучения 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 (ни каких-либо ее преимуществ)и имеет линейные затраты на поиск.Я думаю, вы могли бы реализовать что-то похожим образом, который имитирует простые деревья поиска.

(*) Я говорю здесь о понятиях.В действительности такие вещи, как совместное использование значений, могут включать, например, изменяемые конструкции списка без перегрузки памяти.

1 голос
/ 08 февраля 2011

Это решение в основном совпадает с другим моим решением, но оно не использует Spring, и логика преобразования ResultSet в какой-то список более проста, чем решение Дебилски.

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)
}
1 голос
/ 08 февраля 2011

Строители созданы для этой цели.Получите его через собеседника нужного типа, например, HashMap.newBuilder[String, Set[String]].

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...