2010-11-19 13 views
5

Sto cercando di implementare un default valued map e mi piacerebbe filtri, mappe, ecc. Su un DefaultingMap per produrre anche un DefaultingMap quando possibile. Ecco la mia implementazione iniziale:Implementare una scala di scala in modo che la mappa, il filtro, ecc. Producano il tipo corretto

class DefaultingMap[K, V](defaultValue: => V) 
extends mutable.HashMap[K, V] 
with mutable.MapLike[K, V, DefaultingMap[K, V]] { 

    override def empty = new DefaultingMap[K, V](defaultValue) 

    override def default(key: K): V = {     
    val result = this.defaultValue 
    this(key) = result 
    result            
    } 
} 

ricevo oggetti di tipo DefaultingMap quando uso filter, ma non quando uso map:

scala> val counter = new DefaultingMap[Char, Int](0) 
counter: DefaultingMap[Char,Int] = Map() 

scala> for (c <- "ababcbbb") counter(c) += 1 

scala> counter.filter{case (k, v) => v > 1} 
res1: DefaultingMap[Char,Int] = Map((a,2), (b,5)) 

scala> counter.map{case (k, v) => (k, v * 2)} 
res2: scala.collection.mutable.HashMap[Char,Int] = Map((a,4), (c,2), (b,10)) 

La differenza tra questi due metodi sembra essere che map prende un implicito CanBuildFrom. Quindi ho capito che ho bisogno di avere unda qualche parte per fornire il CanBuildFrom. La mia prima intuizione è stato quello di fare ciò che è fatto in HashMap:

object DefaultingMap extends generic.MutableMapFactory[DefaultingMap] { 

    def empty[K, V]: DefaultingMap[K, V] = // Not possible! 

    implicit def canBuildFrom[K, V]: 
    generic.CanBuildFrom[Coll, (K, V), DefaultingMap[K, V]] = 
     new MapCanBuildFrom[K, V] 
} 

Credo che questo sarebbe farlo per compilare, ma questo approccio non funziona perché è impossibile definire il metodo empty - è necessario conoscere ciò che il defaultValue dovrebbe essere. Se potessi definire lo CanBuildFrom nella classe stessa, anziché l'oggetto associato, sarei a posto perché lo defaultValue è disponibile lì.

Come posso farlo funzionare?

risposta

5

mappe Mutevoli sono Builder s a Scala, in modo che il MapFactory per default prende una mappa vuota del tipo in questione per ottenere un costruttore.

Se si dispone di regole di creazione di mappe personalizzate, una delle cose che si possono fare è definire la fabbrica personalizzata simile a collection.generic.MapFactory. Dovresti definirlo in un modo simile come lì, ma rendere entrambi il metodo empty e il metodo newBuilder per un argomento aggiuntivo per lo defaultValue.

Qualcosa sulla falsariga di (se leggete di più sulla Scala 2,8 collezioni API in altro collegamento suggerito, ci si accorge che non c'è bisogno di implementare compagno generica Oggetti per le mappe):

import collection._                  


class DefaultingMap[K, V](val defaultValue: V)                  
extends mutable.HashMap[K, V]                      
with mutable.MapLike[K, V, DefaultingMap[K, V]] {                 

    override def empty = new DefaultingMap(defaultValue)                

}                             


object DefaultingMap {                        
    def newBuilder[K, V](d: V): DefaultingMap[K, V] = new DefaultingMap[K, V](d)          

    implicit def canBuildFrom[K, V] =                     
    new generic.CanBuildFrom[DefaultingMap[K, V], (K, V), DefaultingMap[K, V]] {          
     def apply(from: DefaultingMap[K, V]) = newBuilder[K, V](from.defaultValue)          
     def apply() = error("unsupported default apply")                
    }                            
}                             


object Main {                          
    def main(args: Array[String]) {                     
    println((new DefaultingMap[Int, Int](5)).defaultValue)               
    println(((new DefaultingMap[Int, Int](5)).map(x => x)).defaultValue)            
    }                             
} 

Stampe:

$ scalac defaulting.scala 
$ scala Main 
5 
5 

lo ammetto, ancora, questo non risolve il problema per il senza parametri apply.

+0

Il punto su MutableMaps che già è Builder è bello - questo semplifica il codice in alcuni punti. Per quanto riguarda l'applicazione senza parametri, sai quando viene richiamato? Forse se è raro non sono troppo preoccupato per questo. – Steve

+0

BTW, penso che potrebbe essere più chiaro se hai abbandonato il metodo 'newBuilder' e hai appena creato la DefaultingMap direttamente. – Steve

+0

Il parametro 'apply' viene richiamato in' breakOut', che è l'unico posto, per quanto ne so ... Vedi l'oggetto del pacchetto di raccolta. E hai ragione su 'newBuilder'. Tuttavia, lascerei comunque la definizione del metodo 'newBuilder' nel companion. Se la 'Mappa' avesse compagni che funzionassero allo stesso modo di' Seq', sarebbe poi diventato utile. Inoltre, li renderebbe conformi ai companion 'MapFactory' standard che hanno il' newBuilder'. – axel22

0

Il Scala 2.8 Collections API è un documento molto bello e mi sembra di ricordare di discutere questo aspetto della trasformazione, anche se non ricordo esattamente dove. Credo che non è molto utile ...

+3

No, non così tanto. ;-) – Steve

