ฉันจะขยาย Array ที่พิมพ์ใน Swift ได้อย่างไร


203

ฉันจะขยายสวิฟท์ได้อย่างไร Array<T>หรือT[]ประเภทด้วย utils หน้าที่กำหนดเองได้อย่างไร

การเรียกดูเอกสาร API ของ Swift แสดงให้เห็นว่าวิธี 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
    }
}

แต่คอมไพเลอร์ถือว่ามันเป็น untyped ที่มันยังอนุญาตให้เรียกนามสกุลด้วย:

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

และเมื่อผ่านไปแล้วกับตัวดีบั๊กเกอร์จะระบุว่าเป็นประเภทใดSwift.Stringแต่เป็นข้อผิดพลาดในการสร้างเพื่อลองเข้าถึงมันเหมือนสตริงโดยไม่ต้องส่งไปที่Stringอันดับแรกเช่น:

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

ไม่มีใครรู้ว่าวิธีการที่เหมาะสมในการสร้างวิธีการขยายประเภทที่ทำหน้าที่เหมือนส่วนขยายในตัวคืออะไร?


โหวตเพราะฉันไม่สามารถหาคำตอบได้ด้วยตนเอง เห็นextension T[]บิตเดียวกันเมื่อคลิก Command บนประเภท Array ใน XCode แต่ไม่เห็นวิธีที่จะนำไปใช้โดยไม่ได้รับข้อผิดพลาด
ชื่อผู้ใช้ tbd

@usernametbd FYI เพิ่งพบว่าดูเหมือนว่าโซลูชันจะถูกลบออก<T>จากลายเซ็นวิธีการ
mythz

คำตอบ:


296

สำหรับการขยายอาร์เรย์ที่พิมพ์ด้วยชั้นเรียนด้านล่างใช้งานได้สำหรับฉัน (Swift 2.2 ) ตัวอย่างเช่นการเรียงลำดับอาร์เรย์ที่พิมพ์:

class HighScoreEntry {
    let score:Int
}

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

การพยายามทำสิ่งนี้ด้วยstructหรือ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 }
    }
}

ในSwift 3บางประเภทถูกเปลี่ยนชื่อ:

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

1
คอมไพเลอร์รายงานว่า 'SequenceType' ถูกเปลี่ยนชื่อเป็น 'Sequence'
sandover

ทำไมคุณไม่ใช้ Iterator.Element ในประเภท return [Iterator.Element]?
gaussblurinc

1
สวัสดีคุณช่วยอธิบายคุณลักษณะ Conditional Conformance ใน 4.1 ได้ไหม? มีอะไรใหม่ใน 4.1? เราสามารถทำได้ใน 2.2? สิ่งที่ฉันหายไป
osrl

ตั้งแต่ Swift 3.1 คุณสามารถขยายอาร์เรย์ด้วย non-classes ด้วยไวยากรณ์ต่อไปนี้: extension Array โดยที่ 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

4
ฉันเห็น. การกรองแบบคู่ดูเหมือนจะเป็นเรื่องยากสำหรับฉัน ... แต่ก็ยังถือว่าสิ่งfilterนี้เทียบเท่ากับการใช้งานของคุณfindนั่นคือผลลัพธ์ของฟังก์ชันนั้นเหมือนกัน หากการปิดตัวกรองของคุณมีผลข้างเคียงคุณอาจไม่ชอบผลลัพธ์อย่างแน่นอน
Palimondo

2
@Palimondo ตรงตามตัวกรองเริ่มต้นมีพฤติกรรมที่ไม่คาดคิดในขณะที่การค้นหา impl ทำงานตามที่คาดไว้ (และทำไมจึงมีอยู่) มันไม่เทียบเท่ากับการใช้งานถ้ามันทำการปิดสองครั้งซึ่งอาจทำให้เกิดการเปลี่ยนแปลงตัวแปรที่ถูกกำหนดขอบเขต นอกจากนี้ยังบันทึกคำถามที่กล่าวถึงโดยเฉพาะที่ต้องการแทนที่สวิฟท์ในfilterตัว
mythz

