Swift에서 유형이 지정된 배열을 어떻게 확장 할 수 있습니까?


203

사용자 정의 기능 유틸리티를 사용하여 Swift Array<T>또는 T[]유형을 확장하려면 어떻게 해야합니까?

Swift의 API 문서를 살펴보면 Array 메소드가의 확장 인 것을 알 수 있습니다 T[].

extension T[] : ArrayType {
    //...
    init()

    var count: Int { get }

    var capacity: Int { get }

    var isEmpty: Bool { get }

    func copy() -> T[]
}

동일한 소스를 복사하여 붙여넣고 다음과 같은 변형을 시도 할 때 :

extension T[] : ArrayType {
    func foo(){}
}

extension T[] {
    func foo(){}
}

오류로 인해 빌드에 실패합니다.

공칭 유형 T[]은 확장 할 수 없습니다

전체 유형 정의를 사용하면 다음과 같이 실패합니다 Use of undefined type 'T'.

extension Array<T> {
    func foo(){}
}

그리고 그것은 또한 실패 Array<T : Any>하고 Array<String>.

Curiously Swift를 사용하면 다음을 사용하여 유형이 지정되지 않은 배열을 확장 할 수 있습니다.

extension Array {
    func each(fn: (Any) -> ()) {
        for i in self {
            fn(i)
        }
    }
}

내가 전화 할 수있는 것은 :

[1,2,3].each(println)

그러나 Swift의 내장 필터를 다음 같이 대체 하는 방법과 같이 유형이 메소드를 통과 할 때 유형이 손실 된 것처럼 보이기 때문에 적절한 일반 유형 확장자를 작성할 수 없습니다 .

extension Array {
    func find<T>(fn: (T) -> Bool) -> T[] {
        var to = T[]()
        for x in self {
            let t = x as T
            if fn(t) {
                to += t
            }
        }
        return to
    }
}

그러나 컴파일러는 다음을 사용하여 확장을 호출 할 수있는 유형화되지 않은 것으로 취급합니다.

["A","B","C"].find { $0 > "A" }

그리고 디버거를 사용하여 단계별로 입력하면 유형이 표시 Swift.String되지만 String먼저 캐스팅하지 않고 문자열처럼 액세스하려고하면 빌드 오류입니다 .

["A","B","C"].find { ($0 as String).compare("A") > 0 }

누구나 기본 제공 확장 기능과 같은 유형의 확장 메서드를 만드는 적절한 방법이 무엇인지 알고 있습니까?


나 자신도 답을 찾을 수 없기 때문에 투표했다. extension T[]XCode에서 배열 유형을 Command- 클릭 할 때 동일한 비트가 표시되지만 오류없이이를 구현하는 방법은 보이지 않습니다.
username tbd

@usernametbd 참고로 방금 발견했습니다. 솔루션이 <T>메소드 서명에서 제거 된 것처럼 보입니다 .
mythz 2016 년

답변:


296

클래스 를 사용하여 유형이 지정된 배열을 확장 하는 경우 다음이 저에게 효과적입니다 (Swift 2.2 ). 예를 들어 형식화 된 배열을 정렬하면 다음과 같습니다.

class HighScoreEntry {
    let score:Int
}

extension Array where Element == HighScoreEntry {
    func sort() -> [HighScoreEntry] {
      return sort { $0.score < $1.score }
    }
}

이 작업을 수행하려고 구조체 또는 typealias 오류를 줄 것이다 :

Type 'Element' constrained to a non-protocol type 'HighScoreEntry'

업데이트 :

비 클래스로 형식화 된 배열을 확장하려면 다음 방법을 사용하십시오.

typealias HighScoreEntry = (Int)

extension SequenceType where Generator.Element == HighScoreEntry {
    func sort() -> [HighScoreEntry] {
      return sort { $0 < $1 }
    }
}

에서 스위프트 3 일부 유형은 이름이 변경되었습니다 :

