Как читать неизменяемые структуры данных из файла в Scala - PullRequest
5 голосов
/ 08 февраля 2010

У меня есть структура данных, состоящая из заданий, каждая из которых содержит набор задач. Данные задания и задания определены в файлах, подобных этим:

jobs.txt:
JA
JB
JC

tasks.txt:
JB  T2
JA  T1
JC  T1
JA  T3
JA  T2
JB  T1 

Процесс создания объектов следующий:
- читать каждое задание, создавать его и хранить по id
- прочитать задание, получить задание по идентификатору, создать задание, сохранить задание в задании

Как только файлы прочитаны, эта структура данных никогда не изменяется. Поэтому я хотел бы, чтобы задачи внутри заданий сохранялись в неизменяемом наборе. Но я не знаю, как сделать это эффективно. (Примечание: неизменяемые задания для хранения карт можно оставить неизменными)

Вот упрощенная версия кода:

class Task(val id: String) 

class Job(val id: String) {
    val tasks = collection.mutable.Set[Task]() // This sholud be immutable
}

val jobs = collection.mutable.Map[String, Job]() // This is ok to be mutable

// read jobs
for (line <- io.Source.fromFile("jobs.txt").getLines) { 
    val job = new Job(line.trim)
    jobs += (job.id -> job)
}

// read tasks
for (line <- io.Source.fromFile("tasks.txt").getLines) {
    val tokens = line.split("\t")
    val job = jobs(tokens(0).trim)
    val task = new Task(job.id + "." + tokens(1).trim)
    job.tasks += task
}

Заранее благодарим за каждое предложение!

Ответы [ 4 ]

4 голосов
/ 08 февраля 2010

Самый эффективный способ сделать это - прочитать все в изменяемые структуры и затем в конце преобразовать в неизменяемые, но это может потребовать много избыточного кодирования для классов с большим количеством полей. Поэтому вместо этого рассмотрите возможность использования того же шаблона, который использует базовая коллекция: задание с новой задачей - это новое задание .

Вот пример, который не беспокоит даже чтение списка вакансий - он выводит его из списка задач. (Это пример, который работает в 2.7.x; последние версии 2.8 используют «Source.fromPath» вместо «Source.fromFile».)

object Example {
  class Task(val id: String) {
    override def toString = id
  }

  class Job(val id: String, val tasks: Set[Task]) {
    def this(id0: String, old: Option[Job], taskID: String) = {
      this(id0 , old.getOrElse(EmptyJob).tasks + new Task(taskID))
    }
    override def toString = id+" does "+tasks.toString
  }
  object EmptyJob extends Job("",Set.empty[Task]) { }

  def read(fname: String):Map[String,Job] = {
    val map = new scala.collection.mutable.HashMap[String,Job]()
    scala.io.Source.fromFile(fname).getLines.foreach(line => {
      line.split("\t") match {
        case Array(j,t) => {
          val jobID = j.trim
          val taskID = t.trim
          map += (jobID -> new Job(jobID,map.get(jobID),taskID))
        }
        case _ => /* Handle error? */
      }
    })
    new scala.collection.immutable.HashMap() ++ map
  }
}

scala> Example.read("tasks.txt")
res0: Map[String,Example.Job] = Map(JA -> JA does Set(T1, T3, T2), JB -> JB does Set(T2, T1), JC -> JC does Set(T1))

Альтернативный подход будет читать список заданий (создание заданий как новое задание (jobID, Set.empty [Task])), а затем обрабатывать состояние ошибки, когда список заданий содержит запись, которой нет в задании список. (Вам все равно придется обновлять карту списка заданий каждый раз, когда вы читаете новое задание.)

2 голосов
/ 08 февраля 2010

Я чувствовал изменения, чтобы он работал на Scala 2.8 (в основном, fromPath вместо fromFile и () после getLines). Это может быть использование нескольких функций Scala 2.8, в особенности groupBy. Возможно, toSet, но это легко адаптировать на 2.7.

У меня нет файлов для тестирования, но я изменил этот материал с val на def, и сигнатуры типов, по крайней мере, совпадают.

class Task(val id: String)  
class Job(val id: String, val tasks: Set[Task])

// read tasks 
val tasks = (
  for {
    line <- io.Source.fromPath("tasks.txt").getLines().toStream
    tokens = line.split("\t") 
    jobId = tokens(0).trim
    task = new Task(jobId + "." + tokens(1).trim) 
  } yield jobId -> task
).groupBy(_._1).map { case (key, value) => key -> value.map(_._2).toSet }

// read jobs 
val jobs = Map() ++ (
  for {
    line <- io.Source.fromPath("jobs.txt").getLines()
    job = new Job(line.trim, tasks(line.trim))
  } yield job.id -> job
)
1 голос
/ 08 февраля 2010

Вы всегда можете отложить создание объекта до тех пор, пока не получите все данные, считанные из файла, например:

case class Task(id: String) 
case class Job(id: String, tasks: Set[Task])

import scala.collection.mutable.{Map,ListBuffer}
val jobIds = Map[String, ListBuffer[String]]()

// read jobs
for (line <- io.Source.fromFile("jobs.txt").getLines) { 
    val job = line.trim
    jobIds += (job.id -> new ListBuffer[String]())
}

// read tasks
for (line <- io.Source.fromFile("tasks.txt").getLines) {
    val tokens = line.split("\t")
    val job = tokens(0).trim
    val task = job.id + "." + tokens(1).trim
    jobIds(job) += task
}

// create objects
val jobs = jobIds.map { j =>
    Job(j._1, Set() ++ j._2.map { Task(_) })
}

Чтобы иметь дело с большим количеством полей, вы можете (с некоторыми усилиями) создать изменяемую версию ваших неизменных классов, используемых для построения. Затем преобразуйте при необходимости:

case class Task(id: String)
case class Job(val id: String, val tasks: Set[Task])
object Job {
    class MutableJob {
        var id: String = ""
        var tasks = collection.mutable.Set[Task]()
        def immutable = Job(id, Set() ++ tasks)
    }
    def mutable(id: String) = {
        val ret = new MutableJob
        ret.id = id
        ret
    }
}

val mutableJobs = collection.mutable.Map[String, Job.MutableJob]() 

// read jobs
for (line <- io.Source.fromFile("jobs.txt").getLines) { 
    val job = Job.mutable(line.trim)
    jobs += (job.id -> job)
}

// read tasks
for (line <- io.Source.fromFile("tasks.txt").getLines) {
    val tokens = line.split("\t")
    val job = jobs(tokens(0).trim)
    val task = Task(job.id + "." + tokens(1).trim)
    job.tasks += task
}

val jobs = for ((k,v) <- mutableJobs) yield (k, v.immutable)
0 голосов
/ 08 февраля 2010

Один из вариантов здесь - иметь некоторый изменяемый, но переходный класс конфигуратора в соответствии с указанным выше MutableMap, но затем передать его в некоторой неизменной форме вашему фактическому классу:

val jobs: immutable.Map[String, Job] = {
  val mJobs = readMutableJobs
  immutable.Map(mJobs.toSeq: _*)
}

Тогда, конечно, вы можете реализовать readMutableJobs по линиям, которые вы уже закодировали

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