如何使用indexOfObject或正确使用containsObject?

20

对于一个数组:如何实现indexOfObjectcontainsObject方法?

我的意思是,我知道可以将该数组桥接到NSArray中并在那里执行这些方法^^
但肯定有一种“本地”的方法来实现此操作。

附:对于containsObject方法,我想我也可以过滤数组,但是对于indexOf方法呢?


是的,除非它是相反的最大值;这个比另一个更早,并且有更多的信息。 - Daij-Djan
对不起,你是正确的,我复制粘贴错了 :-( 现在已经更新了! - Max MacLeod
8个回答

44
您可以使用内置的find函数,从而避免桥接到Objective-C——但前提是您的元素类型是可比较的。(如果它不是可比较的,您可以通过比较函数和扩展来使其可比较。)
示例:
func == (lhs:Piece,rhs:Piece) -> Bool {
    return lhs.val == rhs.val
}

class Piece:Equatable,Printable {
    var val : Int
    var description : String { return String(val) }
    init (_ v:Int) {
        val = v
    }
}

现在你可以调用find(arr,p),其中arr是一个Array<Piece>,而p是一个Piece
有了这个,你就可以开发基于它的实用程序。例如,这里有一个全局函数,可以从数组中删除一个对象,而不需要桥接到Objective-C:
func removeObject<T:Equatable>(inout arr:Array<T>, object:T) -> T? {
    if let found = find(arr,object) {
        return arr.removeAtIndex(found)
    }
    return nil
}

然后像这样测试它:

var arr = [Piece(1), Piece(2), Piece(3)]
removeObject(&arr,Piece(2))
println(arr)

您可以对NSObject的子类进行相同的操作,例如:
func == (v1:UIView, v2:UIView) -> Bool {
    return v1.isEqual(v2)
}
extension UIView : Equatable {}

现在你可以在UIView数组上调用find。但是,如果您想在每个要使用该类的数组上使用find,这可能有点麻烦。我已向Apple提交了增强请求,请求将所有NSObject子类视为可比较,并且==应自动回退到isEqual:
编辑:从Seed 3开始,对于UIView和其他NSObject类,这是自动的。因此,find现在适用于它们。
编辑2:从Swift 2.0开始,indexOf将作为一种方法存在:
let s = ["Manny", "Moe", "Jack"]
let ix = s.indexOf("Moe") // 1

或者,它可以接受一个返回布尔值的函数:

let ix2 = s.indexOf {$0.hasPrefix("J")} // 2

需要注意的是,这个方法只适用于实现了Equatable协议的集合类型。显然,如果没有办法在找到一个元素时对其进行识别,那么就无法在集合中查找该元素。

编辑 3 Swift 2.0还引入了协议扩展。这意味着我可以将全局函数removeObject重写为一个方法!

例如:

extension RangeReplaceableCollectionType where Generator.Element : Equatable {
    mutating func removeObject(object:Self.Generator.Element) {
        if let found = self.indexOf(object) {
            self.removeAtIndex(found)
        }
    }
}

由于数组采用了RangeReplaceableCollectionType,因此现在我可以编写以下代码:

var arr = [Piece(1), Piece(2), Piece(3)]
arr.removeObject(Piece(2))

哦,愉快的一天!


我应该在哪个作用域中声明可比较函数? - Just a coder
大约在Swift 3或4左右,indexOf()变成了index(of:) - ThomasW

9

实际上,这可以用Swift完成。要获取索引,请使用find(YourArray, ObjectToFind)


不幸的是,有许多常见情况下这种方法都行不通。 - matt
1
就像每当ObjectToFind不是可比较的一样。然而,现在情况比我在六月份说那句话时要好得多! - matt

4
据我所知,这还不可用/我需要将其桥接到NSArray。
我不喜欢这样做,感觉很不舒服,所以我用扩展实现了它。这样就隐藏了对NSArray的使用,让苹果以后可以提供它。
import Foundation

extension Array {
    func contains(object:AnyObject!) -> Bool {
        if(self.isEmpty) {
            return false
        }
        let array: NSArray = self.bridgeToObjectiveC();

        return array.containsObject(object)
    }

    func indexOf(object:AnyObject!) -> Int? {
        var index = NSNotFound
        if(!self.isEmpty) {
            let array: NSArray = self.bridgeToObjectiveC();
            index = array.indexOfObject(object)
        }
        if(index == NSNotFound) {
            return Optional.None;
        }
        return index
    }

    //#pragma mark KVC

    func getKeyPath(keyPath: String!) -> AnyObject! {
        return self.bridgeToObjectiveC().valueForKeyPath(keyPath);
    }
}

https://gist.github.com/Daij-Djan/9d1c4b1233b4017f3b67


请注意,在对象前面删除 #。根据苹果的标准,第一个参数应该在函数/方法调用中进行自我描述。 - TheCodingArt
我还注意到了一件事。func contains(object: AnyObject) -> Bool? ... 因为 Obj-c 桥接返回可选项,所以您需要通过添加 ? 来明确说明这一点。 - TheCodingArt
无论如何,实际的返回值始终是可选的。如果您运行代码并阅读方法,仍然必须取消包装可选项,遗憾地说... - TheCodingArt
我刚刚运行了代码,如果您愿意,我可以粘贴一张屏幕截图? - TheCodingArt
1
我刚才发了上面的内容,如果你不检查这个问题,你会遇到一些不必要的bug,因为“可选”存在并且可以被取消包装。我只是想帮助大家避免一些麻烦,但好吧XD。除非他们更新这个问题,否则你们只能通过艰难的方式学习。 - TheCodingArt
显示剩余3条评论

2

另一个选择是使用过滤器:

haystack.filter({$0 == needle}).count > 0

该函数检查数组 haystack 中是否包含元素 needle。


2
苹果在《Swift编程语言》书中提供了一个确切的例子。具体来说,请参阅“Type Constraints in Action”部分(iBook中的p621)。
func findIndex<T: Equatable>(array: [T], valueToFind: T) -> Int? {
    for (index, value) in enumerate(array) {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

一切都取决于您的类型实现Equatable
Swift编程语言涵盖了这一点,并解释了如何实现该协议:
“Swift标准库定义了一个名为Equatable的协议,要求任何符合该协议的类型实现相等运算符(==)和不相等运算符(!=)来比较该类型的任意两个值。” NSHipster有几篇相关的文章: Swift默认协议实现 Swift比较协议 我还发现了这个答案在实现Equatable方面非常有用: 如何实现Swift的Comparable协议? 尽管它提到了Comparable,但Equatable是其中的一个子集,解释也很好。以上摘自:Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/gb/jEUH0.l

一些代码样例可能有所帮助,但鉴于苹果的Swift书籍已涵盖此内容,将其包含在其中不会更好吗?同时参考他们的解释?Matt的回答很好,但对我个人来说并没有帮助。只有在我阅读了Swift指南和其他资料之后才能理解它;这就是我的答案。 - Max MacLeod

1
你不能。这就是为什么 NSArray 仍然存在的原因。但是,关于 StringNSString,苹果文档如下所述:
Swift 的 String 类型与 Foundation 的 NSString 类无缝桥接。如果您正在使用 Cocoa 或 Cocoa Touch 中的 Foundation 框架,则可以在创建的任何 String 值上调用整个 NSString API,除了本章描述的 String 特性之外。您还可以将 String 值与需要 NSString 实例的任何 API 一起使用。
按照这种方法,Array 应该可以使用 NSArray API,但事实并非如此,因为本地 Swift Array 是一个基元(很可能是结构体或类似结构),所以您必须将其“转换”为 NSArray

我之前说过我可以解决这个问题。虽然一开始不太相信,但现在看来我确实做到了。 :D - Daij-Djan

1

看起来并不是所有的NS/CF空间的免费桥接都已经到位。但是,如果您将数组声明为NSArray,则可以正常工作:

let     fruits: NSArray = [ "apple", "orange", "tomato (really?)" ]
let     index = fruits.indexOfObject("orange")

println("Index of Orange: \(index)")

我之前说过我可以解决这个问题。我当时不想相信自己能做到:D - Daij-Djan

-2
如果您的数组元素是对象,并且您想在该数组中查找相同的对象,则可以使用此函数:
func findObject<C: CollectionType where C.Generator.Element: AnyObject>(domain: C, value: C.Generator.Element) -> Int? {
    for (index, element) in enumerate(domain) {
        if element === value {
            return index
        }
    }

    return nil
}

1
有一个名为“indexOfObjectIdenticalTo”的数组函数,可以获取您的元素的索引,例如indexOfSelectedObject = arrSelectedIndex.indexOfObjectIdenticalTo(indexPath.row)。 - Anny

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接