extension Sequence where Iterator.Element == HighScoreEntry 
{
    // ...
}

1
컴파일러에서 'SequenceType'의 이름이 'Sequence'로 바뀌
었다고

왜 반환 유형으로 Iterator.Element를 사용하지 않았 [Iterator.Element]습니까?
gaussblurinc

1
안녕하세요, 4.1의 조건부 적합성 기능을 설명 할 수 있습니까? 4.1의 새로운 기능 우리는 2.2에서 그렇게 할 수 있습니까? 내가 놓친 것
osrl

Swift 3.1부터 다음 구문을 사용하여 비 클래스로 배열을 확장 할 수 있습니다. extension Array where Element == Int
Giles

63

잠시 다른 시도를 한 후에 솔루션은 <T>다음과 같이 서명에서 서명 을 제거하는 것 같습니다 .

extension Array {
    func find(fn: (T) -> Bool) -> [T] {
        var to = [T]()
        for x in self {
            let t = x as T;
            if fn(t) {
                to += t
            }
        }
        return to
    }
}

빌드 오류없이 의도 한대로 작동합니다.

["A","B","C"].find { $0.compare("A") > 0 }

1
BTW 여기서 정의한 내용은 기능적으로 기존 filter기능 과 동일 합니다.let x = ["A","B","C","X”].filter { $0.compare("A") > 0 }
Palimondo

5
@Palimondo 아닙니다 . 내장 필터는 콜백을 두 번 실행 합니다.
mythz 2016 년

4
내가 참조. 이중 필터링은 오히려 나에게 버그 것 같다 ...하지만 여전히이 있음을 보유하고 filter있다 동등한 기능을 당신에게 find, 즉 함수의 결과는 동일합니다. 필터 클로저에 부작용이 있으면 결과가 마음에 들지 않을 수 있습니다.
Palimondo

2
@Palimondo 정확히, 기본 필터는 예상치 못한 동작을하는 반면 위의 find impl은 예상대로 작동합니다 (그리고 존재하는 이유). 클로저를 두 번 실행하면 범위가 지정된 변수를 변형시킬 수 있습니다 (버그가 발생하여 동작에 대한 질문). 또한 질문에는 Swift의 내장을 바꾸고 싶다는 언급이 filter있습니다.
mythz

4
우리는 기능 이라는 단어의 정의를 논박하고있는 것 같습니다 . 통상적으로, 기능 프로그래밍 패러다임 filter, mapreduce기능에서 발생 함수들은 값에 대한 실행된다. 반대로, each위에서 정의한 함수는 아무 것도 반환하지 않기 때문에 부작용으로 실행 된 함수의 예입니다. 현재 Swift 구현이 이상적이지 않으며 설명서에 런타임 특성에 대해 언급하지 않았다는 데 동의 할 수 있습니다.
Palimondo

24

모든 유형을 확장 하십시오 .

extension Array where Element: Comparable {
    // ...
}

일부 유형을 확장하십시오 .

extension Array where Element: Comparable & Hashable {
    // ...
}

특정 유형을 확장하십시오 .

extension Array where Element == Int {
    // ...
}

8

비슷한 문제가있었습니다-일반적인 배열을 swap () 메소드로 확장하고 싶었습니다.이 메소드는 배열과 동일한 유형의 인수를 취해야했습니다. 그러나 제네릭 형식을 어떻게 지정합니까? 시행 착오에 의해 아래에서 작동한다는 것을 알았습니다.

extension Array {
    mutating func swap(x:[Element]) {
        self.removeAll()
        self.appendContentsOf(x)
    }
}

그 핵심은 '요소'라는 단어였습니다. 이 유형을 어디에도 정의하지 않았으며 배열 확장의 컨텍스트 내에 자동으로 존재하며 배열 요소의 유형이 무엇이든 참조하십시오.

나는 무슨 일이 일어나고 있는지 100 % 확신하지 못하지만 아마도 'Element'가 배열의 관련 유형이기 때문일 것입니다 ( https://developer.apple.com/library/ios/documentation의 'Associated Types' 참조) /Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189 )

그러나 배열 구조 참조 ( https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Structure/index.html#//apple_ref/swift 에서이 참조를 볼 수 없습니다. / struct / s : Sa ) ... 그래서 나는 아직도 조금 확신하지 못한다.


1
Array일반 유형입니다 Array<Element>( swiftdoc.org/v2.1/type/Array 참조 ) Element는 포함 된 유형의 자리 표시 자입니다. 예를 들어 : var myArray = [Foo]()myArraytype 만 포함 함을 의미합니다 Foo. Foo이 경우 일반 자리 표시 자로 "매핑"됩니다 Element. 확장을 통해 Array의 일반적인 동작을 변경하려면 ElementFoo와 같은 구체적인 유형이 아닌 일반 자리 표시자를 사용합니다 .
David James

5

Swift 2.2 사용 : 문자열 배열에서 중복을 제거하려고 할 때 비슷한 문제가 발생했습니다. Array 클래스에 확장 기능을 추가하여 원하는 것을 수행 할 수있었습니다.

extension Array where Element: Hashable {
    /**
     * Remove duplicate elements from an array
     *
     * - returns: A new array without duplicates
     */
    func removeDuplicates() -> [Element] {
        var result: [Element] = []
        for value in self {
            if !result.contains(value) {
                result.append(value)
            }
        }
        return result
    }

    /**
     * Remove duplicate elements from an array
     */
    mutating func removeDuplicatesInPlace() {
        var result: [Element] = []
        for value in self {
            if !result.contains(value) {
                result.append(value)
            }
        }
        self = result
    }
}

이 두 메소드를 Array 클래스에 추가하면 배열에서 두 메소드 중 하나를 호출하고 중복을 성공적으로 제거 할 수 있습니다. 배열의 요소는 Hashable 프로토콜을 준수해야합니다. 이제 나는 이것을 할 수 있습니다 :

 var dupes = ["one", "two", "two", "three"]
 let deDuped = dupes.removeDuplicates()
 dupes.removeDuplicatesInPlace()
 // result: ["one", "two", "three"]

이것은 또한 달성 할 수있는 let deDuped = Set(dupes)당신이라는 비파괴 방식으로 반환 할 수있는 toSet만큼 유형의 변화에 정말 괜찮아요으로
alexpyoung

@alexpyoung 당신이 Set ()하면 배열의 순서를 엉망 것입니다
Danny Wang


3

Swift 2 표준 라이브러리 헤더를 살펴 보았습니다. 여기 필터 기능의 프로토 타입이 있습니다.

extension CollectionType {
    func filter(@noescape includeElement: (Self.Generator.Element) -> Bool) -> [Self.Generator.Element]
}

Array의 확장이 아니라 CollectionType의 확장이므로 동일한 메소드가 다른 컬렉션 유형에 적용됩니다. @noescape는 전달 된 블록이 필터 기능의 범위를 벗어나지 않아 일부 최적화가 가능함을 의미합니다. 자본 S를 가진 자아는 우리가 확장하는 계급입니다. Self.Generator는 컬렉션의 개체를 반복하는 반복자이며 Self.Generator.Element는 개체의 유형입니다 (예 : 배열 [Int?] Self.Generator.Element는 Int?).

이 필터 방법은 모든 CollectionType에 적용 할 수 있으며 컬렉션의 요소를 가져 와서 Bool을 반환하는 필터 블록이 필요하며 원래 유형의 배열을 반환합니다. 이것을 합치면 다음과 같은 유용한 방법이 있습니다. 콜렉션 요소를 선택적 값에 매핑하는 블록을 가져 와서 맵과 필터를 결합하고 nil이 아닌 선택적 값의 배열을 반환합니다.

extension CollectionType {

    func mapfilter<T>(@noescape transform: (Self.Generator.Element) -> T?) -> [T] {
        var result: [T] = []
        for x in self {
            if let t = transform (x) {
                result.append (t)
            }
        }
        return result
    }
}

2
import Foundation

extension Array {
    var randomItem: Element? {
        let idx = Int(arc4random_uniform(UInt32(self.count)))
        return self.isEmpty ? nil : self[idx]
    }
}

0

( 스위프트 2.x )

또한 일반적인 형식 메서드에 대해 파란색 -rpint를 포함하는 프로토콜, 예를 들어 프로토콜과 같은 일부 형식 제약 조건을 따르는 모든 일반 배열 요소에 대한 사용자 지정 기능 유틸리티를 포함하는 프로토콜에 맞게 배열을 확장 할 수 있습니다 MyTypes. 이 접근 방식을 사용하는 이점은 일반 배열 인수를 사용하여 함수를 작성할 수 있다는 것입니다. 이러한 배열 인수는 프로토콜과 같은 사용자 정의 함수 유틸리티 프로토콜을 준수해야한다는 제약이 있습니다 MyFunctionalUtils.

배열 요소를로 제한하는 유형을 사용하여 암시 적 으로이 동작을 얻을 수 있습니다 MyTypes. 또는 아래 설명 된 방법에서 볼 수 있듯이 일반 배열 함수 헤더에 입력 배열을 직접 표시합니다. 을 준수합니다 MyFunctionalUtils.


MyTypes타입 제약으로 사용 하기 위한 프로토콜 로 시작합니다 . 이 프로토콜을 사용하여 제네릭에 맞추려는 유형을 확장하십시오 (아래 예는 기본 유형 IntDouble사용자 정의 유형을 확장 합니다 MyCustomType)

/* Used as type constraint for Generator.Element */
protocol MyTypes {
    var intValue: Int { get }
    init(_ value: Int)
    func *(lhs: Self, rhs: Self) -> Self
    func +=(inout lhs: Self, rhs: Self)
}

extension Int : MyTypes { var intValue: Int { return self } }
extension Double : MyTypes { var intValue: Int { return Int(self) } }
    // ...

/* Custom type conforming to MyTypes type constraint */
struct MyCustomType : MyTypes {
    var myInt : Int? = 0
    var intValue: Int {
        return myInt ?? 0
    }

    init(_ value: Int) {
        myInt = value
    }
}

func *(lhs: MyCustomType, rhs: MyCustomType) -> MyCustomType {
    return MyCustomType(lhs.intValue * rhs.intValue)
}

func +=(inout lhs: MyCustomType, rhs: MyCustomType) {
    lhs.myInt = (lhs.myInt ?? 0) + (rhs.myInt ?? 0)
}

프로토콜 MyFunctionalUtils(추가적인 일반적인 배열 함수 유틸리티의 청사진을 잡고 있음)과 그 후 Array by MyFunctionalUtils; 블루 프린트 방식의 구현 :

/* Protocol holding our function utilities, to be used as extension 
   o Array: blueprints for utility methods where Generator.Element 
   is constrained to MyTypes */
protocol MyFunctionalUtils {
    func foo<T: MyTypes>(a: [T]) -> Int?
        // ...
}

/* Extend array by protocol MyFunctionalUtils and implement blue-prints 
   therein for conformance */
extension Array : MyFunctionalUtils {
    func foo<T: MyTypes>(a: [T]) -> Int? {
        /* [T] is Self? proceed, otherwise return nil */
        if let b = self.first {
            if b is T && self.count == a.count {
                var myMultSum: T = T(0)

                for (i, sElem) in self.enumerate() {
                    myMultSum += (sElem as! T) * a[i]
                }
                return myMultSum.intValue
            }
        }
        return nil
    }
}

마지막으로, 다음과 같은 경우에 각각 일반 배열을 취하는 함수를 보여주는 테스트와 두 가지 예

  1. 배열 매개 변수를 'MyTypes'(함수 )로 제한하는 유형을 통해 배열 매개 변수가 'MyFunctionalUtils'프로토콜을 준수한다는 암시 적 주장을 표시 bar1합니다.

  2. 배열 매개 변수가 'MyFunctionalUtils'프로토콜 (function )을 준수 함을 명시 적으로 표시 bar2합니다.

테스트 및 예제는 다음과 같습니다.

/* Tests & examples */
let arr1d : [Double] = [1.0, 2.0, 3.0]
let arr2d : [Double] = [-3.0, -2.0, 1.0]

let arr1my : [MyCustomType] = [MyCustomType(1), MyCustomType(2), MyCustomType(3)]
let arr2my : [MyCustomType] = [MyCustomType(-3), MyCustomType(-2), MyCustomType(1)]

    /* constrain array elements to MyTypes, hence _implicitly_ constraining
       array parameters to protocol MyFunctionalUtils. However, this
       conformance is not apparent just by looking at the function signature... */
func bar1<U: MyTypes> (arr1: [U], _ arr2: [U]) -> Int? {
    return arr1.foo(arr2)
}
let myInt1d = bar1(arr1d, arr2d) // -4, OK
let myInt1my = bar1(arr1my, arr2my) // -4, OK

    /* constrain the array itself to protocol MyFunctionalUtils; here, we
       see directly in the function signature that conformance to
       MyFunctionalUtils is given for valid array parameters */
func bar2<T: MyTypes, U: protocol<MyFunctionalUtils, _ArrayType> where U.Generator.Element == T> (arr1: U, _ arr2: U) -> Int? {

    // OK, type U behaves as array type with elements T (=MyTypes)
    var a = arr1
    var b = arr2
    a.append(T(2)) // add 2*7 to multsum
    b.append(T(7))

    return a.foo(Array(b))
        /* Ok! */
}
let myInt2d = bar2(arr1d, arr2d) // 10, OK
let myInt2my = bar2(arr1my, arr2my) // 10, OK

-1
import Foundation

extension Array {

    func calculateMean() -> Double {
        // is this an array of Doubles?
        if self.first is Double {
            // cast from "generic" array to typed array of Doubles
            let doubleArray = self.map { $0 as! Double }

            // use Swift "reduce" function to add all values together
            let total = doubleArray.reduce(0.0, combine: {$0 + $1})

            let meanAvg = total / Double(self.count)
            return meanAvg

        } else {
            return Double.NaN
        }
    }

    func calculateMedian() -> Double {
        // is this an array of Doubles?
        if self.first is Double {
            // cast from "generic" array to typed array of Doubles
            var doubleArray = self.map { $0 as! Double }

            // sort the array
            doubleArray.sort( {$0 < $1} )

            var medianAvg : Double
            if doubleArray.count % 2 == 0 {
                // if even number of elements - then mean average the middle two elements
                var halfway = doubleArray.count / 2
                medianAvg = (doubleArray[halfway] + doubleArray[halfway - 1]) / 2

            } else {
                // odd number of elements - then just use the middle element
                medianAvg = doubleArray[doubleArray.count  / 2 ]
            }
            return medianAvg
        } else {
            return Double.NaN
        }

    }

}

2
이 다운 캐스트 ( $0 as! Double)는 Swift의 유형 시스템과 싸우고 있으며 OP의 질문의 목적을 무시한다고 생각합니다. 그렇게하면 실제로 수행하려는 계산에 대한 컴파일러 최적화 가능성이 없어지고 의미없는 함수로 Array의 네임 스페이스를 오염시킬 수 있습니다 (UIViews 배열에서 .calculateMedian ()을 보려는 이유 또는 그 문제에 대해 Double 이외의 것?). 더 좋은 방법이 있습니다.
ephemer

시도extension CollectionType where Generator.Element == Double {}
ephemer
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.