2

Se si utilizza collection.immutable.Map in 2.8 o superiore, allora il metodo withDefault è a vostra disposizione:

val m = collection.immutable.Map(1->"a", 2->"b", 3->"c") 
val n = m withDefaultValue "default" 

// n(7) will return "default" 

UPDATE

Se si' re mappatura sulla raccolta, spostare il withDefaultValue alla fine della catena di lavorazione:

val o = (for ((k, v) <- m) yield (k, v)) withDefaultValue "default" 
// o(0) will return "default" 
+0

Il metodo 'withDefaultValue' non funziona con' map' neanche. Prova 'val o = for ((k, v) <- n) yield (k, v); o (0) 'dopo il tuo codice sopra e riceverai un' NoSuchElementException'. – Steve

+0

@Steve Come per il tuo commento (ora cancellato) non è necessario essere immutabile. Prova a fare il conteggio iniziale in questo modo: '" ababcbbb ".groupBy (identity) mapValues ​​{_.size}' –

+0

Mettere il 'withDefaultValue' alla fine della catena di elaborazione non è davvero una buona opzione. Forse non so dove si trova la fine della catena di elaborazione, in particolare se restituisco questo oggetto a qualcun altro. Non saprò quale sia la loro catena di elaborazione. – Steve

1

Ok, qui è un approccio che è molto, molto vicino a quello che voglio:

class DefaultingMap[K, V](defaultValue: => V) 
extends mutable.HashMap[K, V] 
with mutable.MapLike[K, V, DefaultingMap[K, V]] { 

    override def empty = new DefaultingMap[K, V](defaultValue) 

    override def default(key: K): V = {     
    val result = this.defaultValue 
    this(key) = result 
    result            
    } 

    implicit def canBuildFrom[NK] = 
    new generic.CanBuildFrom[DefaultingMap[K, V], (NK, V), DefaultingMap[NK, V]] { 
     def apply(from: DefaultingMap[K, V]) = 
     new DefaultingMap[NK, V](from.newDefaultValue) 
     def apply() = 
     new DefaultingMap[NK, V](defaultValue) 
    } 

    def newDefaultValue = defaultValue 
} 

Ora, se ottengo che CanBuildFrom nel campo di applicazione, tutto funziona come un fascino:

scala> val counter = new DefaultingMap[Char, Int](0) 
counter: DefaultingMap[Char,Int] = Map() 

scala> for (c <- "ababcbbb") counter(c) += 1 

scala> import counter._ 
import counter._ 

scala> counter.map{case (k, v) => (k, v * 2)} 
res1: DefaultingMap[Char,Int] = Map((a,4), (c,2), (b,10)) 

scala> for ((k, v) <- counter; if v > 1) yield (k.toString, v * 2) 
res2: DefaultingMap[java.lang.String,Int] = Map((a,4), (b,10)) 

Tuttavia, se lascio fuori da quello import counter._, ottengo lo stesso comportamento di prima. Se riesco a capire come ottenere quello implicit def canBuildFrom da trovare, sarò impostato ...

+0

Si noti che questo si deteriora quando si esegue 'counter.map {case (k, v) => (k, v.toString)}'. Ora il valore predefinito è di tipo "Qualsiasi". – Debilski

+0

Questo è il tipo correttamente dedotto: tu hai gli elementi 'String' ma un default' Int' e l'unica cosa che condividono in comune è 'Any'. Ma penso che probabilmente hai ragione che sarebbe più utile semplicemente rilasciare il 'NV>: V' in favore di' V' da solo, in questo caso otterrai un 'HashMap [Char, String]'. – Steve

+0

Ok, l'ho modificato come suggerito - non è più 'canBuildFrom [NK, NV>: V]' ma semplicemente 'canBuildFrom [NK]'. – Steve

0

Non ti aiuterà con il tipo di ritorno di map (ma poi di nuovo, il valore predefinito del nuovo la raccolta non può essere generalmente definita utilizzando la trasformazione map). Ma per darvi un altro approccio:

class DefaultHashMap[A, B](dflt: => B) extends scala.collection.mutable.Map[A, B] { 
    val underlying = new scala.collection.mutable.HashMap[A, B]() 

    def get(key: A) = underlying.get(key) orElse Some(default(key)) 
    def iterator: Iterator[(A, B)] = underlying.iterator 
    def +=(kv: (A, B)) = { underlying += kv; this } 
    def -=(key: A) = { underlying -= key; this } 
    override def empty: DefaultHashMap[A, B] = new DefaultHashMap[A, B](dflt) 
    override def default(key: A) = dflt 
} 
+1

Sì, sfortunatamente, il tipo di ritorno di 'map' è praticamente il nocciolo del problema. Ho bisogno di quei valori predefiniti per restare. – Steve

Problemi correlati