Ánh xạ mảng đối tượng vào Từ điển trong Swift


95

Tôi có một mảng các Personđối tượng của:

class Person {
   let name:String
   let position:Int
}

và mảng là:

let myArray = [p1,p1,p3]

Tôi muốn bản đồ myArraytrở thành một Từ điển của [position:name]giải pháp cổ điển là:

var myDictionary =  [Int:String]()

for person in myArray {
    myDictionary[person.position] = person.name
}

Swift có cách nào thanh lịch để làm điều đó với cách tiếp cận chức năng không map, flatMap... hoặc phong cách Swift hiện đại khác


Trò chơi hơi muộn, nhưng bạn có muốn có từ điển về [position:name]hay [position:[name]]không? Nếu bạn có hai người ở cùng một vị trí, từ điển của bạn sẽ chỉ giữ lại người cuối cùng gặp phải trong vòng lặp của bạn .... Tôi có một câu hỏi tương tự mà tôi đang cố gắng tìm giải pháp, nhưng tôi muốn kết quả như thế nào[1: [p1, p3], 2: [p2]]
Zonker.in.Geneva

1
Chúng là một chức năng được tích hợp sẵn. Xem tại đây . Về cơ bảnDictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })
Honey

Câu trả lời:


129

Okay mapkhông phải là một ví dụ điển hình về điều này, vì nó giống như phép lặp, reducethay vào đó bạn có thể sử dụng , mỗi đối tượng của bạn phải kết hợp và biến thành một giá trị duy nhất:

let myDictionary = myArray.reduce([Int: String]()) { (dict, person) -> [Int: String] in
    var dict = dict
    dict[person.position] = person.name
    return dict
}

//[2: "b", 3: "c", 1: "a"]

Trong Swift 4 hoặc cao hơn, vui lòng sử dụng câu trả lời bên dưới để biết cú pháp rõ ràng hơn.


8
Tôi thích cách tiếp cận này nhưng liệu cách này có hiệu quả hơn so với việc chỉ tạo một vòng lặp để tải từ điển không? Tôi lo lắng về hiệu suất vì nó có vẻ như cho mỗi mục nó có để tạo ra một bản sao có thể thay đổi mới của một từ điển bao giờ lớn hơn ...
Kendall Helmstetter Gelner

nó thực sự là một vòng lặp, cách này được đơn giản hóa cách viết nó, hiệu suất là như nhau hoặc bằng cách nào đó tốt hơn so với vòng bình thường
Tj3n

2
Kendall, hãy xem câu trả lời của tôi bên dưới, nó có thể làm cho nó nhanh hơn lặp lại khi sử dụng Swift 4. Mặc dù, tôi chưa đánh giá nó để xác nhận điều đó.
Possen

2
Không sử dụng cái này !!! Như @KendallHelmstetterGelner đã chỉ ra, vấn đề với cách tiếp cận này là mỗi lần lặp lại, nó đang sao chép toàn bộ từ điển ở trạng thái hiện tại, vì vậy trong vòng lặp đầu tiên, nó đang sao chép từ điển một mục. Lần lặp thứ hai đó là sao chép một từ điển hai mục. Đó là một tiêu hao hiệu suất LỚN !! Cách tốt hơn để làm điều đó là viết một init tiện lợi trên từ điển lấy mảng của bạn và thêm tất cả các mục trong đó. Bằng cách đó, nó vẫn là một lớp lót cho người tiêu dùng, nhưng nó loại bỏ toàn bộ mớ hỗn độn phân bổ mà điều này có. Ngoài ra, bạn có thể phân bổ trước dựa trên mảng.
Mark A. Donohoe,

1
Tôi không nghĩ rằng hiệu suất sẽ là một vấn đề. Trình biên dịch Swift sẽ nhận ra rằng không có bản sao cũ nào của từ điển được sử dụng và thậm chí có thể sẽ không tạo chúng. Hãy nhớ quy tắc đầu tiên để tối ưu hóa mã của riêng bạn: "Đừng làm điều đó". Một châm ngôn khác của khoa học máy tính là các thuật toán hiệu quả chỉ hữu ích khi N lớn, và N hầu như không bao giờ lớn.
bugloaf

138

