Найти объект в массиве?

144

Есть ли у Swift что-то вроде _.findWhere в Underscore.js?

У меня есть массив структур типа Tи хотел бы проверить, содержит ли массив объект структуры, nameсвойство которого равно Foo.

Пытались использовать find()и , filter()но они работают только с примитивными типами, например , Stringили Int. Выдает ошибку о несоответствии Equitableпротоколу или что-то в этом роде.

Сахат Ялкабов
источник
Это может быть то, что вы ищете: Найти объект со свойством в массиве .
Мартин Р
почему бы не конвертировать в NSDictionary и поиск
лонгбоу
3
Я считаю, что find больше недоступен в 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"}), если вам нужен объект
Бретт,
Как я могу проверить, что $ 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
Хороший ответ в стиле Swift!
Золтан
4
Спасибо, ты мой вкладчик, это сильно очистило мой код
AD Progress
как проверить несколько условий, скажите, что мне нужно проверить, выполняет ли массив $0.name == "foo"одну операцию и $0.name == "boo" другую операцию
Midhun Narayan
Это помогло мне больше, чем принятый ответ в 2020 году.
Псилок
Как я могу проверить, что $ 0.name содержит строку "foo"? Кто-нибудь может дать синтаксис для этого
Азик Абдулла
131

Вы можете использовать indexметод Arrayс предикатом ( см. Документацию Apple здесь ).

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

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

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

Swift 2.0

if let i = array.indexOf({ $0.name == Foo }) {
    return array[i]
}
user3799504
источник
3
Да, это работает только с Swift 2.0. Извините, должен был упомянуть об этом.
user3799504
@ user3799504 означает $ 0?
Прамод Тапания
$ 0 является сокращением для первого аргумента к закрытию. В этом случае это относится к self.generator.element или каждому элементу массива. Пожалуйста, смотрите: developer.apple.com/library/ios/documentation/Swift/Conceptual/…
user3799504
1
Как насчет Swift 3?
Аднако
38

Swift 3

Если вам нужен объект, используйте:

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

(Если у вас есть более одного объекта с именем «Foo», тогда firstвернет первый объект из неопределенного порядка)

Brett
источник
Спасибо тебе за это. Это должно быть там!
NerdyTherapist
3
Это мило, спасибо! Также можно написать так:array.first {$0.name == "Foo"}
Роланд Т.
2
В Swift3 это должно бытьarray.first(where: {$0.name == "Foo"})
Даниил
С примечанием Даниэля, это правильный и лучший ответ для Swift 3. Не используйте карту, фильтруйте для этой цели; они перебирают целые коллекции, что может быть чрезвычайно расточительным.
Уомбл
20

Вы можете отфильтровать массив, а затем просто выбрать первый элемент, как показано в разделе « Найти объект со свойством в массиве» .

Или вы определяете пользовательское расширение

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: я не знаю, вам придется измерить производительность и сравнить.
Мартин Р
@PatNiemeyer решение выше, безусловно, будет более эффективным, хотя разница, вероятно, не будет большой. 1. Сложность filterвсегда равна, O(n)тогда как в findFirstMatchingтолько в худшем случае (когда искомый элемент является последним или не находится в массиве вообще). 2. filterсоздает совершенно новый массив фильтруемых элементов, в то время как findFirstMatchingjust возвращает запрошенный элемент.
0101
В Swift 3 я получаю ошибки Inheritance от не-протокола, не относящегося к классу типа 'Bool' и использования необъявленного типа 'T' для этого метода расширения.
Isuru
@Isuru: Этот ответ был довольно стар и ссылался на старую версию Swift. В Swift 3 вам больше не нужен пользовательский метод расширения для этой цели, я соответственно обновил ответ.
Мартин Р
20

Swift 3.0

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

Swift 2.1

Фильтрация в свойствах объекта теперь поддерживается в Swift 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

Swift 4 ,

Еще один способ добиться этого с помощью функции фильтра,

if let object = elements.filter({ $0.title == "title" }).first {
    print("found")
} else {
    print("not found")
}
Прамод Море
источник
8

Swift 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]
}
Деохюн Ко
источник
В swift 3 есть метод, где вы можете найти индексы подсписка элементов массива, которые удовлетворяют условию $0.name == "Foo"?
Ахмед Хедр
3

Swift 3

if yourArray.contains(item) {
   //item found, do what you want
}
else{
   //item not found 
   yourArray.append(item)
}
yonlau
источник
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 (of: Any) - объявить ваш объект

import Foundation
class Model: NSObject {  }
Габи Дорофтей
источник
1

Используйте Доллар, который является Lo-Dash или Underscore.js для Swift:

import Dollar

let found = $.find(array) { $0.name == "Foo" }
Тайлер Лонг
источник
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
Джордж Филиппакос
источник
1

Для Swift 3,

let index = array.index(where: {$0.name == "foo"})
Амаль ТС
источник
0

Например, если у нас был массив чисел:

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

Мы могли бы найти первое нечетное число, как это:

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

Это отправит обратно 4 в качестве необязательного целого числа, потому что первое нечетное число (9) находится в индексе четыре.

Amul4608
источник