4
เราดูเหมือนจะเถียงกันเรื่องความหมายของคำว่าการทำงาน ปรกติในกระบวนทัศน์การเขียนโปรแกรมการทำงานที่filter, mapและreduceฟังก์ชั่นมาจากฟังก์ชั่นจะดำเนินการสำหรับค่าที่พวกเขากลับมา ในทางตรงกันข้ามeachฟังก์ชั่นที่คุณกำหนดไว้ด้านบนเป็นตัวอย่างของฟังก์ชั่นที่ดำเนินการสำหรับผลข้างเคียงเนื่องจากไม่มีการคืนค่าใด ๆ ฉันเดาว่าเราสามารถยอมรับได้ว่าการติดตั้ง Swift ในปัจจุบันนั้นไม่เหมาะและเอกสารไม่ได้ระบุอะไรเกี่ยวกับลักษณะการทำงานของรันไทม์
Palimondo

24

ขยายทุกประเภท:

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

ขยายบางประเภท:

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

ขยายประเภทเฉพาะ :

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

8

ฉันมีปัญหาที่คล้ายกัน - ต้องการที่จะขยาย Array ทั่วไปด้วยวิธีการแลกเปลี่ยน () ซึ่งควรจะโต้แย้งชนิดเดียวกันเป็นอาร์เรย์ แต่คุณจะระบุประเภททั่วไปได้อย่างไร ฉันพบโดยการลองผิดลองถูกที่ด้านล่างใช้ได้:

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

กุญแจสำคัญในการเป็นคำว่า 'องค์ประกอบ' โปรดทราบว่าฉันไม่ได้กำหนดประเภทนี้ทุกที่ดูเหมือนว่าจะมีอยู่โดยอัตโนมัติภายในบริบทของส่วนขยายอาร์เรย์และอ้างอิงถึงสิ่งที่ประเภทขององค์ประกอบของอาร์เรย์คือ

ฉันไม่แน่ใจ 100% ว่าเกิดอะไรขึ้น แต่ฉันคิดว่าอาจเป็นเพราะ 'Element' เป็นประเภทของ Array ที่เกี่ยวข้อง (ดู 'ประเภทที่เกี่ยวข้อง' ที่นี่https://developer.apple.com/library/ios/documentation /Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189 )

อย่างไรก็ตามฉันไม่เห็นการอ้างอิงนี้ในการอ้างอิงโครงสร้าง Array ( https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Struct/index.html#//apple_ref/swift / struct / s: Sa ) ... ดังนั้นฉันยังไม่แน่ใจเล็กน้อย


1
Array เป็นประเภททั่วไป: Array<Element> (ดูswiftdoc.org/v2.1/type/Array ) Elementเป็นตัวยึดตำแหน่งสำหรับประเภทที่มีอยู่ ตัวอย่างเช่น: var myArray = [Foo]()หมายความว่าmyArrayFooจะมีเฉพาะประเภท Fooในกรณีนี้คือ "แมป" Elementเพื่อยึดทั่วไป หากคุณต้องการเปลี่ยนพฤติกรรมทั่วไปของ Array (ผ่านทางส่วนขยาย) คุณจะต้องใช้ตัวยึดตำแหน่งทั่วไปElementและไม่ใช่ประเภทที่เป็นรูปธรรมใด ๆ (เช่น Foo)
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

5

หากคุณต้องการเรียนรู้เกี่ยวกับการขยายอาร์เรย์และประเภทการสร้างอื่น ๆ ในรหัสชำระเงินในชั้นเรียนใน repo github นี้https://github.com/ankurp/Cent

ในฐานะของ Xcode 6.1 ไวยากรณ์เพื่อขยายอาร์เรย์มีดังนี้

extension Array {
    func at(indexes: Int...) -> [Element] {
        ... // You code goes herer
    }
}

1
@Rob อัปเดต URL
Encore PTL

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 และจะส่งกลับอาร์เรย์ประเภทเดิม นี่คือวิธีที่ฉันพบว่ามีประโยชน์: มันรวมแผนที่และตัวกรองเข้าด้วยกันโดยการบล็อกที่จับคู่องค์ประกอบคอลเลกชันเป็นค่าที่เป็นทางเลือกและส่งกลับอาร์เรย์ของค่าทางเลือกเหล่านั้นที่ไม่ใช่ศูนย์

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
    }
}