Swift 4bạn có thể thực hiện cách tiếp cận của @ Tj3n một cách rõ ràng và hiệu quả hơn bằng cách sử dụng intophiên bản reduceNó loại bỏ từ điển tạm thời và giá trị trả về, vì vậy nó nhanh hơn và dễ đọc hơn.

Thiết lập mã mẫu:

struct Person { 
    let name: String
    let position: Int
}
let myArray = [Person(name:"h", position: 0), Person(name:"b", position:4), Person(name:"c", position:2)]

Into tham số được truyền từ điển trống của loại kết quả:

let myDict = myArray.reduce(into: [Int: String]()) {
    $0[$1.position] = $1.name
}

Trực tiếp trả về một từ điển thuộc loại được truyền vào into:

print(myDict) // [2: "c", 0: "h", 4: "b"]

Tôi hơi bối rối về lý do tại sao nó dường như chỉ hoạt động với các đối số vị trí ($ 0, $ 1) chứ không phải khi tôi đặt tên cho chúng. Chỉnh sửa: nevermind, trình biên dịch có lẽ gặp trục trặc vì tôi vẫn cố trả về kết quả.
Khởi hành từ

Điều không rõ ràng trong câu trả lời này là điều gì $0thể hiện: đó là inouttừ điển mà chúng ta đang "quay trở lại" - mặc dù không thực sự trở lại, vì việc sửa đổi nó tương đương với việc quay trở lại do nó inout-ness.
future-adam

94

Kể từ Swift 4, bạn có thể làm điều này rất dễ dàng. Có hai trình khởi tạo mới xây dựng từ điển từ một chuỗi các bộ giá trị (cặp khóa và giá trị). Nếu các khóa được đảm bảo là duy nhất, bạn có thể làm như sau:

let persons = [Person(name: "Franz", position: 1),
               Person(name: "Heinz", position: 2),
               Person(name: "Hans", position: 3)]

Dictionary(uniqueKeysWithValues: persons.map { ($0.position, $0.name) })

=> [1: "Franz", 2: "Heinz", 3: "Hans"]

Điều này sẽ không thành công với lỗi thời gian chạy nếu bất kỳ khóa nào bị trùng lặp. Trong trường hợp đó, bạn có thể sử dụng phiên bản này:

let persons = [Person(name: "Franz", position: 1),
               Person(name: "Heinz", position: 2),
               Person(name: "Hans", position: 1)]

Dictionary(persons.map { ($0.position, $0.name) }) { _, last in last }

=> [1: "Hans", 2: "Heinz"]

Điều này hoạt động như vòng lặp for của bạn. Nếu bạn không muốn "ghi đè" các giá trị và dính vào ánh xạ đầu tiên, bạn có thể sử dụng điều này:

Dictionary(persons.map { ($0.position, $0.name) }) { first, _ in first }

=> [1: "Franz", 2: "Heinz"]

Swift 4.2 thêm trình khởi tạo thứ ba để nhóm các phần tử trình tự vào một từ điển. Các khóa từ điển được tạo ra bởi một bao đóng. Các phần tử có cùng khóa được đưa vào một mảng theo thứ tự như trong dãy. Điều này cho phép bạn đạt được kết quả tương tự như trên. Ví dụ:

Dictionary(grouping: persons, by: { $0.position }).mapValues { $0.last! }

=> [1: Person(name: "Hans", position: 1), 2: Person(name: "Heinz", position: 2)]

Dictionary(grouping: persons, by: { $0.position }).mapValues { $0.first! }

=> [1: Person(name: "Franz", position: 1), 2: Person(name: "Heinz", position: 2)]


3
Trong thực tế, theo tài liệu, trình khởi tạo 'grouping' tạo ra từ điển với mảng là giá trị (nghĩa là 'grouping', phải không?), Và trong trường hợp ở trên, nó sẽ giống hơn [1: [Person(name: "Franz", position: 1)], 2: [Person(name: "Heinz", position: 2)], 3: [Person(name: "Hans", position: 3)]]. Không phải là kết quả mong đợi, nhưng nó có thể được ánh xạ xa hơn đến từ điển phẳng nếu bạn muốn.
Varrry 19/07/18

Eureka! Đây là droid mà tôi đang tìm kiếm! Điều này là hoàn hảo và đơn giản hóa mọi thứ trong mã của tôi.
Zonker.in.Geneva

