Cách lặp một vòng lặp với chỉ mục và phần tử trong Swift


784

Có một hàm mà tôi có thể sử dụng để lặp qua một mảng và có cả chỉ mục và phần tử, như liệt kê của Python không?

for index, element in enumerate(list):
    ...

Câu trả lời:


1661

Đúng. Kể từ Swift 3.0, nếu bạn cần chỉ mục cho từng thành phần cùng với giá trị của nó, bạn có thể sử dụng enumerated()phương thức để lặp qua mảng. Nó trả về một chuỗi các cặp bao gồm chỉ mục và giá trị cho từng mục trong mảng. Ví dụ:

for (index, element) in list.enumerated() {
  print("Item \(index): \(element)")
}

Trước Swift 3.0 và sau Swift 2.0, chức năng được gọi là enumerate():

for (index, element) in list.enumerate() {
    print("Item \(index): \(element)")
}

Trước Swift 2.0, enumeratelà một chức năng toàn cầu.

for (index, element) in enumerate(list) {
    println("Item \(index): \(element)")
}

3
Mặc dù có vẻ như là một tuple, trong Swift 1.2 - không chắc chắn về 2.0 - liệt kê trả về cấu trúc Enum CảSequence <base: SequenceType>.
nstein

2
@Leviathlon chi phí hiệu năng đáng chú ý hoặc có thể đo lường được sẽ quan trọng? Số
Dan Rosenstark

việc truy cập vào chỉ mục là lợi ích duy nhất của việc sử dụng enumerate?
Mật ong

29
Có lẽ họ sẽ đổi thành gerund, enumeratingcho Swift 4. Thật thú vị!
Dan Rosenstark

3
@Honey for (index, element) inkhi sử dụng enumeratedlà sai lệch. nên làfor (offset, element) in
Leo Dabus

116

Swift 5 cung cấp một phương thức được gọi là enumerated()cho Array. enumerated()có tuyên bố sau:

func enumerated() -> EnumeratedSequence<Array<Element>>

Trả về một chuỗi các cặp (n, x), trong đó n đại diện cho một số nguyên liên tiếp bắt đầu từ 0 và x đại diện cho một phần tử của chuỗi.


Trong trường hợp đơn giản nhất, bạn có thể sử dụng enumerated()với vòng lặp for. Ví dụ:

let list = ["Car", "Bike", "Plane", "Boat"]
for (index, element) in list.enumerated() {
    print(index, ":", element)
}

/*
prints:
0 : Car
1 : Bike
2 : Plane
3 : Boat
*/

Tuy nhiên, xin lưu ý rằng bạn không bị giới hạn sử dụng enumerated()với vòng lặp for. Trong thực tế, nếu bạn dự định sử dụng enumerated()với một vòng lặp for cho một cái gì đó tương tự như đoạn mã sau, thì bạn đã làm sai:

let list = [Int](1...5)
var arrayOfTuples = [(Int, Int)]()

for (index, element) in list.enumerated() {
    arrayOfTuples += [(index, element)]
}

print(arrayOfTuples) // prints [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]

Một cách nhanh hơn để làm điều này là:

let list = [Int](1...5)
let arrayOfTuples = Array(list.enumerated())
print(arrayOfTuples) // prints [(offset: 0, element: 1), (offset: 1, element: 2), (offset: 2, element: 3), (offset: 3, element: 4), (offset: 4, element: 5)]

Để thay thế, bạn cũng có thể sử dụng enumerated()với map:

let list = [Int](1...5)
let arrayOfDictionaries = list.enumerated().map { (a, b) in return [a : b] }
print(arrayOfDictionaries) // prints [[0: 1], [1: 2], [2: 3], [3: 4], [4: 5]]

Hơn nữa, mặc dù nó có một số hạn chế , forEachcó thể là sự thay thế tốt cho vòng lặp for:

let list = [Int](1...5)
list.reversed().enumerated().forEach { print($0, ":", $1) }

/*
prints:
0 : 5
1 : 4
2 : 3
3 : 2
4 : 1
*/

Bằng cách sử dụng enumerated()makeIterator(), bạn thậm chí có thể lặp lại thủ công trên của bạn Array. Ví dụ:

import UIKit
import PlaygroundSupport

class ViewController: UIViewController {

    var generator = ["Car", "Bike", "Plane", "Boat"].enumerated().makeIterator()

