Skip to content

Commit f4bf7a4

Browse files
committed
Merge pull request #118 from HeMet/master
Implement NSArray.indexOfObject(inSortedRange:options:usingComparator)
2 parents aa3f7a2 + 7fc24e5 commit f4bf7a4

File tree

3 files changed

+132
-1
lines changed

3 files changed

+132
-1
lines changed

Foundation/NSArray.swift

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,70 @@ public class NSArray : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NS
411411
return sortedArrayFromRange(NSMakeRange(0, count), options: opts, usingComparator: cmptr)
412412
}
413413

414-
public func indexOfObject(obj: AnyObject, inSortedRange r: NSRange, options opts: NSBinarySearchingOptions, usingComparator cmp: NSComparator) -> Int { NSUnimplemented() } // binary search
414+
public func indexOfObject(obj: AnyObject, inSortedRange r: NSRange, options opts: NSBinarySearchingOptions, usingComparator cmp: NSComparator) -> Int {
415+
guard (r.location + r.length) <= count else {
416+
NSInvalidArgument("range \(r) extends beyond bounds [0 .. \(count - 1)]")
417+
}
418+
419+
if opts.contains(.FirstEqual) && opts.contains(.LastEqual) {
420+
NSInvalidArgument("both NSBinarySearching.FirstEqual and NSBinarySearching.LastEqual options cannot be specified")
421+
}
422+
423+
let firstEqual = opts.contains(.FirstEqual)
424+
let lastEqual = opts.contains(.LastEqual)
425+
let anyEqual = !(firstEqual || lastEqual)
426+
427+
var result = NSNotFound
428+
var indexOfLeastGreaterThanObj = NSNotFound
429+
var start = r.location
430+
var end = r.location + r.length - 1
431+
432+
loop: while start <= end {
433+
let middle = start + (end - start) / 2
434+
let item = objectAtIndex(middle)
435+
436+
switch cmp(item, obj) {
437+
438+
case .OrderedSame where anyEqual:
439+
result = middle
440+
break loop
441+
442+
case .OrderedSame where lastEqual:
443+
result = middle
444+
fallthrough
445+
446+
case .OrderedAscending:
447+
start = middle + 1
448+
449+
case .OrderedSame where firstEqual:
450+
result = middle
451+
fallthrough
452+
453+
case .OrderedDescending:
454+
indexOfLeastGreaterThanObj = middle
455+
end = middle - 1
456+
457+
default:
458+
fatalError("Implementation error.")
459+
}
460+
}
461+
462+
guard opts.contains(.InsertionIndex) && lastEqual else {
463+
return result
464+
}
465+
466+
guard result == NSNotFound else {
467+
return result + 1
468+
}
469+
470+
guard indexOfLeastGreaterThanObj != NSNotFound else {
471+
return count
472+
}
473+
474+
return indexOfLeastGreaterThanObj
475+
}
476+
477+
415478

416479
public convenience init?(contentsOfFile path: String) { NSUnimplemented() }
417480
public convenience init?(contentsOfURL url: NSURL) { NSUnimplemented() }

Foundation/NSObjCRuntime.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ public let NSNotFound: Int = Int.max
8080
fatalError("\(fn) is not yet implemented")
8181
}
8282

83+
@noreturn func NSInvalidArgument(message: String, method: String = __FUNCTION__) {
84+
fatalError("\(method): \(message)")
85+
}
86+
8387
internal struct _CFInfo {
8488
// This must match _CFRuntimeBase
8589
var info: UInt32

TestFoundation/TestNSArray.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class TestNSArray : XCTestCase {
2727
("test_enumeration", test_enumeration),
2828
("test_sequenceType", test_sequenceType),
2929
("test_getObjects", test_getObjects),
30+
("test_binarySearch", test_binarySearch)
3031
]
3132
}
3233

@@ -84,4 +85,67 @@ class TestNSArray : XCTestCase {
8485
XCTAssertEqual(fetched, ["bar", "baz", "foo1"])
8586
}
8687