Liên kết chết đến trình khởi tạo. Bạn có thể vui lòng cập nhật để sử dụng permalink không?
Mark A. Donohoe

@MarqueIV Tôi đã sửa các liên kết bị hỏng. Bạn không chắc chắn những gì bạn đang đề cập đến với liên kết cố định trong ngữ cảnh này?
Mackie Messer

Permalinks là những liên kết mà các trang web sử dụng sẽ không bao giờ thay đổi. Vì vậy, ví dụ: thay vì một cái gì đó như 'www.SomeSite.com/events/today/item1.htm' có thể thay đổi hàng ngày, hầu hết các trang web sẽ thiết lập một 'liên kết cố định' thứ hai như 'www.SomeSite.com?eventid= 12345 'sẽ không bao giờ thay đổi và do đó, rất an toàn khi sử dụng trong các liên kết. Không phải tất cả mọi người đều làm điều đó, nhưng hầu hết các trang từ các trang web lớn sẽ cung cấp một.
Mark A. Donohoe

8

Bạn có thể viết bộ khởi tạo tùy chỉnh cho Dictionaryloại, ví dụ từ các bộ giá trị:

extension Dictionary {
    public init(keyValuePairs: [(Key, Value)]) {
        self.init()
        for pair in keyValuePairs {
            self[pair.0] = pair.1
        }
    }
}

và sau đó sử dụng mapcho mảng Person:

var myDictionary = Dictionary(keyValuePairs: myArray.map{($0.position, $0.name)})

5

Làm thế nào về một giải pháp dựa trên KeyPath?

extension Array {

    func dictionary<Key, Value>(withKey key: KeyPath<Element, Key>, value: KeyPath<Element, Value>) -> [Key: Value] {
        return reduce(into: [:]) { dictionary, element in
            let key = element[keyPath: key]
            let value = element[keyPath: value]
            dictionary[key] = value
        }
    }
}

Đây là cách bạn sẽ sử dụng nó:

struct HTTPHeader {

    let field: String, value: String
}

let headers = [
    HTTPHeader(field: "Accept", value: "application/json"),
    HTTPHeader(field: "User-Agent", value: "Safari"),
]

let allHTTPHeaderFields = headers.dictionary(withKey: \.field, value: \.value)

// allHTTPHeaderFields == ["Accept": "application/json", "User-Agent": "Safari"]

2

Bạn có thể sử dụng một hàm giảm. Đầu tiên, tôi đã tạo một trình khởi tạo được chỉ định cho lớp Person

class Person {
  var name:String
  var position:Int

  init(_ n: String,_ p: Int) {
    name = n
    position = p
  }
}

Sau đó, tôi đã khởi tạo Mảng giá trị

let myArray = [Person("Bill",1), 
               Person("Steve", 2), 
               Person("Woz", 3)]

Và cuối cùng, biến từ điển có kết quả:

let dictionary = myArray.reduce([Int: Person]()){
  (total, person) in
  var totalMutable = total
  totalMutable.updateValue(person, forKey: total.count)
  return totalMutable
}

1

Đây là những gì tôi đã được sử dụng

struct Person {
    let name:String
    let position:Int
}
let persons = [Person(name: "Franz", position: 1),
               Person(name: "Heinz", position: 2),
               Person(name: "Hans", position: 3)]

var peopleByPosition = [Int: Person]()
persons.forEach{peopleByPosition[$0.position] = $0}

Sẽ rất tốt nếu có một cách để kết hợp 2 dòng cuối cùng để peopleByPositioncó thể thành a let.

Chúng tôi có thể tạo một phần mở rộng cho Array để thực hiện điều đó!

extension Array {
    func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable {
        var map = [T: Element]()
        self.forEach{ map[block($0)] = $0 }
        return map
    }
}

Sau đó, chúng ta chỉ có thể làm

let peopleByPosition = persons.mapToDict(by: {$0.position})

1
extension Array {
    func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable {
        var map = [T: Element]()
        self.forEach{ map[block($0)] = $0 }
        return map
    }
}

1

Có lẽ một cái gì đó như thế này?

myArray.forEach({ myDictionary[$0.position] = $0.name })
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.