    override func viewDidLoad() {
        super.viewDidLoad()

        let button = UIButton(type: .system)
        button.setTitle("Tap", for: .normal)
        button.frame = CGRect(x: 100, y: 100, width: 100, height: 100)
        button.addTarget(self, action: #selector(iterate(_:)), for: .touchUpInside)
        view.addSubview(button)
    }

    @objc func iterate(_ sender: UIButton) {
        let tuple = generator.next()
        print(String(describing: tuple))
    }

}

PlaygroundPage.current.liveView = ViewController()

/*
 Optional((offset: 0, element: "Car"))
 Optional((offset: 1, element: "Bike"))
 Optional((offset: 2, element: "Plane"))
 Optional((offset: 3, element: "Boat"))
 nil
 nil
 nil
 */

1
Là truy cập vào chỉ mục là lợi ích duy nhất của việc sử dụng enumerate?
Mật ong

52

Bắt đầu với Swift 2, hàm liệt kê cần phải được gọi trên bộ sưu tập như vậy:

for (index, element) in list.enumerate() {
    print("Item \(index): \(element)")
}

47

Tôi đã tìm thấy câu trả lời này trong khi tìm kiếm một cách để làm điều đó với Từ điển , và hóa ra nó khá dễ dàng để điều chỉnh nó, chỉ cần vượt qua một tuple cho phần tử.

// Swift 2

var list = ["a": 1, "b": 2]

for (index, (letter, value)) in list.enumerate() {
    print("Item \(index): \(letter) \(value)")
}

18

Bạn chỉ có thể sử dụng vòng lặp liệt kê để có kết quả mong muốn:

Swift 2:

for (index, element) in elements.enumerate() {
    print("\(index): \(element)")
}

Swift 3 & 4:

for (index, element) in elements.enumerated() {
    print("\(index): \(element)")
}

Hoặc bạn có thể chỉ cần đi qua một vòng lặp for để có được kết quả tương tự:

for index in 0..<elements.count {
    let element = elements[index]
    print("\(index): \(element)")
}

Hy vọng nó giúp.


14

Liệt kê cơ bản

for (index, element) in arrayOfValues.enumerate() {
// do something useful
}

hoặc với Swift 3 ...

for (index, element) in arrayOfValues.enumerated() {
// do something useful
}

Liệt kê, lọc và bản đồ

Tuy nhiên, tôi thường sử dụng phép liệt kê kết hợp với bản đồ hoặc bộ lọc. Ví dụ với hoạt động trên một vài mảng.

Trong mảng này, tôi muốn lọc các phần tử lẻ hoặc thậm chí được lập chỉ mục và chuyển đổi chúng từ Ints thành Double. Vì vậy, enumerate()giúp bạn lập chỉ mục và phần tử, sau đó bộ lọc kiểm tra chỉ mục và cuối cùng để loại bỏ bộ dữ liệu kết quả, tôi ánh xạ nó thành phần tử.

let evens = arrayOfValues.enumerate().filter({
                            (index: Int, element: Int) -> Bool in
                            return index % 2 == 0
                        }).map({ (_: Int, element: Int) -> Double in
                            return Double(element)
                        })
let odds = arrayOfValues.enumerate().filter({
                            (index: Int, element: Int) -> Bool in
                            return index % 2 != 0
                        }).map({ (_: Int, element: Int) -> Double in
                            return Double(element)
                        })

9

Sử dụng .enumerate()các tác phẩm, nhưng nó không cung cấp chỉ mục thực sự của phần tử; nó chỉ cung cấp một Int bắt đầu bằng 0 và tăng thêm 1 cho mỗi phần tử liên tiếp. Điều này thường không liên quan, nhưng có khả năng xảy ra hành vi bất ngờ khi được sử dụng với ArraySliceloại. Lấy mã sau:

let a = ["a", "b", "c", "d", "e"]
a.indices //=> 0..<5

let aSlice = a[1..<4] //=> ArraySlice with ["b", "c", "d"]
aSlice.indices //=> 1..<4

var test = [Int: String]()
for (index, element) in aSlice.enumerate() {
    test[index] = element
}
test //=> [0: "b", 1: "c", 2: "d"] // indices presented as 0..<3, but they are actually 1..<4
test[0] == aSlice[0] // ERROR: out of bounds

Đó là một ví dụ hơi khó hiểu và nó không phải là vấn đề phổ biến trong thực tế nhưng tôi vẫn nghĩ rằng điều đó đáng để biết điều này có thể xảy ra.


2
it does not actually provide the true index of the element; it only provides an Int beginning with 0 and incrementing by 1 for each successive elementVâng, đó là lý do tại sao nó được gọi là liệt kê . Ngoài ra, lát không phải là mảng, vì vậy không có gì ngạc nhiên khi nó hoạt động khác đi. Không có lỗi ở đây - mọi thứ đều do thiết kế. :)
Eric Aya

5
Đúng, nhưng tôi không bao giờ gọi nó là một lỗi. Đây chỉ là hành vi có khả năng bất ngờ mà tôi nghĩ là đáng để đề cập cho những người không biết làm thế nào nó có thể tương tác tiêu cực với loại ArraySlice.
Cole Campbell

Bạn có biết bất kỳ cách nào để lấy chỉ mục của phần tử thực tế - ví dụ nếu sử dụng filtertrước không?
Alexandre Cassagne

9

Bắt đầu với Swift 3, đó là

for (index, element) in list.enumerated() {
  print("Item \(index): \(element)")
}

