Scala: обновить массив внутри карты - PullRequest
0 голосов
/ 19 марта 2020

Я создаю Карту с Массивом внутри. Мне нужно продолжать добавлять значения в этот массив. Как мне это сделать?

var values: Map[String, Array[Float]] = Map()

Я пробовал несколько способов, таких как:

myobject.values.getOrElse("key1", Array()).++(Array(float1))

Несколько других способов, но ничего не обновляет массив внутри карты.

Ответы [ 4 ]

5 голосов
/ 19 марта 2020

Существует проблема с этим кодом:

values.getOrElse("key1", Array()).++(Array(float1))

Это не обновляет Map в values, оно просто создает новый Array, а затем выбрасывает его.

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

values = values.updated("key1", values.getOrElse("key1", Array.empty[Float]) :+ float1)

Чтобы понять это, вам необходимо четко различать изменчивые переменные и изменяемые данные.

var используется для создания изменяемой переменной , что означает, что переменной можно присвоить новое значение, например,

var name = "John"
name = "Peter" // Would not work if name was a val

В отличие от этого изменяемые данные хранятся в объектах, содержимое которых может быть изменено

val a = Array(1,2,3)
a(0) = 12 // Works even though a is a val not a var

В вашем примере values является изменяемой переменной, но Map является неизменной, поэтому ее нельзя изменить , Вы должны создать новый неизменный Map и назначить его для изменяемой var.

4 голосов
/ 19 марта 2020

Из того, что я вижу (согласно ++), вы хотели бы добавить Array с еще одним элементом. Но Array структура фиксированной длины, поэтому вместо этого я бы рекомендовал использовать Vector. Потому что, я полагаю, вы используете неизменяемый Map, вам также необходимо обновить его.

Таким образом, окончательное решение может выглядеть следующим образом:

var values: Map[String, Vector[Float]] = Map()
val key = "key1"
val value = 1.0
values = values + (key -> (values.getOrElse(key, Vector.empty[Float]) :+ value))

Надеюсь, это поможет!

2 голосов
/ 19 марта 2020

Вы можете использовать Scala 2,13 transform , чтобы преобразовать вашу карту в любом случае.

val values = Map("key" -> Array(1f, 2f, 3f), "key2" -> Array(4f,5f,6f))

values.transform {
  case ("key", v) => v ++ Array(6f)
  case (_,v) => v
}

Результат:

Map(key -> Array(1.0, 2.0, 3.0, 6.0), key2 -> Array(4.0, 5.0, 6.0))

Обратите внимание, что добавление в массивы занимает линейное время, поэтому вы можете рассмотреть более эффективную структуру данных, такую ​​как Vector или Queue или даже List (если вы можете позволить себе добавлять, а не добавлять).

Обновление :

Однако, если вы хотите обновить только один ключ, вероятно, лучше использовать updatedWith:

values.updatedWith("key")(_.map(_ ++ Array(6f)))

, который даст тот же результат. Хорошая вещь в приведенном выше коде состоит в том, что если ключ не существует, он не изменит карту вообще, не выдав никакой ошибки.

1 голос
/ 20 марта 2020

Неизменяемые и изменяемые коллекции

Вам нужно выбрать , какой тип коллекции вы будете использовать immutable или mutable один. Оба великолепны и работают совершенно по-разному . Я предполагаю, что вы знакомы с mutable one (из других языков), но immutable по умолчанию в scala и, вероятно, вы используете его в своем коде (потому что он не нуждается в импорте). Неизменяемое Map не может быть изменено ... вы можете создать только новое с обновленными значениями (ответы Тима и Ивана охватывают это).

Есть несколько способов решить вашу проблему, и все хорошо в зависимости от варианта использования .

См. Реализацию ниже (от m1 до m6):

//just for convenience 
type T = String
type E = Long

import scala.collection._

//immutable map with immutable seq (default).
var m1 = immutable.Map.empty[T,List[E]] 