0

( รวดเร็ว 2.x )

นอกจากนี้คุณยังสามารถขยายอาร์เรย์เพื่อให้สอดคล้องกับโปรโตคอลที่มีสีฟ้า rpints สำหรับวิธีการประเภททั่วไปเช่นโปรโตคอลที่มีกำหนดเองของคุณ utils การทำงานสำหรับทุกองค์ประกอบของอาร์เรย์ทั่วไปสอดคล้องกับข้อ จำกัด MyTypesบางชนิดกล่าวว่าโปรโตคอล โบนัสที่ใช้วิธีนี้คือคุณสามารถเขียนฟังก์ชั่นที่ใช้อาร์กิวเมนต์อาเรย์ทั่วไปโดยมีข้อ จำกัด ว่าอาร์กิวเมนต์อาเรย์เหล่านี้จะต้องเป็นไปตามโปรโตคอลยูทิลิตี้ฟังก์ชั่นที่คุณกำหนดเองMyFunctionalUtilsว่าสิ่งเหล่านี้อาร์กิวเมนต์อาร์เรย์ต้องเป็นไปตามที่กำหนดเองสาธารณูปโภคฟังก์ชั่นโพรโทคอของคุณบอกว่าโปรโตคอล

คุณสามารถรับพฤติกรรมนี้ได้โดยปริยายโดยพิมพ์ข้อ จำกัด ขององค์ประกอบอาร์เรย์ให้MyTypesหรือ --- ตามที่ฉันจะแสดงในวิธีที่ฉันอธิบายด้านล่าง --- ค่อนข้างเรียบร้อยอย่างชัดเจนแจ้งให้ฟังก์ชั่นอาร์เรย์ทั่วไปของคุณโดยตรงแสดงอาร์เรย์อินพุตนั้น เป็นไปMyFunctionalUtilsตาม


เราเริ่มต้นด้วยโปรโตคอลMyTypesเพื่อใช้เป็นข้อ จำกัด ประเภท ขยายประเภทที่คุณต้องการให้พอดีกับ generics ของคุณด้วยโปรโตคอลนี้ (ตัวอย่างด้านล่างขยายประเภทพื้นฐานIntและDoubleรวมถึงประเภทที่กำหนดเอง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 โดย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. แสดงการยืนยันโดยนัยว่าพารามิเตอร์อาร์เรย์สอดคล้องกับโปรโตคอล 'MyFunctionalUtils' ผ่านประเภทที่ จำกัด องค์ประกอบของอาร์เรย์ให้เป็น 'MyTypes' (ฟังก์ชันbar1)

  2. แสดงอย่างชัดเจนว่าพารามิเตอร์อาร์เรย์สอดคล้องกับโปรโตคอล 'MyFunctionalUtils' (ฟังก์ชัน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
downcasts เหล่านี้ ( $0 as! Double) กำลังต่อสู้กับระบบประเภทของ Swift และเอาชนะจุดประสงค์ของคำถาม OP ในความคิดของฉัน ด้วยการทำเช่นนั้นคุณกำลังสูญเสียศักยภาพในการปรับแต่งคอมไพเลอร์สำหรับการคำนวณที่คุณต้องการทำจริง ๆ และคุณยังทำให้เนมสเปซของอาเรย์ด้วยฟังก์ชันที่ไม่มีความหมาย (ทำไมคุณถึงต้องการดู ของอะไรนอกจากเป็นสองเท่าสำหรับเรื่องนั้น?) มีวิธีที่ดีกว่าคือ
ephemer

ลองextension CollectionType where Generator.Element == Double {}
ephemer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.