2014-09-27 5 views
0

Пусть у меня есть эти классы: Foo(id: String) и Bar(fooId: String)Scala: вызов функции, чтобы проверить, если значение существует, если нет, то создать и добавить в список раза

У меня есть список Foo объектов, я хочу, чтобы создать список из Bar объектов в соответствии с ID в Foo объектов. Если в списке существует панель, я не хочу, чтобы ее снова создавали и добавляли в список. Я могу использовать Set для решения этой проблемы, но мне это не нравится. Это просто неправильно.

def createBars() = { 
    var barList = List[Bar]() 
    for { 
    f ← fooList 
    bar = findOrCreateBar(f, barList) 
    barList = barList:+bar //error 

    } yield bar 
} 

def findOrCreateBar(f: Foo, barList: List[Bar]): Bar = { 
    barList match { 
    case Nil ⇒ 
     Bar(f.id) 
    case _ ⇒ 
     val bars = for { 
     bar ← barList 
     if f.id == bar.id 
     } yield bar 

     if (bars.isEmpty) { 
     Bar(f.id) 
     } else { 
     bars.head 
     } 
    } 
} 

У меня есть несколько вопросов:

первого код выше не работает, потому что я получаю ошибку компиляции в barList:+bar в createBars. :+ невозможно решить! Также компилятор говорит, что var для объявления barList может быть val! Таким образом, кажется, что компилятор не видит barList в barList = barList:+bar как список!

второй Если я заменю вар из barList ВАЛ Я не могу переназначить измененный список barList

третьего Я использую для понимания в createBars, потому что я думал, что у меня есть нет другого выбора, чтобы вернуть barList. Использование аннотаций и т. Д. Только для разрыва в регулярной петле-петле - не очень хорошая идея, imho!

Пожалуйста, помогите!

+0

Как вы возражаете против использования набора? – dhg

+0

@dhg в принципе, я думаю, что использование Set в этой ситуации было бы полезно для управления симптомом, но не для решения проблемы, а именно: не создавать и добавлять один и тот же объект повторно. И я действительно хочу сделать код, который я написал, используя список. – mnish

ответ

2

Что о

fooList.distinct.map(foo=>Bar(foo.id)) 

Простой, эффективный и сохранить первоначальный заказ.

+0

Вопрос заключался в повторном использовании элементов с одинаковым идентификатором. – Aivean

+0

@Aivean. Вопрос довольно прост: «проверьте, существует ли значение, если нет, создайте и добавьте в список ** один раз **». @mnish специально отметил, что он не хочет использовать set (как предлагают другие), так как это создаст объект (Bar) несколько раз. Обратите внимание, что функция 'findOrCreateBar' пытается сделать именно это. – roterl

0

У вас есть несколько недостатков.

Прежде всего, старайтесь избегать изменчивых переменных там, где это возможно.

Тогда неэффективно искать существующие элементы в List, так как для этого требуется O (n) время для одиночного поиска и запускает алгоритм в квадрическом времени.

И, наконец, добавление элемента в конце List требует также O (n) времени. Если вы не можете избежать итеративного построения списка, используйте что-то изменчивое, например List.newBuilder.

Обратно к вашей проблеме, я разрешу ее с помощью рекурсивной функции, которая принимает и возвращает неизменные значения. (Не смотрите на уродливый взлом в начале, это только для демонстрационных целей).

var globalBarCount = 0 

case class Foo(id: String) 
case class Bar(fooId: String) { 
    // this is hack to track Bar instances 
    // never do this in real code 
    val barId = { globalBarCount += 1; globalBarCount } 
    override def toString: String = s"Bar#$barId($fooId)" 
} 

def createBars(foos:List[Foo]) = { 
    def createBars0(fooToProcess: List[Foo], existentBarsIndex: Map[String, Bar], 
        reversedResultAccum: List[Bar]): List[Bar] = 
    fooToProcess match { 
     case Nil => reversedResultAccum.reverse 
     case headFoo :: tail => 
     val newBar = existentBarsIndex.getOrElse(headFoo.id, Bar(headFoo.id)) 
     createBars0(tail, existentBarsIndex + (headFoo.id -> newBar), newBar :: reversedResultAccum) 
    } 

    createBars0(foos, Map.empty, Nil) 
} 

val fooList = List(Foo("foo1"), Foo("foo2"), Foo("foo3"), Foo("foo1")) 
val barList = createBars(fooList) 
println(barList) 

Результат:

List(Bar#1(foo1), Bar#2(foo2), Bar#3(foo3), Bar#1(foo1)) 

UPD Как я и обещал, добавляя новые подходы.

Вы можете использовать foldLeft, этот подход в основном делает то же, что и выше, но короче и, следовательно, предпочтительнее.

val barList2 = fooList.foldLeft((List.empty[Bar], Map.empty[String, Bar])) { 
    (accum, foo) => 
     val (resultListReversed, alreadyCreatedBars) = accum 
     val newBar = alreadyCreatedBars.getOrElse(foo.id, new Bar(foo.id)) 
     (newBar :: resultListReversed, alreadyCreatedBars + (foo.id -> newBar)) 
    }._1.reverse 

И окончательный подход.Этот код очень короткий, но вы предпочтете избежать его использования, так как не рекомендуется использовать изменяемые переменные внутри map.

import scala.collection.mutable 

    val barList3 = { 
    val createdBars = mutable.HashMap[String, Bar]() 
    fooList.map(foo => createdBars.getOrElseUpdate(foo.id, new Bar(foo.id))) 
    } 
+0

Благодарим вас за ответ. Но я получаю эту ошибку: java.lang.NoSuchMethodError: scala.collection.immutable. $ Colon $ colon.hd $ 1() Ljava/lang/Object – mnish

+0

@mnish Это странно. Убедитесь, что у вас есть все необходимые импорты. Я обновлю свой ответ, добавлю несколько других более коротких методов, чтобы выполнить свою задачу, когда я вернусь домой (через несколько часов). – Aivean

+0

@mnish добавил два новых подхода, как и обещал. Проверь их. – Aivean

1

На самом деле, лучше всего использовать Set. Это будет намного быстрее и намного яснее.

fooList.map(foo => new Bar(foo.id)).toSet 

Но, во всяком случае, здесь намного чище, версия о том, что вы пытаетесь сделать (то есть, по-прежнему использует цикл и List и var и т.д.):

def createBars2(fooList: List[Foo]) = { 
    var barList = Vector[Bar]() 
    for (foo <- fooList if !barList.exists(_.fooId == foo.id)) { 
    barList :+= Bar(foo.id) 
    } 
    barList.toList 
} 

val fooList = List(Foo("a"), Foo("b"), Foo("c"), Foo("b")) 
println(createBars2(fooList))