88+
func test_binarySearch() {
89+
let array = NSArray(array: [
90+
NSNumber(int: 0), NSNumber(int: 1), NSNumber(int: 2), NSNumber(int: 2), NSNumber(int: 3),
91+
NSNumber(int: 4), NSNumber(int: 4), NSNumber(int: 6), NSNumber(int: 7), NSNumber(int: 7),
92+
NSNumber(int: 7), NSNumber(int: 8), NSNumber(int: 9), NSNumber(int: 9)])
93+
94+
// Not sure how to test fatal errors.
95+
96+
// NSArray throws NSInvalidArgument if range exceeds bounds of the array.
97+
// let rangeOutOfArray = NSRange(location: 5, length: 15)
98+
// let _ = array.indexOfObject(NSNumber(integer: 9), inSortedRange: rangeOutOfArray, options: [.InsertionIndex, .FirstEqual], usingComparator: compareIntNSNumber)
99+
100+
// NSArray throws NSInvalidArgument if both .FirstEqual and .LastEqaul are specified
101+
// let searchForBoth: NSBinarySearchingOptions = [.FirstEqual, .LastEqual]
102+
// let _ = objectIndexInArray(array, value: 9, startingFrom: 0, length: 13, options: searchForBoth)
103+
104+
let notFound = objectIndexInArray(array, value: 11, startingFrom: 0, length: 13)
105+
XCTAssertEqual(notFound, NSNotFound, "NSArray return NSNotFound if object is not found.")
106+
107+
let notFoundInRange = objectIndexInArray(array, value: 7, startingFrom: 0, length: 5)
108+
XCTAssertEqual(notFoundInRange, NSNotFound, "NSArray return NSNotFound if object is not found.")
109+
110+
let indexOfAnySeven = objectIndexInArray(array, value: 7, startingFrom: 0, length: 13)
111+
XCTAssertTrue(Set([8, 9, 10]).contains(indexOfAnySeven), "If no options provided NSArray returns an arbitrary matching object's index.")
112+
113+
let indexOfFirstNine = objectIndexInArray(array, value: 9, startingFrom: 7, length: 6, options: [.FirstEqual])
114+
XCTAssertTrue(indexOfFirstNine == 12, "If .FirstEqual is set NSArray returns the lowest index of equal objects.")
115+
116+
let indexOfLastTwo = objectIndexInArray(array, value: 2, startingFrom: 1, length: 7, options: [.LastEqual])
117+
XCTAssertTrue(indexOfLastTwo == 3, "If .LastEqual is set NSArray returns the highest index of equal objects.")
118+
119+
let anyIndexToInsertNine = objectIndexInArray(array, value: 9, startingFrom: 0, length: 13, options: [.InsertionIndex])
120+
XCTAssertTrue(Set([12, 13, 14]).contains(anyIndexToInsertNine), "If .InsertionIndex is specified and no other options provided NSArray returns any equal or one larger index than any matching object’s index.")
121+
122+
let lowestIndexToInsertTwo = objectIndexInArray(array, value: 2, startingFrom: 0, length: 5, options: [.InsertionIndex, .FirstEqual])
123+
XCTAssertTrue(lowestIndexToInsertTwo == 2, "If both .InsertionIndex and .FirstEqual are specified NSArray returns the lowest index of equal objects.")
124+
125+
let highestIndexToInsertNine = objectIndexInArray(array, value: 9, startingFrom: 7, length: 6, options: [.InsertionIndex, .LastEqual])
126+
XCTAssertTrue(highestIndexToInsertNine == 13, "If both .InsertionIndex and .LastEqual are specified NSArray returns the index of the least greater object...")
127+
128+
let indexOfLeastGreaterObjectThanFive = objectIndexInArray(array, value: 5, startingFrom: 0, length: 10, options: [.InsertionIndex, .LastEqual])
129+
XCTAssertTrue(indexOfLeastGreaterObjectThanFive == 7, "If both .InsertionIndex and .LastEqual are specified NSArray returns the index of the least greater object...")
130+
131+
let endOfArray = objectIndexInArray(array, value: 10, startingFrom: 0, length: 13, options: [.InsertionIndex, .LastEqual])
132+
XCTAssertTrue(endOfArray == array.count, "...or the index at the end of the array if the object is larger than all other elements.")
133+
}
134+
135+
func objectIndexInArray(array: NSArray, value: Int, startingFrom: Int, length: Int, options: NSBinarySearchingOptions = []) -> Int {
136+
return array.indexOfObject(NSNumber(integer: value), inSortedRange: NSRange(location: startingFrom, length: length), options: options, usingComparator: compareIntNSNumber)
137+
}
138+
139+
func compareIntNSNumber(lhs: AnyObject, rhs: AnyObject) -> NSComparisonResult {
140+
let lhsInt = (lhs as! NSNumber).integerValue
141+
let rhsInt = (rhs as! NSNumber).integerValue
142+
if lhsInt == rhsInt {
143+
return .OrderedSame
144+
}
145+
if lhsInt < rhsInt {
146+
return .OrderedAscending
147+
}
148+
149+
return .OrderedDescending
150+
}
87151
}

0 commit comments

Comments
 (0)