2013-02-01 250 views
4

是否可以使用Scala模式匹配重寫下面的代碼?Scala模式匹配:如何匹配列表中的元素?

val ls: List[String] = ??? // some list of strings 

val res = if (ls.contains("foo")) FOO 
    else if (ls.contains("bar")) BAR 
    else SOMETHING_ELSE 
+0

UPDATE:該列表很短(最多4或5個項目),並且只能包含其中一個搜索值。列表實際上代表了樹狀結構中的路徑。我想確定哪個子樹是路徑地址。所以我正在尋找路徑中的特定節點,如果發現我返回該子樹的標識符。問題是我正在尋找的節點可能在樹中的不同層次上,所以我不知道它是否會成爲路徑(列表)中的第1,第2或第4個元素。 – wajda

回答

6

您可以實現這使用功能如

def onContains[T](xs: Seq[String], actionMappings: (String, T)*): Option[T] = { 
    actionMappings collectFirst { 
    case (str, v) if xs contains str => v 
    } 
} 

而且使用這樣的:

val x = onContains(items, 
    "foo" -> FOO, 
    "bar" -> BAR 
) 
+0

感謝您的回答。 'actionMappings:(String,T)*'中的*意味着什麼? –

+2

它允許定義一個方法有一個可變數量的參數,可以作爲一個'Seq'集合來訪問。 http://www.tutorialspoint.com/scala/functions_variable_arguments.htm –

13

您可以添加if條件匹配這樣的:

ls match { 
    case x if x.contains("foo") => // FOO 
    case x if x.contains("bar") => // BAR 
    case _ => // ELSE 
} 

然而,這是不是最好的方式,因爲每個if檢查需要遍歷列表,所以這並不能很好地擴展。有許多不同的方法可以解決這個問題,但我們需要了解更多關於內涵的信息,因爲運行時語義通常與您的代碼不同(例如,您可以遞歸遍歷列表尋找「foo」或「酒吧「,但假設你只有一個在列表中)。

+0

我以爲這樣做,但'案件'看起來多餘,因爲我有'如果'無論如何。 我想寫一些像{case _ ::「foo」:: _ => ??? } – wajda

2

正如弗蘭克的回答所說,這是可能的,但如果你這樣做會很骯髒,那麼代價很高。

這取決於你想要做什麼。你想返回那個「foo」或「bar」的索引(例如)?然後你會這樣做:

def indexOf[T]: (List[T], T) => Int = (ls, x) => ls match { 
    case Nil => -1 
    case e::es if(e.equals(x)) => 0 
    case e::es => val i = indexOf(es, x); if(i < 0) i else i + 1 
} 

此代碼未經測試,但您明白了。

+0

我正在尋找相當不錯,簡潔和易讀的代碼... :) – wajda

+0

我該如何閱讀代碼? scala – zinking

0

如果你需要的是某種具有優先命令執行的我可以建議

def executeCommand(input: List[String]): Option[Unit] = { 

    val priorities = Map(
    "foo" -> 1, 
    "bar" -> 2, 
    "baz" -> 3) withDefault(_ => 4) 

    def extractCommand(cmds: List[String]): Option[String] = 
    (cmds sortBy priorities).headOption 

    extractCommand(input) map { 
    case "foo" => println("found foo") 
    case "bar" => println("found bar") 
    case "baz" => println("found baz") 
    case _  => println("no known command") 
    } 

} 

在這種具體實施沒有意義的結果被返回(你只能去副作用),但如果你的情況下,應該返回一些值,你會發現它包裹在一個Option作爲方法的結果。


修訂
根據您的附加評論

def execute(input: List[String]): Option[String] = { 
    val commands: PartialFunction[String, String] = { 
    case "foo" => "result for foo" 
    case "bar" => "result for bar" 
    case "baz" => "result for baz" 
    } 

    (input find commands.isDefinedAt) map commands 

} 

這隻有你的命令是互斥的,只有一個應在input列表

+0

是的,我的案例應該返回一個值(請參閱我對該問題的評論)。我喜歡你的方法,儘管它比簡單的if-else構造簡潔得多。但我需要將我的常量(foo,bar,baz等)在代碼中列出兩次。我有幾十個他們,所以我會避免這一點。 – wajda

0
val ls = List[String]("bar", "foo", "baz") // stuff to check against 
val mappy = Map[String, String]("foo" -> "FOO", "bar" -> "BAR") // conversions go here 

val res = ls.flatMap{ 
    case x: String => mappy.get(x) 
} match { 
    case Seq(y) => y 
    case Nil => "SOMETHING_ELSE" // the `else` case goes here 
    case _ => new Exception("could be more than one thing") // handle this however you want 
} 

我相信這是做的最Scalaesque方式。案例與結果之間的關係在Map中簡明扼要,您可以根據需要選擇處理多個結果。你說過

名單短(最多4個或5個項目),並只能包含尋求一個值

但有些可能需要處理這種可能性。如果你真的不關心多個匹配,你可以做

val res = ls.flatMap(mappy.get).headOption.getOrElse("SOMETHING_ELSE") 

在這兩種情況下,它遍歷只有一次的名單。請享用!