//mutable map with immutable seq. This is great for most use-cases.
val m2 = mutable.Map.empty[T,List[E]]

//mutable concurrent map with immutable seq.
//should be fast and threadsafe (if you know how to deal with it) 
val m3 = collection.concurrent.TrieMap.empty[T,List[E]] 

//mutable map with mutable seq.
//should be fast but could be unsafe. This is default in most imperative languages (PHP/JS/JAVA and more).
//Probably this is what You have tried to do
val m4 = mutable.Map.empty[T,mutable.ArrayBuffer[E]] 

//immutable map with mutable seq.
//still could be unsafe 
val m5 = immutable.Map.empty[T,mutable.ArrayBuffer[E]] 
//immutable map with mutable seq v2 (used in next snipped)
var m6 = immutable.Map.empty[T,mutable.ArrayBuffer[E]]

//Oh... and NEVER DO THAT, this is wrong
//I mean... don't keep mutable Map in `var`
//var mX = mutable.Map.empty[T,...]

Другие ответы показывают immutable.Map с immutable.Seq, и это предпочтительный способ (или, по крайней мере, по умолчанию). Это стоит чего-то, но для большинства приложений это совершенно нормально. Здесь у вас есть хороший источник информации о неизменных структурах данных: https://stanch.github.io/reftree/talks/Immutability.html.

Каждый вариант имеет свои плюсы и минусы. Каждый имеет дело с обновлениями по-своему, и это делает этот вопрос намного сложнее, чем выглядит на первый взгляд.

Решения

val k = "The Ultimate Answer"
val v = 42f

//immutable map with immutable seq (default).
m1 = m1.updated(k, v :: m1.getOrElse(k, Nil))

//mutable map with immutable seq.
m2.update(k, v :: m2.getOrElse(k, Nil))

//mutable concurrent map with immutable seq.
//m3 is bit harder to do in scala 2.12... sorry :)

//mutable map with mutable seq.
m4.getOrElseUpdate(k, mutable.ArrayBuffer.empty[Float]) += v

//immutable map with mutable seq.
m5 = m5.updated(k, {
  val col = m5.getOrElse(k, c.mutable.ArrayBuffer.empty[E])
  col += v
  col
})

//or another implementation of immutable map with mutable seq.
m6.get(k) match {
  case None => m6 = m6.updated(k, c.mutable.ArrayBuffer(v))
  case Some(col) => col += v
}

проверьте scalafiddle с этими реализациями. https://scalafiddle.io/sf/WFBB24j/3. Это отличный инструмент (ps: вы всегда можете сохранить CTRL+S ваши изменения и поделиться ссылкой, чтобы написать вопрос о вашем фрагменте).

О ... и если Вас интересует параллелизм (m3 case), напишите другой вопрос. Такие топи c заслуживают того, чтобы быть в отдельном потоке:)

(im) изменяемые коллекции API (im) изменяемые коллекции

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

val example = Array(1,2,3) 
example(0) = 33 //edit in place
println(example.mkString(", ")) //33, 2, 3

Но некоторые функции на нем (например, ++) создадут новую последовательность ... не изменит существующую:

val example2 = example ++ Array(42, 41) //++ is immutable operator
println(example.mkString(", ")) //33, 2, 3  //example stays unchanged
println(example2.mkString(", ")) //33, 2, 3, 42, 41 //but new sequence is created

Там метод updateWith, который является изменяемым и будет существовать только в изменяемых последовательностях . Существует также updatedWith, и он существует как в неизменяемых, так и в изменяемых коллекциях, и если вы не будете достаточно осторожны, вы будете использовать неправильную (да ... еще 1 буква).

This означает, что вы должны быть осторожны, какие функции вы используете, неизменяемые или изменяемые. Большую часть времени вы можете различать их по типу результата. Если что-то возвращает коллекцию, то, вероятно, это будет какая-то копия оригинального seq. Результатом является единица, тогда оно непостоянно.

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