Как проверить наличие множества типов в коллекции объекта базового типа? - PullRequest
2 голосов
/ 22 октября 2010

Мне нужно пересечь типы входящих объектов с набором предопределенных.

Необработанный подход заключается в сканировании входящей коллекции для каждого предопределенного типа:

trait Field
class Field1 extends Field
class Field2 extends Field
class Field3 extends Field
...

class FieldManager(shownFields:Iterable[Field]) {
  var hiddenFields = new ArrayBuffer[Field]
  var found = false
  for (sf <- shownFields) {
    if (sf.isInstanceOf[Field1]) {
      found = true
      break
    }
  if (!found)
    hiddenFields+=new Field1
  for (sf <- shownFields) {
    if (sf.isInstanceOf[Field2]) {
      found = true
      break
    }
  if (!found)
    hiddenFields+=new Field2
  for (sf <- shownFields) {
    if (sf.isInstanceOf[Field3]) {
      found = true
      break
    }
  if (!found)
    hiddenFields+=new Field3
  ...
}

Ого, этотак многословно для Scala!Там должен быть более короткий путь.Как и шаблон функции в C ++:

class FieldManager {
  vector<Field*> hiddenFields, shownFields;
  template<class T>
  void fillHiddenType() {
    FOR_EACH(Field *, field, shownFields) {
      if (dynamic_cast<T*>(field))
        return
      hiddenFields.push_back(new T)
    }
  }
  void fillHidden() {
    fillHiddenType<Field1>();
    fillHiddenType<Field2>();
    fillHiddenType<Field3>();
    ...
  }
};

В этом примере C ++ мы упоминаем каждый тип для сканирования только один раз.

Хорошо, Scala также хорош для параметров типа, давайте попробуем:

def fillHiddenType[T] {
  for (sf <- shownFields)
    if (sf.isInstanceOf[T])
      return
  hiddenFields+=new T  
}

Но компилятору не нравится создавать экземпляр T: class type required but T found.Попробуйте передать экземпляр в качестве аргумента, и появится следующая проблема warning: abstract type T in type is unchecked since it is eliminated by erasure, а isInstanceOf[] всегда верно!Тип T абстрагирован настолько хорошо, что полностью стирается, даже если [T <: Field] не позволяет сравнивать его с типами из нашей коллекции selectedFields. </p>

Вопрос заключается в следующем: как проверить наличие объектакакого типа в коллекции компактным образом?

Обновление Следующий рабочий код был протестирован на Scala 2.7.7 Оба ответа были полезны.Спасибо, ребята!

//Scala 2.7.7 misses toSet members
implicit def toSet[T](i:Iterable[T]): Set[T] = {
    val rv = new HashSet[T]
    rv ++= i
    rv
}

def filterOut[T](all:Iterable[T], toRemove:Set[T]) = {
    all.filter(x => ! toRemove.contains(x))
}

def filterOutByType[T <: AnyRef](all:Iterable[T], toFilter:Set[T]):Iterable[T] = {
    def toClass(x:AnyRef) = x.getClass.asInstanceOf[Class[T]]
    val allTypes = all map toClass
    val extraTypes = toSet(filterOut(allTypes, toFilter map toClass))
    all.filter(extraTypes contains toClass(_))
}

Ответы [ 2 ]

6 голосов
/ 22 октября 2010

Тривиально найти первого члена коллекции, который имеет заданный тип:

shownFields.find(_.isInstanceOf[Field1])

, но он по-прежнему будет возвращать экземпляр Option[Field], а не Option[Field1] - который вы хотитедля сильного набора текста.Здесь поможет метод сбора:

showFields.collect{case x : Field1 => x}

, который вернет Iterable[Field1], затем вы можете использовать headOption, чтобы выбрать первый элемент итерируемого как Option[Field1], либо Some[Field1], еслиприсутствует или None в противном случае:

showFields.collect{case x : Field1 => x}.headOption

Чтобы сделать его более эффективным и не вычислять ВСЕ поля1 в списке, я бы также сделал его ленивым, используя метод view:

showFields.view.collect{case x : Field1 => x}.headOption

, а затем для предоставления значения по умолчанию, если оно не найдено, используйте метод getOrElse, который Опции предоставляют:

showFields.view.collect{case x : Field1 => x}.headOption getOrElse (new Field1)

Обновление

Я просто перечитал вопрос, если кажется, что вы хотите, чтобы hiddenFields содержал новый экземпляр каждого подтипа Field, для которого нет элемента в showFields.

Чтобы найти все типы, представленные в Iterable:

val shownFieldTypes = showFields.map(_.getClass).toSet

(преобразование его в набор приводит к уникальным значениям)

Если у вас есть набор интересующих вас полей:

val allFieldTypes = Set(classOf[Field1], classOf[Field2], ...)

Выможно вычесть, чтобы найти недостающие:

val hiddenFieldTypes = allFieldTypes -- shownFieldTypes

Подвох в том, что вы застряли, используяnewInstance, а рефлексия не всегда желательна ... Итак:

val protoHiddenFields = Set(new Field1, new Field2, ...)
val allFieldTypes = protoHiddenFields.map(_.getClass)
val hiddenFieldTypes = allFieldTypes -- shownFieldTypes
val hiddenFields = protohiddenFields.filter(hiddenFieldTypes contains _.getClass)

Прелесть этого подхода в том, что ваши прототипы могут быть инициализированы с использованием параметров конструктора, если вы этого хотите

4 голосов
/ 22 октября 2010

Вы можете поддерживать набор известных подклассов Field, например, так:

val allFieldClasses = Set[Class[_ <: Field]](
  classOf[Field1], 
  classOf[Field2],
  classOf[Field3],
  ...)

Тогда создание скрытых полей - это просто вопрос фильтрации показанных классов полей из этого набора и создания экземпляров.из оставшихся:

def createHiddenFields(shownFields: Iterable[Field]):Set[Field] = {
  val toClass = (_:Field).getClass.asInstanceOf[Class[Field]]
  (allFieldTypes -- (shownFields map toClass)) map (_.newInstance)
}

К сожалению, вы должны будете помнить о том, что нужно обновлять allFieldClasses при добавлении новых подклассов Field - компилятор не предупредит васчто ваш набор не завершен - но я не вижу пути к этому.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...