2010-02-08 3 views
5

J'ai une structure de données faite de Jobs contenant chacun un ensemble de tâches. Les deux données de travail et tâches sont définies dans des fichiers comme ceux-ci:comment lire des structures de données immuables à partir d'un fichier dans scala

jobs.txt: 
JA 
JB 
JC 

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

Le processus de création d'objets est la suivante:
- lire chaque tâche, créez et stocker par id
- tâche de lecture, récupérer travail par id, créer une tâche, stocker une tâche dans le job

Une fois les fichiers lus, cette structure de données n'est jamais modifiée. Donc, je voudrais que les tâches dans les emplois seraient stockés dans un ensemble immuable. Mais je ne sais pas comment le faire de manière efficace. (Note: la carte immuable stockage des emplois peut être laissé immuable)

Voici une version simplifiée du code:

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 
} 

Merci d'avance pour toute suggestion!

Répondre

4

La façon la plus efficace de le faire serait de lire tout dans les structures mutables et ensuite convertir en les immuables à la fin, mais cela peut nécessiter beaucoup de codage redondant pour les classes avec beaucoup de champs. Donc, au lieu de cela, pensez à utiliser le même modèle que la collection sous-jacente utilise: un travail avec une nouvelle tâche est un nouveau travail.

Voici un exemple qui ne dérange même pas la lecture de la liste des tâches - il l'infère de la liste des tâches. (Ceci est un exemple qui fonctionne sous 2.7.x, les versions récentes de 2,8 utilisation « Source.fromPath » au lieu 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)) 

Une autre approche lirait la liste des tâches (création d'emplois comme nouveau Job (jobID, Set.empty [Tâche])), puis gérer la condition d'erreur lorsque la liste des tâches contenait une entrée qui n'était pas dans la liste des travaux. (Vous auriez encore besoin de mettre à jour la carte de liste des tâches à chaque fois que vous avez lu dans une nouvelle tâche.)

+0

J'aime cette approche. Mais je voudrais juste écrire une méthode 'addTask' qui retourne un nouveau' Job' avec les mêmes données, plus la nouvelle tâche. Cela changerait un peu la logique, mais, comme c'est le cas, Job semble trop savoir comment il va être initialisé. :-) –

+0

Je l'ai fait de cette façon pour mettre en évidence le remplacement de l'ancien travail par un nouveau, qui m'a semblé être le concept clé ici. Mais je suis d'accord qu'un 'addTask' quelque part serait mieux. Il y a beaucoup d'endroits pour lesquels on pourrait argumenter (devrait-il prendre un 'Option [Job]', ou être une fermeture autour de la carte mutable?). –

+0

Merci, j'aime cette solution pour l'idée du Job qui crée le nouveau Job (par constructeur ou par addTask). Je suis encore très jeune à scala (je viens de java) et je ne suis pas encore sûr si, dans un cas comme celui-ci, l'immutabilité vaut le coût d'avoir beaucoup de création d'objet puisque pour moi la performance est assez importante le cas réel j'ai plus les 2 classes, avec des liens complexes entre eux et des milliers d'objets). –

0

Une option ici est d'avoir une mutable mais classe transitoire de configurateur le long des lignes du MutableMap ci-dessus mais passer cela par une certaine forme immuable à votre classe réelle:

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

Alors Bien sûr, vous pouvez mettre en œuvre readMutableJobs le long des lignes que vous avez déjà codé

+0

Désolé, je Was'n assez clair: l'emploi La carte est ok pour être mutable, ce sont les tâches précisées dans le seul emploi qui devrait être immuable (je l'ai modifié ma question) –

+0

Je pense qu'il est juste de dire que vous pourriez faire la même approche travailler sur les tâches mutables/immuables comme il a travaillé sur la carte des emplois! Par exemple, avoir un constructeur 'Job' prendre une copie immuable des tâches comme il est créé –

1

Vous pouvez toujours retarder la création de l'objet jusqu'à ce que toutes les données lues dans le fichier, comme:

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

Pour faire face à d'autres champs, vous pouvez (avec un peu d'effort) faire une version mutable de vos classes immuables, utilisées pour la construction. Ensuite, convertir au besoin:

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

Merci. Votre solution est correcte pour l'exemple que j'ai posté, mais dans le cas réel, Job et Task ont ​​plus de champs que de simples identifiants. Par exemple Job a aussi une date d'échéance (Date) et Task a une longueur (Int), et ainsi de suite ... –

+0

Merci encore, c'était la solution à laquelle j'ai pensé quand j'ai fait face au problème pour la première fois. Cependant, à mon avis, cela nécessite trop de code supplémentaire ce qui signifie plus de bugs, de maintenance, ... –

1

Je l'ai fait une sensation des changements pour l'exécuter sur Scala 2.8 (la plupart du temps, fromPath au lieu de fromFile et () après getLines) . Il peut utiliser quelques fonctionnalités de Scala 2.8, notamment groupBy. Probablement toSet aussi, mais celui-là est facile à adapter sur 2.7.

Je n'ai pas les fichiers pour le tester, mais j'ai changé ce truc de val à def, et les signatures de type, au moins, correspondent.

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 
) 
Questions connexes