Sweeper
Sweeper

Reputation: 272895

Why does IndexSet say that a new element is inserted even though the element is already in the set?

Using Sets, I can conveniently insert an element into the set, and also check whether the element was in the set using one call to insert:

let array = [1,2,3,4,4,2,5,3,6,7,1]
var set = Set<Int>()
for item in array {
    // set.insert not only inserts the item, but also tells me whether the item was in set before the insert
    if set.insert(item).inserted {
        print("Encountered new item: \(item)")
    } else {
        print("\(item) has already been encountered!")
    }
}

Output:

Encountered new item: 1
Encountered new item: 2
Encountered new item: 3
Encountered new item: 4
4 has already been encountered!
2 has already been encountered!
Encountered new item: 5
3 has already been encountered!
Encountered new item: 6
Encountered new item: 7
1 has already been encountered!

However, if I rewrite the the same logic using IndexSet:

let array = [1,2,3,4,4,2,5,3,6,7,1]
var set = IndexSet()
for item in array {
    // set.insert not only inserts the item, but also tells me whether the item was in set before the insert
    if set.insert(item).inserted {
        print("Encountered new item: \(item)")
    } else {
        print("\(item) has already been encountered!")
    }
}

The output becomes:

Encountered new item: 1
Encountered new item: 2
Encountered new item: 3
Encountered new item: 4
Encountered new item: 4
Encountered new item: 2
Encountered new item: 5
Encountered new item: 3
Encountered new item: 6
Encountered new item: 7
Encountered new item: 1

It appears that the tuple returned by IndexSet.insert always matches (true, _), but even so, the set's count is not increased. I must check set.contains(item) before insert to produce the desired output.

Question: Is this intended behaviour for IndexSet?

I know that IndexSet is a Cocoa API, and not native to Swift, so I thought perhaps there is some special semantics that IndexSet has about what it means for two numbers to be "the same" that I'm not aware of. I also looked for bug reports on bugs.swift.org about IndexSet, but I didn't find anything about insert always returning true.

Upvotes: 3

Views: 177

Answers (1)

Martin R
Martin R

Reputation: 539965

IndexSet is a the Swift value overlay type for NSIndexSet and NSMutableIndexSet. As can be seen in the implementation in IndexSet.swift, all operations on the IndexSet are forwarded to NS(Mutable)IndexSet.

// Temporary boxing function, until we can get a native Swift type for NSIndexSet
@inline(__always)
mutating func _applyMutation<ReturnType>(_ whatToDo : (NSMutableIndexSet) throws -> ReturnType) rethrows -> ReturnType {

    // ...
}

Apparently that does not allow to determine if an inserted already exists, this is marked as “TODO” in the source code:

/// Insert an integer into the `IndexSet`.
@discardableResult
public mutating func insert(_ integer: Element) -> (inserted: Bool, memberAfterInsert: Element) {
    _applyMutation { $0.add(integer) }
    // TODO: figure out how to return the truth here
    return (true, integer)
}

So IndexSet.insert(value) calls NSMutableIndexSet.add(value), which adds the new value to the index set if it is not present. It behaves correctly in so far as the indices are unique, but the inserted return value is always true.

Upvotes: 3

Related Questions