Знайти об’єкт у масиві?


144

У Swift є щось на кшталт _.findWhere в Underscore.js?

У мене є масив типів структур Tі я хотів би перевірити, чи містить масив структурний об'єкт, nameвластивість якого дорівнює Foo.

Намагалися використовувати find()і , filter()але вони працюють тільки з примітивними типами, наприклад , Stringабо Int. Здається помилка про невідповідність Equitableпротоколу чи щось подібне.


Це може бути те, що ви шукаєте: Знайдіть об’єкт із властивістю в масиві .
Martin R

чому б не перетворити на nsdictionary і шукати
longbow

3
Я вважаю, що пошук у Swift 2.0 більше не доступний. Я перетворював якийсь 1,2 код на Swift 2.0, і він сказав, що замість нього використовувати IndexOf.
Швидка сода

Відповіді:


91

FWIW, якщо ви не хочете користуватися спеціальною функцією або розширенням, ви можете:

let array = [ .... ]
if let found = find(array.map({ $0.name }), "Foo") {
    let obj = array[found]
}

Це генерує nameспочатку масив, а потім findз нього.

Якщо у вас є величезний масив, ви можете зробити:

if let found = find(lazy(array).map({ $0.name }), "Foo") {
    let obj = array[found]
}

або можливо:

if let found = find(lazy(array).map({ $0.name == "Foo" }), true) {
    let obj = array[found]
}

Це ще краще. Я відзначу це як відповідь, оскільки це виглядає в цілому більш простим і не вимагає створення спеціальної функції.
Сахат Ялкабов

27
Станом на Swift 2.0 ви можете використовувати: array.indexOf ({$ 0.name == "Foo"})
tf.alves

73
З Swift 3.0 ви можете використовувати: array.first (де: {$ 0.name == "Foo"}), якщо вам потрібен об'єкт
Brett

Як я можу перевірити, чи містить $ 0.name, рядок "foo"? відповіді про точну відповідність. Мені потрібно містить рядок. Хтось може надати для цього синтаксис
Азік Абдулла

290

SWIFT 5

Перевірте, чи існує елемент

if array.contains(where: {$0.name == "foo"}) {
   // it exists, do something
} else {
   //item could not be found
}

Отримайте елемент

if let foo = array.first(where: {$0.name == "foo"}) {
   // do something with foo
} else {
   // item could not be found
}

Отримайте елемент та його зміщення

if let foo = array.enumerated().first(where: {$0.element.name == "foo"}) {
   // do something with foo.offset and foo.element
} else {
   // item could not be found
}

Отримайте компенсацію

if let fooOffset = array.firstIndex(where: {$0.name == "foo"}) {
    // do something with fooOffset
} else {
    // item could not be found
}

6
Приємна відповідь у стилі Свіфт!
Золтан

4
Дякую, що ти мій закладач, це дуже добре очистило мій код
AD Progress

як перевірити декілька умов, скажіть, мені потрібно перевірити, чи $0.name == "foo"робиться масив однією операцією та $0.name == "boo" робити іншу операцію
Midhun Narayan

Це допомогло мені більше, ніж прийнята відповідь у 2020 році.
Psiloc

Як я можу перевірити, чи містить $ 0.name, рядок "foo"? Хтось може надати для цього синтаксис
Азік Абдулла

131

Ви можете скористатися indexметодом, доступним Arrayіз присудком ( див. Тут документацію Apple ).

func index(where predicate: (Element) throws -> Bool) rethrows -> Int?

Для вашого конкретного прикладу це:

Швидкий 5.0

if let i = array.firstIndex(where: { $0.name == "Foo" }) {
    return array[i]
}

Swift 3.0

if let i = array.index(where: { $0.name == Foo }) {
    return array[i]
}

Швидкий 2.0

if let i = array.indexOf({ $0.name == Foo }) {
    return array[i]
}

3
Так, це працює лише з швидким 2.0. Вибачте, мав це згадати
користувач3799504

@ user3799504 означає $ 0?
Прамод Тапанія

$ 0 - це скорочення для першого аргументу про закриття. У цьому випадку це стосується self.generator.element або кожного елемента масиву. Дивіться: developer.apple.com/library/ios/documentation/Swift/Conceptual/…
користувач3799504

1
Як щодо Swift 3?
adnako

38

Швидкий 3

Якщо вам потрібен об'єкт, використовуйте:

array.first{$0.name == "Foo"}

