web-dev-qa-db-ja.com

タイプによるSwift [AnyObject]配列のフィルタリング

AnyObjectの配列があるとします。

let grabBag: [AnyObject] = [ "Tom", 4, "Dick", NSObject(), "Harry" ]

それをStringsの配列にキャストして、実際にはStringsである要素のみを抽出したいと思います。私はこれがうまくいくと期待しています:

let strings = grabBag.filter{ $0 is String } as! [String]      // 1

ただし、エラー'Bool' is not convertible to 'String'が発生します。それでもこれは機能します:

let definitelyStrings = grabBag.filter{ $0 is String }         // 2
let strings = definitelyStrings as! [String]                   //

2が機能しないのに、なぜ1が機能するのですか?そして、2の要素を抽出して[AnyObject]にキャストする([T]よりも)簡単な方法はありますか?

18
OldPeculier

素敵なワンライナーにはflatMapを使用することをお勧めします。

let strings = grabBag.flatMap { $0 as? String }

これで、stringsのタイプは[String]


更新:Swift 4.2では、compactMapの代わりにflatMapを使用する必要があります。

let strings = grabBag.compactMap { $0 as? String }
31
Eric Aya

これがflatMapの目的です。

let strings = grabBag.flatMap{ $0 as? String }

これは、オプションを返すクロージャを取ります。オプションがnil以外の場合、結果に追加されます。

((これは他の言語のflatMapの意味とは一致せず、SwiftのflatMapの他の意味とも一致しないことに注意してください。より適切な名前はmapOptionalまたはmapSomeでした。しかし、一貫性がなくても、それでも直感的です。 「オプションにマップしてから、すべてのnilをフラット化します」。 Rob Mayoffは、OptionalsがSequenceTypesである場合、おそらくそうあるべきであると述べています。これは賢明な名前です。)

8
Rob Napier

テスト1の失敗は、明らかにコンパイラのバグだと思います。実際、REPLでクラッシュします。

Welcome to Apple Swift version 2.0 (700.1.100.2 700.1.74). Type :help for assistance.
  1> import Foundation
  2> let grabBag: [AnyObject] = [ "Tom", 4, "Dick", NSObject(), "Harry" ]
grabBag: [AnyObject] = 5 values {
  [0] = "Tom"
  [1] = Int64(4)
  [2] = "Dick"
  [3] = {
    isa = NSObject
  }
  [4] = "Harry"
}
  3> let strings = grabBag.filter { $0 is String } as! String
strings: String = {
  _core = {
    _baseAddress =
    _countAndFlags =
    _owner = <extracting data from value failed>

  }
}
Execution interrupted. Enter Swift code to recover and continue.
Enter LLDB commands to investigate (type :help for assistance.)
4> :bt
* thread #1: tid = 0x501bac, 0x00000001005c41f4 $__lldb_expr12`main + 420 at repl.Swift:3, queue = 'com.Apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
  * frame #0: 0x00000001005c41f4 $__lldb_expr12`main + 420 at repl.Swift:3
    frame #1: 0x0000000100001420 repl_Swift`_mh_execute_header + 5152
    frame #2: 0x00007fff8dd725c9 libdyld.dylib`start + 1
    frame #3: 0x00007fff8dd725c9 libdyld.dylib`start + 1

とにかく、ロブ・ネイピアも答えたように、grabBag.flatMap { $0 as? String }は短く、おそらく単純です。

3
rob mayoff

これは小さなSwift 5 ayaioの答えに基づく配列拡張です。タイプでたくさんフィルタリングしたいが、毎回クロージャーを書きたくない場合に役立つかもしれません。

extension Array {
   func filteredByType<T> (_: T.Type) -> [T] {
       return compactMap({ (element) in
           return element as? T
       })
   }
}

使用例:

let array: [Any] = ["foo", 47, ["baz"], "bar"]
let stringArray: [String] = array.filteredByType(String.self)
print(stringArray) // ["foo", "bar"]
1
Gamma