2010-02-08 7 views
5

Tengo una estructura de datos compuesta por trabajos que contienen cada uno un conjunto de tareas. Tanto los datos de empleo y de tareas se definen en archivos como estos:cómo leer estructuras de datos inmutables desde un archivo en scala

jobs.txt: 
JA 
JB 
JC 

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

El proceso de creación de objetos es la siguiente:
- leer cada puesto de trabajo, crear y almacenarlo por id
- tarea de lectura, recuperar empleo por id, crear tarea, almacenar tarea en el trabajo

Una vez que se leen los archivos, esta estructura de datos nunca se modifica. Entonces me gustaría que las tareas dentro de los trabajos se almacenen en un conjunto inmutable. Pero no sé cómo hacerlo de manera eficiente. (Nota: el mapa inmutable almacenamiento de trabajos se puede dejar inmutable)

Aquí es una versión simplificada del código:

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 
} 

Gracias de antemano por todas las sugerencias!

Respuesta

4

La forma más eficiente de hacer esto sería para leer todo en estructuras mutables y luego convertir a los inmutables al final, pero esto podría requerir una gran cantidad de codificación redundante para las clases con muchos campos Por lo tanto, considere usar el mismo patrón que utiliza la colección subyacente: un trabajo con una nueva tarea es un nuevo trabajo .

Aquí hay un ejemplo que ni siquiera molesta al leer la lista de trabajos: la infiere de la lista de tareas. (Este es un ejemplo que funciona bajo 2.7.x, las versiones recientes de 2.8 uso "Source.fromPath" en lugar de "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)) 

Un enfoque alternativo sería leer la lista de tareas (la creación de puestos de trabajo como nuevo trabajo (ID de trabajo, Establecer.empty [Tarea])), y luego manejar la condición de error de cuando la lista de tareas contenía una entrada que no estaba en la lista de trabajos. (Usted todavía tiene que actualizar el mapa lista de trabajos cada vez que se lee en una nueva tarea.)

+0

Me gusta este enfoque. Pero simplemente escribiría un método 'addTask' que devuelve un nuevo' Job' con los mismos datos, más la nueva tarea. Cambiaría la lógica un poco, pero, tal como está, 'Job' parece saber demasiado sobre cómo se va a inicializar. :-) –

+0

Lo hice de esta manera para resaltar el reemplazo del trabajo anterior por uno nuevo, que me pareció ser el concepto clave aquí. Pero estoy de acuerdo en que un 'addTask' en algún lugar sería mejor. Hay muchos lugares por los que uno podría pelear (¿debería tomar una 'Opción [Trabajo]', o ser un cierre alrededor del mapa mutable?). –

+0

Gracias, me gusta esta solución para la idea de que Job cree el nuevo trabajo (por constructor o método addTask). Todavía soy muy nuevo en Scala (vengo de Java) y todavía no estoy seguro si, en un caso como este, la inmutabilidad vale la pena el costo de tener muchos objetos creados, ya que para mí el rendimiento es bastante importante (en el caso real tengo más las 2 clases, con enlaces complejos entre ellas y miles de objetos). –

0

Una opción aquí es tener algún mutable pero transitoria clase configurador lo largo de las líneas de la MutableMap anteriormente, pero luego pasar esto a través de alguna forma inmutable a su clase real:

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

Luego de Por supuesto, puede implementar readMutableJobs siguiendo las líneas que ya ha codificado

+0

Lo siento was'n lo suficientemente clara: el empleo Mapa está bien ser mutable, son las tareas encomendadas en el único trabajo que debe ser inmutable (He editado mi pregunta) –

+0

Creo que es justo decir ¡que podría hacer que el mismo enfoque funcione en las tareas mutables/inmutables, ya que ha funcionado en el mapa de trabajos! Por ejemplo, tener un constructor 'Job' toma una copia inmutable de las tareas tal como se creó –

1

Siempre puede retrasar la creación del objeto hasta que tenga todos los datos leídos desde el archivo, como por ejemplo:

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(_) }) 
} 

Para hacer frente a más campos, usted podría (con un poco de esfuerzo) hacer una versión mutable de sus clases inmutables, utilizados para la construcción. A continuación, convertir, según sea necesario:

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

Gracias. Su solución está bien para el ejemplo que publiqué, pero en el caso real tanto Job como Task tienen más campos que solo ids. Por ejemplo, Job también tiene una fecha de vencimiento (Fecha) y Tarea tiene una longitud (Int), y así sucesivamente ... –

+0

Gracias de nuevo, esta fue la solución en la que pensé cuando me enfrenté al problema por primera vez. Sin embargo, en mi opinión, requiere demasiado código adicional que significa más errores, mantenimiento, ... –

1

hice una sensación cambios para que se ejecute en Scala 2.8 (en su mayoría, fromPath en lugar de fromFile y () después getLines) . Puede estar utilizando algunas características de Scala 2.8, más notablemente groupBy. Probablemente toSet también, pero ese es fácil de adaptar en 2.7.

No tengo los archivos para probarlo, pero cambié estas cosas de val a def, y las firmas de tipo, al menos, coinciden.

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 
) 
Cuestiones relacionadas