2010-02-08 19 views
5

Ho una struttura dati composta da lavori ciascuno contenente un insieme di attività. Sia i dati di lavoro e di attività vengono definite in file come questi:come leggere le strutture di dati immutabili dal file in scala

jobs.txt: 
JA 
JB 
JC 

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

Il processo di creazione degli oggetti è la seguente:
- leggere ogni lavoro, creare e conservarla per ID
- leggere compito, recuperare lavoro per id, creare attività, memorizzare attività nel lavoro

Una volta letti i file, questa struttura dati non viene mai modificata. Quindi vorrei che le attività all'interno dei lavori fossero archiviate in un set immutabile. Ma non so come farlo in modo efficiente. (Nota: la mappa immutabile memorizzazione di posti di lavoro può essere lasciato immutabile)

Ecco una versione semplificata del codice:

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 
} 

Grazie in anticipo per ogni suggerimento!

risposta

4

Il modo più efficace per farlo sarebbe quello di leggere tutto in strutture mutevoli e poi convertire in quelli immutabili, alla fine, ma questo potrebbe richiedere un sacco di codifica ridondante per le classi con molti campi Quindi, invece, considera di utilizzare lo stesso modello utilizzato dalla raccolta sottostante: un lavoro con una nuova attività è un nuovo lavoro .

Ecco un esempio che non si preoccupa nemmeno di leggere l'elenco dei lavori: lo deduce dall'elenco delle attività. (Questo è un esempio che funziona sotto 2.7.x; le versioni recenti di 2,8 utilizzo "Source.fromPath" invece di "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 approccio alternativo potrebbe leggere l'elenco dei lavori (la creazione di posti di lavoro come nuovo Job (jobID, Set.empty [Task])), quindi gestire la condizione di errore di quando l'elenco delle attività conteneva una voce che non era presente nell'elenco dei lavori. (Si avrebbe ancora bisogno di aggiornare la mappa lista lavoro ogni volta che si legge in una nuova attività.)

+0

Mi piace questo approccio. Ma scriverei semplicemente un metodo 'addTask' che restituisce un nuovo' Lavoro' con gli stessi dati, oltre alla nuova attività. Cambierà un po 'la logica, ma, come è ovvio, 'Job' sembra sapere troppo su come sarà inizializzato. :-) –

+0

L'ho fatto in questo modo per evidenziare la sostituzione del vecchio lavoro con uno nuovo, che mi sembrava il concetto chiave qui. Ma sono d'accordo che un 'addTask' da qualche parte sarebbe meglio. Ci sono molti posti in cui si potrebbe obiettare (dovrebbe prendere un 'Opzione [Lavoro]', o essere una chiusura attorno alla mappa mutevole?). –

+0

Grazie, mi piace questa soluzione per l'idea del lavoro che crea il nuovo lavoro (per costruttore o metodo addTask). Sono ancora molto nuovo a scala (vengo da java) e non sono ancora sicuro se, in un caso come questo, l'immutabilità valga il costo di avere molti oggetti creati poiché per me le prestazioni sono abbastanza importanti (in nel caso reale ho più le 2 classi, con collegamenti complessi tra loro e migliaia di oggetti). –

0

Una possibilità è quello di avere un po 'mutevole ma transitoria classe configuratore lungo le linee del MutableMap sopra, ma poi passare questo attraverso in qualche forma immutabile alla classe attuale:

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

Poi Naturalmente puoi implementare readMutableJobs lungo le linee che hai già codificato

+0

Mi dispiace was'n abbastanza chiaro: il lavoro Map è ok per essere mutabile, sono i compiti all'interno del singolo lavoro che dovrebbe essere immutabile (Ho modificato la mia domanda) –

+0

Penso che sia giusto dire che potresti far funzionare lo stesso approccio sui compiti mutevoli/immutabili in quanto ha funzionato sulla mappa dei lavori! Ad esempio, avere un costruttore 'Job' prende una copia immutabile delle attività mentre viene creata –

1

Puoi sempre ritardare la creazione dell'oggetto finché non hai tutti i dati letti dal file, ad esempio:

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

Per affrontare più campi, è possibile (con qualche sforzo) fare una versione mutevole delle classi immutabili, utilizzati per la costruzione. Poi, convertire in base alle esigenze:

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

Grazie. La soluzione è ok per l'esempio che ho postato, ma nel caso reale sia Lavoro che Attività hanno più campi che solo ID. Ad esempio, Job ha anche una data di scadenza (Date) e Task ha una lunghezza (Int), e così via ... –

+0

Grazie ancora, questa è stata la soluzione a cui ho pensato quando ho affrontato il problema per la prima volta. Tuttavia, a mio parere, richiede troppo codice extra che significa più bug, manutenzione, ... –

1

ho fatto tatto modifiche per l'esecuzione su Scala 2.8 (per lo più, fromPath invece di fromFile, e () dopo getLines) . Potrebbe utilizzare alcune funzioni di Scala 2.8, in particolare groupBy. Probabilmente anche toSet, ma è facile adattarsi alla 2.7.

non ho i file per testare, ma ho cambiato questa roba val-def, e il tipo di firme, per lo meno, partita.

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 
) 
Problemi correlati