diff --git a/swift/Index+Sugar.swift b/swift/Index+Sugar.swift index 7056891e..7dd7af3b 100644 --- a/swift/Index+Sugar.swift +++ b/swift/Index+Sugar.swift @@ -18,9 +18,9 @@ extension USearchIndex { /// - Parameter key: Unique identifier for that object. /// - Parameter vector: Single-precision vector. /// - Throws: If runs out of memory. - public func add(key: USearchKey, vector: ArraySlice<Float32>) { - vector.withContiguousStorageIfAvailable { - addSingle(key: key, vector: $0.baseAddress!) + public func add(key: USearchKey, vector: ArraySlice<Float32>) throws { + try vector.withContiguousStorageIfAvailable { + try addSingle(key: key, vector: $0.baseAddress!) } } @@ -28,8 +28,8 @@ extension USearchIndex { /// - Parameter key: Unique identifier for that object. /// - Parameter vector: Single-precision vector. /// - Throws: If runs out of memory. - public func add(key: USearchKey, vector: [Float32]) { - add(key: key, vector: vector[...]) + public func add(key: USearchKey, vector: [Float32]) throws { + try add(key: key, vector: vector[...]) } /// Approximate nearest neighbors search. @@ -37,11 +37,11 @@ extension USearchIndex { /// - Parameter count: Upper limit on the number of matches to retrieve. /// - Returns: Labels and distances to closest approximate matches in decreasing similarity order. /// - Throws: If runs out of memory. - public func search(vector: ArraySlice<Float32>, count: Int) -> ([Key], [Float]) { + public func search(vector: ArraySlice<Float32>, count: Int) throws -> ([Key], [Float]) { var matches: [Key] = Array(repeating: 0, count: count) var distances: [Float] = Array(repeating: 0, count: count) - let results = vector.withContiguousStorageIfAvailable { - searchSingle(vector: $0.baseAddress!, count: CUnsignedInt(count), keys: &matches, distances: &distances) + let results = try vector.withContiguousStorageIfAvailable { + try searchSingle(vector: $0.baseAddress!, count: CUnsignedInt(count), keys: &matches, distances: &distances) } matches.removeLast(count - Int(results!)) distances.removeLast(count - Int(results!)) @@ -53,8 +53,8 @@ extension USearchIndex { /// - Parameter count: Upper limit on the number of matches to retrieve. /// - Returns: Labels and distances to closest approximate matches in decreasing similarity order. /// - Throws: If runs out of memory. - public func search(vector: [Float32], count: Int) -> ([Key], [Float]) { - return search(vector: vector[...], count: count) + public func search(vector: [Float32], count: Int) throws -> ([Key], [Float]) { + return try search(vector: vector[...], count: count) } /// Retrieve vectors for a given key. @@ -62,11 +62,11 @@ extension USearchIndex { /// - Parameter count: For multi-indexes, Number of vectors to retrieve. Defaults to 1. /// - Returns: Two-dimensional array of Single-precision vectors. /// - Throws: If runs out of memory. - public func get(key: USearchKey, count: Int = 1) -> [[Float]]? { + public func get(key: USearchKey, count: Int = 1) throws -> [[Float]]? { var vector: [Float] = Array(repeating: 0.0, count: Int(self.dimensions) * count) - let returnedCount = vector.withContiguousMutableStorageIfAvailable { buf in + let returnedCount = try vector.withContiguousMutableStorageIfAvailable { buf in guard let baseAddress = buf.baseAddress else { return UInt32(0) } - return getSingle( + return try getSingle( key: key, vector: baseAddress, count: CUnsignedInt(count) @@ -86,9 +86,9 @@ extension USearchIndex { /// - Parameter key: Unique identifier for that object. /// - Parameter vector: Double-precision vector. /// - Throws: If runs out of memory. - public func add(key: Key, vector: ArraySlice<Float64>) { - vector.withContiguousStorageIfAvailable { - addDouble(key: key, vector: $0.baseAddress!) + public func add(key: Key, vector: ArraySlice<Float64>) throws { + try vector.withContiguousStorageIfAvailable { + try addDouble(key: key, vector: $0.baseAddress!) } } @@ -96,8 +96,8 @@ extension USearchIndex { /// - Parameter key: Unique identifier for that object. /// - Parameter vector: Double-precision vector. /// - Throws: If runs out of memory. - public func add(key: Key, vector: [Float64]) { - add(key: key, vector: vector[...]) + public func add(key: Key, vector: [Float64]) throws { + try add(key: key, vector: vector[...]) } /// Approximate nearest neighbors search. @@ -105,11 +105,11 @@ extension USearchIndex { /// - Parameter count: Upper limit on the number of matches to retrieve. /// - Returns: Labels and distances to closest approximate matches in decreasing similarity order. /// - Throws: If runs out of memory. - public func search(vector: ArraySlice<Float64>, count: Int) -> ([Key], [Float]) { + public func search(vector: ArraySlice<Float64>, count: Int) throws -> ([Key], [Float]) { var matches: [Key] = Array(repeating: 0, count: count) var distances: [Float] = Array(repeating: 0, count: count) - let results = vector.withContiguousStorageIfAvailable { - searchDouble(vector: $0.baseAddress!, count: CUnsignedInt(count), keys: &matches, distances: &distances) + let results = try vector.withContiguousStorageIfAvailable { + try searchDouble(vector: $0.baseAddress!, count: CUnsignedInt(count), keys: &matches, distances: &distances) } matches.removeLast(count - Int(results!)) distances.removeLast(count - Int(results!)) @@ -121,8 +121,8 @@ extension USearchIndex { /// - Parameter count: Upper limit on the number of matches to retrieve. /// - Returns: Labels and distances to closest approximate matches in decreasing similarity order. /// - Throws: If runs out of memory. - public func search(vector: [Float64], count: Int) -> ([Key], [Float]) { - search(vector: vector[...], count: count) + public func search(vector: [Float64], count: Int) throws -> ([Key], [Float]) { + try search(vector: vector[...], count: count) } /// Retrieve vectors for a given key. @@ -130,11 +130,11 @@ extension USearchIndex { /// - Parameter count: For multi-indexes, Number of vectors to retrieve. Defaults to 1. /// - Returns: Two-dimensional array of Double-precision vectors. /// - Throws: If runs out of memory. - public func get(key: USearchKey, count: Int = 1) -> [[Float64]]? { + public func get(key: USearchKey, count: Int = 1) throws -> [[Float64]]? { var vector: [Float64] = Array(repeating: 0.0, count: Int(self.dimensions) * count) - let count = vector.withContiguousMutableStorageIfAvailable { buf in + let count = try vector.withContiguousMutableStorageIfAvailable { buf in guard let baseAddress = buf.baseAddress else { return UInt32(0) } - return getDouble( + return try getDouble( key: key, vector: baseAddress, count: CUnsignedInt(count) @@ -156,12 +156,12 @@ extension USearchIndex { /// - Parameter filter: Closure used to determine whether to skip a key in the results. /// - Returns: Labels and distances to closest approximate matches in decreasing similarity order. /// - Throws: If runs out of memory. - public func filteredSearch(vector: ArraySlice<Float32>, count: Int, filter: @escaping FilterFn) -> ([Key], [Float]) + public func filteredSearch(vector: ArraySlice<Float32>, count: Int, filter: @escaping FilterFn) throws -> ([Key], [Float]) { var matches: [Key] = Array(repeating: 0, count: count) var distances: [Float] = Array(repeating: 0, count: count) - let results = vector.withContiguousStorageIfAvailable { - filteredSearchSingle( + let results = try vector.withContiguousStorageIfAvailable { + try filteredSearchSingle( vector: $0.baseAddress!, count: CUnsignedInt(count), @@ -181,8 +181,8 @@ extension USearchIndex { /// - Parameter filter: Closure used to determine whether to skip a key in the results. /// - Returns: Labels and distances to closest approximate matches in decreasing similarity order. /// - Throws: If runs out of memory. - public func filteredSearch(vector: [Float32], count: Int, filter: @escaping FilterFn) -> ([Key], [Float]) { - filteredSearch(vector: vector[...], count: count, filter: filter) + public func filteredSearch(vector: [Float32], count: Int, filter: @escaping FilterFn) throws -> ([Key], [Float]) { + try filteredSearch(vector: vector[...], count: count, filter: filter) } /// Approximate nearest neighbors search. @@ -191,12 +191,12 @@ extension USearchIndex { /// - Parameter filter: Closure used to determine whether to skip a key in the results. /// - Returns: Labels and distances to closest approximate matches in decreasing similarity order. /// - Throws: If runs out of memory. - public func filteredSearch(vector: ArraySlice<Float64>, count: Int, filter: @escaping FilterFn) -> ([Key], [Float]) + public func filteredSearch(vector: ArraySlice<Float64>, count: Int, filter: @escaping FilterFn) throws -> ([Key], [Float]) { var matches: [Key] = Array(repeating: 0, count: count) var distances: [Float] = Array(repeating: 0, count: count) - let results = vector.withContiguousStorageIfAvailable { - filteredSearchDouble( + let results = try vector.withContiguousStorageIfAvailable { + try filteredSearchDouble( vector: $0.baseAddress!, count: CUnsignedInt(count), @@ -216,8 +216,8 @@ extension USearchIndex { /// - Parameter filter: Closure used to determine whether to skip a key in the results. /// - Returns: Labels and distances to closest approximate matches in decreasing similarity order. /// - Throws: If runs out of memory. - public func filteredSearch(vector: [Float64], count: Int, filter: @escaping FilterFn) -> ([Key], [Float]) { - filteredSearch(vector: vector[...], count: count, filter: filter) + public func filteredSearch(vector: [Float64], count: Int, filter: @escaping FilterFn) throws -> ([Key], [Float]) { + try filteredSearch(vector: vector[...], count: count, filter: filter) } #if arch(arm64) @@ -227,9 +227,9 @@ extension USearchIndex { /// - Parameter vector: Half-precision vector. /// - Throws: If runs out of memory. @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) - public func add(key: Key, vector: ArraySlice<Float16>) { - vector.withContiguousStorageIfAvailable { buffer in - addHalf(key: key, vector: buffer.baseAddress!) + public func add(key: Key, vector: ArraySlice<Float16>) throws { + try vector.withContiguousStorageIfAvailable { buffer in + try addHalf(key: key, vector: buffer.baseAddress!) } } @@ -238,8 +238,8 @@ extension USearchIndex { /// - Parameter vector: Half-precision vector. /// - Throws: If runs out of memory. @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) - public func add(key: Key, vector: [Float16]) { - add(key: key, vector: vector[...]) + public func add(key: Key, vector: [Float16]) throws { + try add(key: key, vector: vector[...]) } /// Approximate nearest neighbors search. @@ -248,11 +248,11 @@ extension USearchIndex { /// - Returns: Labels and distances to closest approximate matches in decreasing similarity order. /// - Throws: If runs out of memory. @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) - public func search(vector: ArraySlice<Float16>, count: Int) -> ([Key], [Float]) { + public func search(vector: ArraySlice<Float16>, count: Int) throws -> ([Key], [Float]) { var matches: [Key] = Array(repeating: 0, count: count) var distances: [Float] = Array(repeating: 0, count: count) - let results = vector.withContiguousStorageIfAvailable { - searchHalf(vector: $0.baseAddress!, count: CUnsignedInt(count), keys: &matches, distances: &distances) + let results = try vector.withContiguousStorageIfAvailable { + try searchHalf(vector: $0.baseAddress!, count: CUnsignedInt(count), keys: &matches, distances: &distances) } matches.removeLast(count - Int(results!)) distances.removeLast(count - Int(results!)) @@ -265,8 +265,8 @@ extension USearchIndex { /// - Returns: Labels and distances to closest approximate matches in decreasing similarity order. /// - Throws: If runs out of memory. @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) - public func search(vector: [Float16], count: Int) -> ([Key], [Float]) { - search(vector: vector[...], count: count) + public func search(vector: [Float16], count: Int) throws -> ([Key], [Float]) { + try search(vector: vector[...], count: count) } /// Retrieve vectors for a given key. @@ -275,11 +275,11 @@ extension USearchIndex { /// - Returns: Two-dimensional array of Half-precision vectors. /// - Throws: If runs out of memory. @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) - public func get(key: USearchKey, count: Int = 1) -> [[Float16]]? { + public func get(key: USearchKey, count: Int = 1) throws -> [[Float16]]? { var vector: [Float16] = Array(repeating: 0.0, count: Int(self.dimensions) * count) - let count = vector.withContiguousMutableStorageIfAvailable { buf in + let count = try vector.withContiguousMutableStorageIfAvailable { buf in guard let baseAddress = buf.baseAddress else { return UInt32(0) } - return getSingle( + return try getSingle( key: key, vector: baseAddress, count: CUnsignedInt(count) diff --git a/swift/Test.swift b/swift/Test.swift index fbe655a0..05257a0e 100644 --- a/swift/Test.swift +++ b/swift/Test.swift @@ -1,5 +1,5 @@ // -// File.swift +// Test.swift // // // Created by Ash Vardanian on 5/11/23. @@ -12,7 +12,7 @@ import XCTest @available(iOS 13, macOS 10.15, tvOS 13.0, watchOS 6.0, *) class Test: XCTestCase { func testUnit() throws { - let index = USearchIndex.make( + let index = try USearchIndex.make( metric: USearchMetric.l2sq, dimensions: 4, connectivity: 8, @@ -20,37 +20,37 @@ class Test: XCTestCase { ) let vectorA: [Float32] = [0.3, 0.5, 1.2, 1.4] let vectorB: [Float32] = [0.4, 0.2, 1.2, 1.1] - index.reserve(2) + try index.reserve(2) // Adding a slice - index.add(key: 42, vector: vectorA[...]) + try index.add(key: 42, vector: vectorA[...]) // Adding a vector - index.add(key: 43, vector: vectorB) + try index.add(key: 43, vector: vectorB) - let results = index.search(vector: vectorA, count: 10) + let results = try index.search(vector: vectorA, count: 10) XCTAssertEqual(results.0[0], 42) - let fetched: [[Float]]? = index.get(key: 42) + let fetched: [[Float]]? = try index.get(key: 42) XCTAssertEqual(fetched?[0], vectorA) - XCTAssertTrue(index.contains(key: 42)) - XCTAssertEqual(index.count(key: 42), 1) - XCTAssertEqual(index.count(key: 49), 0) - index.rename(from: 42, to: 49) - XCTAssertEqual(index.count(key: 49), 1) + XCTAssertTrue(try index.contains(key: 42)) + XCTAssertEqual(try index.count(key: 42), 1) + XCTAssertEqual(try index.count(key: 49), 0) + try index.rename(from: 42, to: 49) + XCTAssertEqual(try index.count(key: 49), 1) - let refetched: [[Float]]? = index.get(key: 49) + let refetched: [[Float]]? = try index.get(key: 49) XCTAssertEqual(refetched?[0], vectorA) - let stale: [[Float]]? = index.get(key: 42) + let stale: [[Float]]? = try index.get(key: 42) XCTAssertNil(stale) - index.remove(key: 49) - XCTAssertEqual(index.count(key: 49), 0) + try index.remove(key: 49) + XCTAssertEqual(try index.count(key: 49), 0) } func testUnitMulti() throws { - let index = USearchIndex.make( + let index = try USearchIndex.make( metric: USearchMetric.l2sq, dimensions: 4, connectivity: 8, @@ -59,85 +59,85 @@ class Test: XCTestCase { ) let vectorA: [Float32] = [0.3, 0.5, 1.2, 1.4] let vectorB: [Float32] = [0.4, 0.2, 1.2, 1.1] - index.reserve(2) + try index.reserve(2) // Adding a slice - index.add(key: 42, vector: vectorA[...]) + try index.add(key: 42, vector: vectorA[...]) // Adding a vector - index.add(key: 42, vector: vectorB) + try index.add(key: 42, vector: vectorB) - let results = index.search(vector: vectorA, count: 10) + let results = try index.search(vector: vectorA, count: 10) XCTAssertEqual(results.0[0], 42) - let fetched: [[Float]]? = index.get(key: 42, count: 2) + let fetched: [[Float]]? = try index.get(key: 42, count: 2) XCTAssertEqual(fetched?.contains(vectorA), true) XCTAssertEqual(fetched?.contains(vectorB), true) - XCTAssertTrue(index.contains(key: 42)) - XCTAssertEqual(index.count(key: 42), 2) - XCTAssertEqual(index.count(key: 49), 0) - index.rename(from: 42, to: 49) - XCTAssertEqual(index.count(key: 49), 2) + XCTAssertTrue(try index.contains(key: 42)) + XCTAssertEqual(try index.count(key: 42), 2) + XCTAssertEqual(try index.count(key: 49), 0) + try index.rename(from: 42, to: 49) + XCTAssertEqual(try index.count(key: 49), 2) - let refetched: [[Float]]? = index.get(key: 49, count: 2) + let refetched: [[Float]]? = try index.get(key: 49, count: 2) XCTAssertEqual(refetched?.contains(vectorA), true) XCTAssertEqual(refetched?.contains(vectorB), true) - let stale: [[Float]]? = index.get(key: 42) + let stale: [[Float]]? = try index.get(key: 42) XCTAssertNil(stale) - index.remove(key: 49) - XCTAssertEqual(index.count(key: 49), 0) + try index.remove(key: 49) + XCTAssertEqual(try index.count(key: 49), 0) } - func testIssue399() { - let index = USearchIndex.make( + func testIssue399() throws { + let index = try USearchIndex.make( metric: USearchMetric.l2sq, dimensions: 1, connectivity: 8, quantization: USearchScalar.F32 ) - index.reserve(3) + try index.reserve(3) // add 3 entries then ensure all 3 are returned - index.add(key: 1, vector: [1.1]) - index.add(key: 2, vector: [2.1]) - index.add(key: 3, vector: [3.1]) + try index.add(key: 1, vector: [1.1]) + try index.add(key: 2, vector: [2.1]) + try index.add(key: 3, vector: [3.1]) XCTAssertEqual(index.count, 3) - XCTAssertEqual(index.search(vector: [1.0], count: 3).0, [1, 2, 3]) // works 😎 + XCTAssertEqual(try index.search(vector: [1.0], count: 3).0, [1, 2, 3]) // works 😎 // replace second-added entry then ensure all 3 are still returned - index.remove(key: 2) - index.add(key: 2, vector: [2.2]) + try index.remove(key: 2) + try index.add(key: 2, vector: [2.2]) XCTAssertEqual(index.count, 3) - XCTAssertEqual(index.search(vector: [1.0], count: 3).0, [1, 2, 3]) // works 😎 + XCTAssertEqual(try index.search(vector: [1.0], count: 3).0, [1, 2, 3]) // works 😎 // replace first-added entry then ensure all 3 are still returned - index.remove(key: 1) - index.add(key: 1, vector: [1.2]) - let afterReplacingInitial = index.search(vector: [1.0], count: 3).0 + try index.remove(key: 1) + try index.add(key: 1, vector: [1.2]) + let afterReplacingInitial = try index.search(vector: [1.0], count: 3).0 XCTAssertEqual(index.count, 3) XCTAssertEqual(afterReplacingInitial, [1, 2, 3]) // v2.11.7 fails with "[1] != [1, 2, 3]" 😨 } - func testFilteredSearchSingle() { - let index = USearchIndex.make( + func testFilteredSearchSingle() throws { + let index = try USearchIndex.make( metric: USearchMetric.l2sq, dimensions: 1, connectivity: 8, quantization: USearchScalar.F32 ) - index.reserve(3) + try index.reserve(3) // add 3 entries - index.add(key: 1, vector: [1.1]) - index.add(key: 2, vector: [2.1]) - index.add(key: 3, vector: [3.1]) + try index.add(key: 1, vector: [1.1]) + try index.add(key: 2, vector: [2.1]) + try index.add(key: 3, vector: [3.1]) XCTAssertEqual(index.count, 3) // filter which accepts all keys: XCTAssertEqual( - index.filteredSearch(vector: [1.0], count: 3) { + try index.filteredSearch(vector: [1.0], count: 3) { key in true }.0, [1, 2, 3] @@ -145,7 +145,7 @@ class Test: XCTestCase { // filter which rejects all keys: XCTAssertEqual( - index.filteredSearch(vector: [1.0], count: 3) { + try index.filteredSearch(vector: [1.0], count: 3) { key in false }.0, [] @@ -154,7 +154,7 @@ class Test: XCTestCase { // filter function accepts a set of keys passed in through a capture. let acceptedKeys: [USearchKey] = [1, 2] XCTAssertEqual( - index.filteredSearch(vector: [1.0], count: 3) { + try index.filteredSearch(vector: [1.0], count: 3) { key in acceptedKeys.contains(key) }.0, acceptedKeys @@ -163,37 +163,37 @@ class Test: XCTestCase { // filter function accepts a set of keys passed in through a capture, // and also adheres to the count. XCTAssertEqual( - index.filteredSearch(vector: [1.0], count: 1) { + try index.filteredSearch(vector: [1.0], count: 1) { key in key > 1 }.0, [2] ) // works 😎 XCTAssertEqual( - index.filteredSearch(vector: [1.0], count: 2) { + try index.filteredSearch(vector: [1.0], count: 2) { key in key > 1 }.0, [2, 3] ) // works 😎 } - func testFilteredSearchDouble() { - let index = USearchIndex.make( + func testFilteredSearchDouble() throws { + let index = try USearchIndex.make( metric: USearchMetric.l2sq, dimensions: 1, connectivity: 8, quantization: USearchScalar.F64 ) - index.reserve(3) + try index.reserve(3) // add 3 entries - index.add(key: 1, vector: [Float64(1.1)]) - index.add(key: 2, vector: [Float64(2.1)]) - index.add(key: 3, vector: [Float64(3.1)]) + try index.add(key: 1, vector: [Float64(1.1)]) + try index.add(key: 2, vector: [Float64(2.1)]) + try index.add(key: 3, vector: [Float64(3.1)]) XCTAssertEqual(index.count, 3) // filter which accepts all keys: XCTAssertEqual( - index.filteredSearch(vector: [Float64(1.0)], count: 3) { + try index.filteredSearch(vector: [Float64(1.0)], count: 3) { key in true }.0, [1, 2, 3] @@ -201,7 +201,7 @@ class Test: XCTestCase { // filter which rejects all keys: XCTAssertEqual( - index.filteredSearch(vector: [Float64(1.0)], count: 3) { + try index.filteredSearch(vector: [Float64(1.0)], count: 3) { key in false }.0, [] @@ -210,7 +210,7 @@ class Test: XCTestCase { // filter function accepts a set of keys passed in through a capture. let acceptedKeys: [USearchKey] = [1, 2] XCTAssertEqual( - index.filteredSearch(vector: [Float64(1.0)], count: 3) { + try index.filteredSearch(vector: [Float64(1.0)], count: 3) { key in acceptedKeys.contains(key) }.0, acceptedKeys @@ -219,13 +219,13 @@ class Test: XCTestCase { // filter function accepts a set of keys passed in through a capture, // and also respects the count. XCTAssertEqual( - index.filteredSearch(vector: [Float64(1.0)], count: 1) { + try index.filteredSearch(vector: [Float64(1.0)], count: 1) { key in key > 1 }.0, [2] ) // works 😎 XCTAssertEqual( - index.filteredSearch(vector: [Float64(1.0)], count: 2) { + try index.filteredSearch(vector: [Float64(1.0)], count: 2) { key in key > 1 }.0, [2, 3]