2015-07-16 2 views
0

Я пытаюсь сделать Set-like, который принимает только одну запись для каждого подтипа trait. Мой код в настоящее время:Scala Map, индексированный по типу

import scala.reflect.ClassTag 

trait Component 
case class Entity(id: Int) 

case class ComponentA(some: String) extends Component 
case class ComponentB(other: Int) extends Component 
case class ComponentC(thing: Boolean) extends Component 

val components: Map[Entity, Set[Component]] = Map(Entity(1) -> Set(ComponentA("A"), ComponentB(1))) 
def getComponent[A <: Component: ClassTag](entity: Entity): Option[A] = { 
    components.getOrElse(entity, Nil).collectFirst { case c: A => c } 
} 

getComponent[ComponentA](Entity(1)) 
getComponent[ComponentB](Entity(1)) 
getComponent[ComponentC](Entity(1)) 

Мой список лиц будет в миллионы, но компоненты для каждого объекта будет ~ 100. Есть ли более быстрый способ с индексированной картой какого-либо типа, чтобы предотвратить O (n)collectFirst при каждом чтении и filter + append для каждого обновления?

Я попробовал несколько вещей, но ближе всего я мог бы получить, это создать ComponentType признак, используемый для ключа Map[ComponentType, Component], а затем метод getComponent возвращает родовое Component вместо фактического требуемого типа. Это оставляет меня набирать cast во время выполнения или совпадение шаблонов для каждого вызывающего, вместо того, чтобы компилятор его использовал для меня.

Любые другие комбинации с типами, перечислениями, возможно специализированными Set или Map типами, которые могут здесь помочь?

Возможно, вся проверка типов не стоит накладных расходов на выполнение только 100/2 итераций в среднем, но мне нечего сравнивать производительность.

+1

Я бы не возиться с оптимизацией производительности, прежде чем определить, что существует реальная проблема производительности , –

+0

Это абсолютно верно. Моя текущая реализация выполняется медленнее, чем хотелось бы, но я еще не определил, что это еще одна причина. Но мне кажется, что это разочаровывает то, что я не могу получить эту же самую подпись метода для работы с другим базовым типом коллекции. Поэтому я все еще пытаюсь выяснить _why_ :) –

ответ

3

Вы можете избежать создания ComponentType признака, используя Map[ClassTag[_], Component] для второй структуры уровня:

val components: Map[Entity, Map[ClassTag[_], Component]] = Map(
    Entity(1) -> Map(
    implicitly[ClassTag[ComponentA]] -> ComponentA("A"), 
    implicitly[ClassTag[ComponentB]] -> ComponentB(1)) 
) 

def getComponent[A <: Component](entity: Entity)(implicit tag: ClassTag[A]): Option[A] = { 
    components.getOrElse(entity, Map[ClassTag[_], Component]()).get(tag).asInstanceOf[Option[A]] 
} 

Таким же образом можно реализовать addComponent:

def addComponent[A <: Component](entity: Entity, component: A)(implicit tag: ClassTag[A]): Map[Entity, Map[ClassTag[_], Component]] = { 
    components + (entity -> (components.getOrElse(entity, Map[ClassTag[_], Component]()) + (tag -> component))) 
} 

Вы не могли бы избежать литья таким образом, , но если вы скроете реализацию своей карты components в классе Components, то вы можете быть довольно безопасным, я думаю:

class Components { 
    private val components: Map[Entity, Map[ClassTag[_], Component]] = Map() 
    def getComponent[A <: Component](entity: Entity)(implicit tag: ClassTag[A]): Option[A] = ??? 
    def addComponent[A <: Component](entity: Entity, component: A)(implicit tag: ClassTag[A]): Components = ??? 
} 

Update: на основе @ RüdigerKlaehn свой комментарий, я изменил код, это более читаемым Сейчас:

val components: Map[(Entity, ClassTag[_]), Component] = Map(
    (Entity(1), implicitly[ClassTag[ComponentA]]) -> ComponentA("A"), 
    (Entity(1), implicitly[ClassTag[ComponentB]]) -> ComponentB(1) 
) 

def getComponent[A <: Component](entity: Entity)(implicit tag: ClassTag[A]): Option[A] = { 
    components.get((entity, tag)).asInstanceOf[Option[A]] 
} 

def addComponent[A <: Component](entity: Entity, component: A)(implicit tag: ClassTag[A]): Map[(Entity, ClassTag[_]), Component] = { 
    components + ((entity, tag) -> component) 
} 
+1

Почему бы не использовать кортеж (Entity, ClassTag [_]) как ключ одноуровневой карты, если вы хотите это сделать? –

+0

В моем случае, я оба хочу иногда перебирать все компоненты (я не знаю, что я ищу), а иногда и получить один (я знаю, что я ищу). Но это очень правильное предложение, возможно, я закончу это и реорганизую другие биты. –

+0

@ RüdigerKlaehn, потому что я об этом не думал, но он выглядит хорошо – kosii

Смежные вопросы