Scala Newb Вопрос - о области видимости и переменных
Я анализирую XML и продолжаю писать код вроде:
val xml = <outertag>
<dog>val1</dog>
<cat>val2</cat>
</outertag>
var cat = ""
var dog = ""
for (inner <- xml \ "_") {
inner match {
case <dog>{ dg @ _* }</dog> => dog = dg(0).toString()
case <cat>{ ct @ _* }</cat> => cat = ct(0).toString()
}
}
/* do something with dog and cat */
Это раздражает меня, потому что я должен быть в состоянии объявить кошку и собаку как val (неизменяемый), так как мне нужно установить их только один раз, но я должен сделать их изменяемыми. И кроме того, кажется, что в scala должен быть лучший способ сделать это. Есть идеи?
3 ответа
Вот два (теперь сделаем это три) возможных решения. Первый довольно быстрый и грязный. Вы можете запустить весь бит в интерпретаторе Scala.
val xmlData = <outertag>
<dog>val1</dog>
<cat>val2</cat>
</outertag>
// A very simple way to do this mapping.
def simpleGetNodeValue(x:scala.xml.NodeSeq, tag:String) = (x \\ tag).text
val cat = simpleGetNodeValue(xmlData, "cat")
val dog = simpleGetNodeValue(xmlData, "dog")
cat
будет "val2", и dog
будет "val1".
Обратите внимание, что если ни один из узлов не найден, будет возвращена пустая строка. Вы можете обойти это, или вы могли бы написать это немного более идиоматическим способом:
// A more idiomatic Scala way, even though Scala wouldn't give us nulls.
// This returns an Option[String].
def getNodeValue(x:scala.xml.NodeSeq, tag:String) = {
(x \\ tag).text match {
case "" => None
case x:String => Some(x)
}
}
val cat1 = getNodeValue(xmlData, "cat") getOrElse "No cat found."
val dog1 = getNodeValue(xmlData, "dog") getOrElse "No dog found."
val goat = getNodeValue(xmlData, "goat") getOrElse "No goat found."
cat1
будет "val2", dog1
будет "val1", и goat
будет "Коза не найдена".
ОБНОВЛЕНИЕ: Вот еще один удобный метод, чтобы взять список имен тегов и вернуть их совпадения в виде карты [String, String].
// Searches for all tags in the List and returns a Map[String, String].
def getNodeValues(x:scala.xml.NodeSeq, tags:List[String]) = {
tags.foldLeft(Map[String, String]()) { (a, b) => a(b) = simpleGetNodeValue(x, b)}
}
val tagsToMatch = List("dog", "cat")
val matchedValues = getNodeValues(xmlData, tagsToMatch)
Если вы запустите это, matchedValues
будет Map(dog -> val1, cat -> val2)
,
Надеюсь, это поможет!
ОБНОВЛЕНИЕ 2: Согласно предложению Дэниела, я использую оператор двойной обратной косой черты, который будет переходить в дочерние элементы, что может быть лучше по мере развития вашего набора данных XML.
scala> val xml = <outertag><dog>val1</dog><cat>val2</cat></outertag>
xml: scala.xml.Elem = <outertag><dog>val1</dog><cat>val2</cat></outertag>
scala> val cat = xml \\ "cat" text
cat: String = val2
scala> val dog = xml \\ "dog" text
dog: String = val1
Подумайте о том, чтобы обернуть проверку XML и сопоставление с образцом в функцию, которая возвращает несколько значений, которые вам нужны в виде кортежа (Tuple2[String, String]
). Но остановитесь и подумайте: похоже, что возможно не соответствовать dog
а также cat
элементы, в результате чего вы возвращаете null для одного или обоих компонентов кортежа. Возможно, вы могли бы вернуть кортеж Option[String]
или бросить, если ни один из шаблонов элементов не может быть связан.
В любом случае вы, как правило, можете решить эти проблемы инициализации, обернув составляющие операторы в функцию, чтобы получить выражение. Когда у вас есть выражение в руке, вы можете инициализировать константу с результатом его оценки.