9

Để hoàn thiện, bạn có thể chỉ cần lặp lại các chỉ số mảng của mình và sử dụng chỉ mục để truy cập phần tử ở chỉ mục tương ứng:

let list = [100,200,300,400,500]
for index in list.indices {
    print("Element at:", index, " Value:", list[index])
}

Sử dụng forEach

list.indices.forEach {
    print("Element at:", $0, " Value:", list[$0])
}

Sử dụng enumerated()phương pháp thu thập . Lưu ý rằng nó trả về một bộ các bộ dữ liệu với offsetelement:

for item in list.enumerated() {
    print("Element at:", item.offset, " Value:", item.element)
}

sử dụng forEach:

list.enumerated().forEach {
    print("Element at:", $0.offset, " Value:", $0.element)
}

Những người sẽ in

Phần tử tại: 0 Giá trị: 100

Phần tử tại: 1 Giá trị: 200

Phần tử tại: 2 Giá trị: 300

Phần tử tại: 3 Giá trị: 400

Phần tử tại: 4 Giá trị: 500

Nếu bạn cần chỉ mục mảng (không phải phần bù) và phần tử của nó, bạn có thể mở rộng Bộ sưu tập và tạo phương thức của riêng bạn để lấy các phần tử được lập chỉ mục:

extension Collection {
    func indexedElements(body: ((index: Index, element: Element)) throws -> Void) rethrows {
        var index = startIndex
        for element in self {
            try body((index,element))
            formIndex(after: &index)
        }
    }
}

Một cách triển khai khả thi khác theo đề xuất của Alex là nén các chỉ số bộ sưu tập với các yếu tố của nó:

extension Collection {
    func indexedElements(body: ((index: Index, element: Element)) throws -> Void) rethrows {
        for element in zip(indices, self) { try body(element) }
    }
}

Kiểm tra:

let list =  ["100","200","300","400","500"]
list.dropFirst(2).indexedElements {
    print("Index:", $0.index, "Element:", $0.element)
}

Điều này sẽ p [rint

Chỉ số: 2 Yếu tố: 300

Chỉ số: 3 yếu tố: 400

Chỉ số: 4 yếu tố: 500


BTW bạn có thể thực hiện enumeratedIndicesbằng cách lặp lại vớizip(self.indices, self)
Alexander - Tái lập lại

@ Alexander-RebstateMonica for element in zip(indices, self) { try body(element) }. Btw Tôi không thích cách đặt tên mà tôi đã chọn, indexedElementscó thể mô tả tốt hơn những gì nó làm
Leo Dabus

Ôi tôi nghĩ đó là một cái tên tốt hơn. Vâng, một forvòng lặp hoạt động, nhưng cũngzip(self.indices, self) .forEach(body)
Alexander - Tái lập lại

@ Alexander-ReinstateMonica forEachthực hiện một vòng lặp phía sau hậu trường. Tôi thích giữ cho nó đơn giản github.com/apple/swift/blob/master/stdlib/public/core/ mẹo@inlinable public func forEach( _ body: (Element) throws -> Void ) rethrows { for element in self { try body(element) } } }
Leo Dabus

7

Đây là Công thức của vòng lặp liệt kê:

for (index, value) in shoppingList.enumerate() {
print("Item \(index + 1): \(value)")
}

để biết thêm chi tiết bạn có thể kiểm tra ở đây .


5

Cá nhân tôi thích sử dụng forEachphương pháp này:

list.enumerated().forEach { (index, element) in
    ...
}

Bạn cũng có thể sử dụng phiên bản ngắn:

list.enumerated().forEach { print("index: \($0.0), value: \($0.1)") }

4

Xcode 8 và Swift 3: Array có thể được liệt kê bằng cách sử dụng tempArray.enumerated()

Thí dụ:

var someStrs = [String]()

someStrs.append("Apple")  
someStrs.append("Amazon")  
someStrs += ["Google"]    


for (index, item) in someStrs.enumerated()  
{  
        print("Value at index = \(index) is \(item)").  
}

bảng điều khiển:

Value at index = 0 is Apple
Value at index = 1 is Amazon
Value at index = 2 is Google

3

Dành cho những ai muốn sử dụng forEach.

Swift 4

extension Array {
  func forEachWithIndex(_ body: (Int, Element) throws -> Void) rethrows {
    try zip((startIndex ..< endIndex), self).forEach(body)
  }
}

Hoặc là

array.enumerated().forEach { ... }

2

Đối với những gì bạn muốn làm, bạn nên sử dụng enumerated()phương thức trên Mảng của bạn :

for (index, element) in list.enumerated() {
    print("\(index) - \(element)")
}

-1

Chúng tôi đã gọi hàm liệt kê để thực hiện điều này. giống

for (index, phần tử) trong mảng.enum Cả () {

// chỉ mục là chỉ mục của mảng

// phần tử là phần tử của mảng

}

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.