(Якщо у вас є більше одного об’єкта з назвою "Foo", то firstповерне перший об'єкт із не визначеного впорядкування)


Дякую за це. Це повинно бути там!
NerdyTherapist

3
Це приємно, дякую! Можна написати також так:array.first {$0.name == "Foo"}
Роланд Т.

2
У Swift3 це повинно бутиarray.first(where: {$0.name == "Foo"})
Даніель,

З поміткою Даніеля, це правильна, найкраща відповідь для Swift 3. Не використовуйте для цього карту, не фільтруйте; вони повторюють цілі колекції, що може бути дуже марно.
Womble

20

Ви можете відфільтрувати масив, а потім просто вибрати перший елемент, як показано в Find Object with Properties in Array .

Або ви визначаєте спеціальне розширення

extension Array {

    // Returns the first element satisfying the predicate, or `nil`
    // if there is no matching element.
    func findFirstMatching<L : BooleanType>(predicate: T -> L) -> T? {
        for item in self {
            if predicate(item) {
                return item // found
            }
        }
        return nil // not found
    }
}

Приклад використання:

struct T {
    var name : String
}

let array = [T(name: "bar"), T(name: "baz"), T(name: "foo")]

if let item = array.findFirstMatching( { $0.name == "foo" } ) {
    // item is the first matching array element
} else {
    // not found
}

У Swift 3 ви можете використовувати існуючий first(where:)метод (як зазначено в коментарі ):

if let item = array.first(where: { $0.name == "foo" }) {
    // item is the first matching array element
} else {
    // not found
}

З точки зору ефективності, як це порівняти array.lazy.filter( predicate ).first? Наскільки ефективно .lazy для малих масивів?
Пат Німейер

@PatNiemeyer: Я не знаю, вам доведеться виміряти продуктивність і порівняти.
Мартін R

@PatNiemeyer рішення вище, безумовно, буде більш ефективним, хоча різниця не буде, ймовірно, великою. 1. Складність цього filterзавжди полягає O(n)в тому, що в цьому випадку findFirstMatchingце лише в гіршому сценарії (коли шуканий елемент останній або зовсім не в масиві). 2. filterстворює абсолютно новий масив відфільтрованих елементів, тоді як findFirstMatchingсправедливий повертає запитуваний елемент.
0101

У Swift 3 я отримую спадкування помилок від непротокольного, некласового типу "Bool" та використання незадекларованого типу "T" для цього методу розширення.
Ісуру

@Isuru: Ця відповідь була досить старою і згадувала стару версію Swift. У Swift 3 вам більше не потрібен спеціальний метод розширення для цієї мети, я відповідним чином оновив відповідь.
Мартін R

20

Swift 3.0

if let index = array.index(where: { $0.name == "Foo" }) {
    return array[index]
}

Швидкий 2.1

Фільтр у властивостях об'єкта тепер підтримується у швидкому 2.1. Ви можете фільтрувати масив на основі будь-якого значення структури або класу, ось приклад

for myObj in myObjList where myObj.name == "foo" {
 //object with name is foo
}

АБО

for myObj in myObjList where myObj.Id > 10 {
 //objects with Id is greater than 10
}

9

Швидкий 4 ,

Ще один спосіб досягти цього за допомогою функції фільтра,

if let object = elements.filter({ $0.title == "title" }).first {
    print("found")
} else {
    print("not found")
}

8

Швидкий 3

ви можете використовувати індекс (де :) у Swift 3

func index(where predicate: @noescape Element throws -> Bool) rethrows -> Int?

приклад

if let i = theArray.index(where: {$0.name == "Foo"}) {
    return theArray[i]
}

Чи є метод у швидкій 3, де ви можете знайти індекси переліку елементів масиву, які відповідають умові $0.name == "Foo"?
Ахмед Хедр

3

Швидкий 3

if yourArray.contains(item) {
   //item found, do what you want
}
else{
   //item not found 
   yourArray.append(item)
}

2

Swift 2 або пізнішої версії

Можна поєднати indexOfта mapнаписати функцію «знайти елемент» в одному рядку.

let array = [T(name: "foo"), T(name: "Foo"), T(name: "FOO")]
let foundValue = array.indexOf { $0.name == "Foo" }.map { array[$0] }
print(foundValue) // Prints "T(name: "Foo")"

Використання filter+ firstвиглядає чистіше, але filterоцінює всі елементи масиву. indexOf+ mapвиглядає складно, але оцінка припиняється, коли знайдена перша відповідність у масиві. У обох підходів є плюси і мінуси.


1

Використання contains:

var yourItem:YourType!
if contains(yourArray, item){
    yourItem = item
}

Або ви можете спробувати, на що вказував Мартін, у коментарях та filterспробуйте ще раз: Знайдіть об’єкт із властивістю в масиві .


Це поверне лише буле? Мені також потрібно отримати об’єкт, а не просто перевірити, чи є в масиві.
Сахат Ялкабов

Це припущення itemтого ж типу, що і елемент масиву. Однак у мене є лише назва view.annotation.title. Мені потрібно порівняти елементи в масиві за цим заголовком.
Сахат Ялкабов

Щось подібне if contains(yourArray, view.annotation.title) { // code goes here }.
Сахат Ялкабов

Мартін показав вам інший шлях у коментарях. Перевірте надане ним посилання.
Крістіан

1

Інший спосіб отримати доступ до array.index (з: Будь-який) - це оголосити ваш об'єкт

import Foundation
class Model: NSObject {  }


1

Швидкий 3:

Ви можете використовувати вбудований функціонал Swifts, щоб знайти власні об’єкти в масиві.

Спочатку ви повинні переконатися, що ваш власний об'єкт відповідає протоколу : Equatable .

class Person : Equatable { //<--- Add Equatable protocol
    let name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    //Add Equatable functionality:
    static func == (lhs: Person, rhs: Person) -> Bool {
        return (lhs.name == rhs.name)
    }
}

Завдяки функціональності Equatable, що додається до вашого об’єкта, Swift тепер покаже вам додаткові властивості, які ви можете використовувати в масиві:

//create new array and populate with objects:
let p1 = Person(name: "Paul", age: 20)
let p2 = Person(name: "Mike", age: 22)
let p3 = Person(name: "Jane", age: 33)
var people = [Person]([p1,p2,p3])

//find index by object:
let index = people.index(of: p2)! //finds Index of Mike

//remove item by index:
people.remove(at: index) //removes Mike from array


0

Наприклад, якщо у нас був масив чисел:

let numbers = [2, 4, 6, 8, 9, 10]

Ми можемо знайти перше непарне число на зразок цього:

let firstOdd = numbers.index { $0 % 2 == 1 }

Це посилає назад 4 як необов'язкове ціле число, оскільки перше непарне число (9) знаходиться в індексі чотири.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.