Data structures 如何在scala中从文件中读取不可变的数据结构

Data structures 如何在scala中从文件中读取不可变的数据结构,data-structures,scala,immutability,mutable,Data Structures,Scala,Immutability,Mutable,我有一个由作业组成的数据结构,每个作业包含一组任务。作业和任务数据都在如下文件中定义: jobs.txt: JA JB JC tasks.txt: JB T2 JA T1 JC T1 JA T3 JA T2 JB T1 创建对象的过程如下: -读取每个作业,创建并按id存储它 -读取任务、按id检索作业、创建任务、将任务存储在作业中 一旦文件被读取,这个数据结构就永远不会被修改。所以我希望作业中的任务存储在一个不可变的集合中。但我不知道如何有效地做到这一点。(注意:存储作业的不

我有一个由作业组成的数据结构,每个作业包含一组任务。作业和任务数据都在如下文件中定义:

jobs.txt:
JA
JB
JC

tasks.txt:
JB  T2
JA  T1
JC  T1
JA  T3
JA  T2
JB  T1 
创建对象的过程如下:
-读取每个作业,创建并按id存储它
-读取任务、按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
}
类任务(valid:String)
类作业(val id:字符串){
val tasks=collection.mutable.Set[Task]()//此任务不可变
}
val jobs=collection.mutable.Map[String,Job]()//这可以是可变的
//阅读作业
用于(生产线作业)
}
//阅读任务

对于(line来说,这里的一个选项是沿着上面的
MutableMap
的行使用一些可变但暂时的configurer类,然后以某种不可变的形式将其传递给实际的类:

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

当然,您可以按照已经编码的行实现
readMutableJobs
您可以始终延迟对象创建,直到从文件中读入所有数据,如:

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类任务(id:String)
案例类作业(id:字符串,任务:设置[任务])
导入scala.collection.mutable.{Map,ListBuffer}
val jobIds=Map[String,ListBuffer[String]]()
//阅读作业
用于(行新ListBuffer[String]())
}
//阅读任务
为了(线路)
作业(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)
case类任务(id:String)
案例类作业(val id:字符串,val任务:设置[任务])
对象作业{
类可变作业{
变量id:String=“”
var tasks=collection.mutable.Set[Task]()
def immutable=作业(id,Set()++任务)
}
def可变(id:字符串)={
val ret=新的可变作业
ret.id=id
ret
}
}
val mutableJobs=collection.mutable.Map[字符串,Job.MutableJob]()
//阅读作业
用于(生产线作业)
}
//阅读任务

对于(行< P>)最有效的方法是将所有的内容读入可变结构,然后在最后转换为不可变的结构,但是这可能需要对具有大量字段的类进行大量冗余编码。因此,请考虑使用基础集合使用的相同模式:具有新任务的作业是一个新作业。 下面的示例甚至不需要阅读作业列表——它是从任务列表中推断出来的。(这是一个在2.7.x下工作的示例;2.8的最新版本使用“Source.fromPath”而不是“Source.fromFile”。)


另一种方法是读取作业列表(将作业创建为新作业(jobID,Set.empty[Task]),然后处理当任务列表包含不在作业列表中的条目时出现的错误情况。(每次读取新任务时,您仍然需要更新作业列表映射)。

我做了一些更改,使其在Scala 2.8上运行(大多数情况下,
fromPath
而不是
fromFile
,以及
getLines
之后的
()
)。它可能使用了一些Scala 2.8特性,最显著的是
groupBy
。也可能
设置
,但这一特性很容易在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
)
类任务(valid:String)
类作业(val id:字符串,val任务:设置[任务])
//阅读任务
val任务=(
为了{
行任务
).groupBy(u._1).map{case(key,value)=>key->value.map(u._2).toSet}
//阅读作业
val jobs=Map()++(
为了{
直线作业
)

很抱歉我不够清楚:作业映射可以是可变的,单个作业中设置的任务应该是不可变的(我已经编辑了我的问题)我认为可以公平地说,您可以使相同的方法在可变/不可变任务上工作,就像它在作业地图上工作一样!例如,让一个
作业
构造函数在创建任务时获取任务的不可变副本。对于我发布的示例,您的解决方案是可以的,但在实际情况下,作业和任务都有更多字段不仅仅是ID。例如,作业还有一个截止日期(date),任务有一个长度(Int),等等…再次感谢,这是我第一次面对这个问题时想到的解决方案。然而,在我看来,它需要太多额外的代码,这意味着更多的bug、维护……我喜欢这种方法。但我只需要编写一个
addTask
方法,返回一个新的
作业,并使用相同的数据,再加上新的task、 它会稍微改变逻辑,但实际上,
Job
似乎对如何初始化它知道得太多了。:-)我这样做是为了强调用新工作替换旧工作,这在我看来是这里的关键概念。但我同意,
addTask
在某个地方会更好。有很多地方可以争论(是需要一个
选项[job]
,还是围绕可变地图关闭?)。谢谢,我喜欢这个解决方案,因为它让作业创建新作业(通过构造函数或addTask方法)。我对scala还是很陌生(我来自java),我还不确定在这种情况下,不可变性是否值得为创建多个对象付出代价,因为性能对我来说非常重要(在实际情况中,我有更多的两个类,它们之间有复杂的链接和数千个对象)。如果性能很重要,请读入可变结构,然后切换到不可变结构。例如,您可以有一个
jobUnderstruction extends Job
,它有一个额外的
taskAccumulator
可变,还有一个
toJo
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
)