Swift:标准数组的二进制搜索?

Posted

技术标签:

【中文标题】Swift:标准数组的二进制搜索?【英文标题】:Swift: Binary search for standard array? 【发布时间】:2015-11-01 11:20:27 【问题描述】:

我有一个排序数组,想对其进行二分搜索。

所以我想问一下 Swift 库中是否已经提供了一些东西,比如 sort 等?或者是否有可用的类型独立版本?

我当然可以自己写,但我想避免重新发明***。

【问题讨论】:

你需要排序功能还是二分查找功能? 不知道过滤器有多快。它适用于每个阵列。但我知道我的数组已排序。所以我可以使用二进制来提高速度。 当然,这就是我切换的原因。我的数组中有 > 1.700.000 个字符串。我又使用了这个搜索一万次。 如果您使用此页面上的任何实现,值得强调的是Binary Search is notoriously hard to get right。我强烈建议对您使用的任何代码进行测试。例如,Java 的 Arrays.binarySearch() 在 SDK 6.0 版本之前被破坏。作为my own answer 的插件,它包括测试。 如果您想查看完整的算法库或查看特定的 BS,这里是 Swift Algorithms Club repo github.com/raywenderlich/swift-algorithm-club/blob/master/… 【参考方案1】:

这是在 swift 5 中创建二分搜索函数的方法,在此示例中,我假设您要查找的项目保证在列表中,但是如果您的项目不能保证在列表中,那么您可以先运行这段代码检查一下:

yourList.contains(yourItem) //will return true or false

这里是二分查找函数:

override func viewDidLoad() 
    super.viewDidLoad()
    
    print(binarySearch(list: [1, 2, 4, 5, 6], num: 6)) //returns 4


func binarySearch(list: [Int], num: Int) -> Int //returns index of num

    var firstIndex = 0
    var lastIndex = list.count - 1
    
    var middleIndex = (firstIndex + lastIndex) / 2
    var middleValue = list[middleIndex]
    
    while true //loop until we find the item we are looking for
    
        middleIndex = (firstIndex + lastIndex) / 2 //getting the list's middle index
        middleValue = list[middleIndex]
        
        if middleValue > num
        
            lastIndex = middleIndex - 1 //get the left side of the remaining list
        
        else if middleValue < num
        
            firstIndex = middleIndex + 1 //get the right side of the remaining list
        
        else if middleValue == num
         
            break //found the correct value so we can break out of the loop
        
    
    return middleIndex

我制作了一个 youtube 视频来解释这个here

【讨论】:

【参考方案2】:

为了完整起见,这里是一个完全基于模式匹配的实现:

extension Collection where Element: Comparable 
    func binarySearch(for element: Element) -> Index? 
        switch index(startIndex, offsetBy: distance(from: startIndex, to: endIndex) / 2) 
        case let i where i >= endIndex: return nil
        case let i where self[i] == element: return i
        case let i where self[i] > element: return self[..<i].binarySearch(for: element)
        case let i: return self[index(after: i)..<endIndex].binarySearch(for: element)
        
    

上面的代码应该适用于任何类型的集合,切片或不切片,零偏移或非零偏移。

【讨论】:

【参考方案3】:

另一种实现方式:如果您希望结构或类可搜索而不使它们成为Comparable,则改为使用BinarySearchable

public protocol BinarySearchable 
    associatedtype C: Comparable
    var searchable: C  get 



public extension Array where Element: BinarySearchable 

    func binarySearch(_ prefix: Element.C) -> Index 
        var low = 0
        var high = count
        while low != high 
            let mid = (low + high) / 2
            if self[mid].searchable < prefix 
                low = mid + 1
             else 
                high = mid
            
        
        return low
    


应按name 排序和搜索的结构的示例用法:

struct Country: BinraySearchable 
    var code: String
    var name: String

    var searchable: String  name 


// Suppose you have a list of countries sorted by `name`, you want to find
// the index of the first country whose name starts with "United", others
// will follow:

let index = listOfCountries.binarySearch("United")

【讨论】:

BinarySearchable 类型没有意义,你可以直接使用Comparable 代替。 @ErikAigner 如果搜索键不是对象本身,则不是。假设您有一个带有id 的对象,并且您想按id 进行排序和搜索,但该数组存储了整个对象。您单独定义“可搜索”属性,因此需要单独的协议 BinarySearchable。 这样你可以让一个对象只能被一个属性搜索,因为你只能扩展它一次。如果您直接使用Comparable,您可以将存储的对象包装在实现您想要的比较行为的struct 中。 @ErikAigner 我知道,但仅通过一个属性使对象可比较可能是不可取的,甚至是危险的——想想意外的比较。 Binray 搜索是一个非常具体的功能,我更喜欢将这种类型的比较限制在它上面。【参考方案4】:

这是使用二分搜索的通用方法:

func binarySearch<T:Comparable>(_ inputArr:Array<T>, _ searchItem: T) -> Int? 
    var lowerIndex = 0
    var upperIndex = inputArr.count - 1

    while (true) 
        let currentIndex = (lowerIndex + upperIndex)/2
        if(inputArr[currentIndex] == searchItem) 
            return currentIndex
         else if (lowerIndex > upperIndex) 
            return nil
         else 
            if (inputArr[currentIndex] > searchItem) 
                upperIndex = currentIndex - 1
             else 
                lowerIndex = currentIndex + 1
            
        
    


var myArray = [1,2,3,4,5,6,7,9,10]
if let searchIndex = binarySearch(myArray, 5) 
    print("Element found on index: \(searchIndex)")

【讨论】:

在没有匹配的情况下不返回-1会大大改善这一点。更类似于 Swift 的方法是返回一个可选项。另一种好的方法是在找不到元素时返回endIndex @Benjohn 完全同意可选的,答案是 2 岁,是时候编辑它了 :) 也可以应用于字符串? @RajuyourPepe 用于符合Comparable 协议的类 您应该在开头添加guard !inputArr.isEmpty else return nil 以解决空数组的情况。另外,我认为 else if 条件应该是lowerIndex &gt;= upperIndex【参考方案5】:

详情

Swift 5.2、Xcode 11.4 (11E146)

解决方案

import Foundation

extension RandomAccessCollection where Element: Comparable 

    private func binarySearchIteration(forIndexOf value: Element, in range: Range<Index>? = nil,
                                       valueDetected: ((Index, _ in: Range<Index>) -> Index?)) -> Index? 
        let range = range ?? startIndex..<endIndex

        guard range.lowerBound < range.upperBound else  return nil 

        let size = distance(from: range.lowerBound, to: range.upperBound)
        let middle = index(range.lowerBound, offsetBy: size / 2)

        switch self[middle] 
        case value: return valueDetected(middle, range) ?? middle
        case ..<value: return binarySearch(forIndexOf: value, in: index(after: middle)..<range.upperBound)
        default: return binarySearch(forIndexOf: value, in: range.lowerBound..<middle)
        
    

    func binarySearch(forIndexOf value: Element, in range: Range<Index>? = nil) -> Index? 
        binarySearchIteration(forIndexOf: value, in: range)  currentIndex, _ in currentIndex 
    

    func binarySearch(forFirstIndexOf value: Element, in range: Range<Index>? = nil) -> Index? 
        binarySearchIteration(forIndexOf: value, in: range)  currentIndex, range in
            binarySearch(forFirstIndexOf: value, in: range.lowerBound..<currentIndex)
        
    

    func binarySearch(forLastIndexOf value: Element, in range: Range<Index>? = nil) -> Index? 
        binarySearchIteration(forIndexOf: value, in: range)  currentIndex, range in
            binarySearch(forFirstIndexOf: value, in: index(after: currentIndex)..<range.upperBound)
        
    

    func binarySearch(forIndicesRangeOf value: Element, in range: Range<Index>? = nil) -> Range<Index>? 
        let range = range ?? startIndex..<endIndex
        guard range.lowerBound < range.upperBound else  return nil 

        guard let currentIndex = binarySearchIteration(forIndexOf: value, in: range, valueDetected:  index, _ in index
        ) else  return nil 

        let firstIndex = binarySearch(forFirstIndexOf: value, in: range.lowerBound ..< index(after: currentIndex)) ?? currentIndex
        let lastIndex = binarySearch(forFirstIndexOf: value, in: index(after: currentIndex) ..< range.upperBound) ?? currentIndex

        return firstIndex..<index(after: lastIndex)
    

用法

//let array = ["one", "two", "three", "three", "three", "three", "three", "four", "five", "five"]
//let value = "three"
let array = [1, 2, 3, 3, 3, 3, 3, 4, 5, 5]
let value = 3
print(array.binarySearch(forFirstIndexOf: value))
print(array.binarySearch(forLastIndexOf: value))
print(array.binarySearch(forIndicesRangeOf: value))

测试

protocol _BinarySearchTestable: class where Collection: RandomAccessCollection, Collection.Element: Comparable 
    associatedtype Collection
    var array: Collection!  get set 
    var elementToSearch: Collection.Element!  get set 
    func testFindFirstIndexOfValueInCollection()
    func testFindLastIndexOfValueInCollection()
    func testFindIndicesRangeOfValueInCollection()


extension _BinarySearchTestable where Self: XCTest 

    typealias Element = Collection.Element
    typealias Index = Collection.Index

    func _testFindFirstIndexOfValueInCollection() 
        _testfindFirstIndex(comparableArray: array, testableArray: array)
    

    func _testFindLastIndexOfValueInCollection() 
        let index1 = array.lastIndex(of: elementToSearch)
        let index2 = array.binarySearch(forLastIndexOf: elementToSearch)
        _testElementsAreEqual(indexInComparableArray: index1, comparableArray: array,
                              indexInTestableArray: index2, testableArray: array)
    

    func _testFindIndicesRangeOfValueInCollection() 
        var range1: Range<Index>?
        if  let firstIndex = array.firstIndex(of: elementToSearch),
            let lastIndex = array.lastIndex(of: elementToSearch) 
                range1 = firstIndex ..< array.index(after: lastIndex)
        
        let range2 = array.binarySearch(forIndicesRangeOf: elementToSearch)
        XCTAssertEqual(range1, range2)
    

    private func _testElementsAreEqual(indexInComparableArray: Index?, comparableArray: Collection,
                                       indexInTestableArray: Index?, testableArray: Collection) 
        XCTAssertEqual(indexInComparableArray, indexInTestableArray)
        var valueInComparableArray: Element?
        if let index = indexInComparableArray  valueInComparableArray = comparableArray[index] 

        var valueInTestableArray: Element?
        if let index = indexInComparableArray  valueInTestableArray = testableArray[index] 
        XCTAssertEqual(valueInComparableArray, valueInTestableArray)
    

    private func _testfindFirstIndex(comparableArray: Collection, testableArray: Collection) 
        let index1 = comparableArray.firstIndex(of: elementToSearch)
        let index2 = testableArray.binarySearch(forFirstIndexOf: elementToSearch)
        _testElementsAreEqual(indexInComparableArray: index1, comparableArray: comparableArray,
                              indexInTestableArray: index2, testableArray: testableArray)
    


class TestsInEmptyArray: XCTestCase, _BinarySearchTestable 

    var array: [String]!
    var elementToSearch: String!

    override func setUp() 
        array = []
        elementToSearch = "value"
    

    func testFindFirstIndexOfValueInCollection()  _testFindFirstIndexOfValueInCollection() 
    func testFindLastIndexOfValueInCollection()  _testFindLastIndexOfValueInCollection() 
    func testFindIndicesRangeOfValueInCollection()  _testFindIndicesRangeOfValueInCollection() 


class TestsInArray: XCTestCase, _BinarySearchTestable 

    var array: [Int]!
    var elementToSearch: Int!

    override func setUp() 
        array = [1, 2, 3, 3, 3, 3, 3, 4, 5, 5]
        elementToSearch = 3
    

    func testFindFirstIndexOfValueInCollection()  _testFindFirstIndexOfValueInCollection() 
    func testFindLastIndexOfValueInCollection()  _testFindLastIndexOfValueInCollection() 
    func testFindIndicesRangeOfValueInCollection()  _testFindIndicesRangeOfValueInCollection() 


class TestsInArrayWithOneElement: XCTestCase, _BinarySearchTestable 

    var array: [Date]!
    var elementToSearch: Date!

    override func setUp() 
        let date = Date()
        array = [date]
        elementToSearch = date
    

    func testFindFirstIndexOfValueInCollection()  _testFindFirstIndexOfValueInCollection() 
    func testFindLastIndexOfValueInCollection()  _testFindLastIndexOfValueInCollection() 
    func testFindIndicesRangeOfValueInCollection()  _testFindIndicesRangeOfValueInCollection() 

【讨论】:

【参考方案6】:

Swift 5 中的简单解决方案:

func binarySerach(list: [Int], item: Int) -> Int? 
    var low = 0
    var high = list.count - 1
    while low <= high 
        let mid = (low + high) / 2
        let guess = list[mid]
        if guess == item 
            return mid
         else if guess > item 
            high = mid - 1
         else 
            low = mid + 1
        
    
    return nil


let myList = [1,3,4,7,9]

print(binarySerach(list: myList, item: 9))
//Optional(4)

【讨论】:

【参考方案7】:

通过递归二分查找,

func binarySearch(data : [Int],search: Int,high : Int,low:Int) -> Int? 
    if (low >  high)
    
        return nil
    
    let mid = low + (low + high)/2

    if (data[mid] == search) 
        return mid
    
    else if (search < data[mid])
        return binarySearch(data: data, search: search, high: high-1, low: low)
    else 
        return binarySearch(data: data, search: search, high: high, low: low+1)
    

输入:let arry = Array(0...5) // [0,1,2,3,4,5]

print(binarySearch(data: arry, search: 0, high: arry.count-1, low: 0))

【讨论】:

【参考方案8】:

这是我最喜欢的二分搜索实现。它不仅对查找元素很有用,而且对查找插入索引也很有用。通过提供相应的谓词(例如 $0 &lt; x vs $0 &gt; x vs $0 &lt;= x vs $0 &gt;= x )来控制关于假定排序顺序(升序或降序)和相等元素行为的详细信息。该评论明确地说明了它的作用。

extension RandomAccessCollection 
    /// Finds such index N that predicate is true for all elements up to
    /// but not including the index N, and is false for all elements
    /// starting with index N.
    /// Behavior is undefined if there is no such N.
    func binarySearch(predicate: (Element) -> Bool) -> Index 
        var low = startIndex
        var high = endIndex
        while low != high 
            let mid = index(low, offsetBy: distance(from: low, to: high)/2)
            if predicate(self[mid]) 
                low = index(after: mid)
             else 
                high = mid
            
        
        return low
    

示例用法:

(0 ..< 778).binarySearch  $0 < 145  // 145

【讨论】:

一个 Swift 3 版本发布在***.com/questions/40226479/…。 在while循环前添加if low == high || predicate(self[index(high, offsetBy: -1)]) return high ,优化追加操作。 这应该是RandomAccessCollection 的扩展,而不是Collection。这个扩展只能保证 O(n log n) 复杂度。 下标到 Collection 总是 O(1),但 K 偏移索引是 O(K)。因此,Collection 上此扩展的总体复杂度为 O(N)(因为 N/2 + N/4 + N/8 + ... + 1)。 RandomAccessCollection 的复杂度是 O(logN),因为偏移量是 O(1)。 @DanRosenstark taylorswift 绝对正确,该算法仅保证 RandomAccessCollection 的 O(logN) 复杂度,而不是任意集合。然而,造成这种情况的具体原因并不完全正确,所以我觉得我需要澄清它以避免在 cmets 中进一步混淆。【参考方案9】:
extension ArraySlice where Element: Comparable 
    func binarySearch(_ value: Element) -> Int? 
        guard !isEmpty else  return nil 

        let midIndex = (startIndex + endIndex) / 2
        if value == self[midIndex] 
            return midIndex
         else if value > self[midIndex] 
            return self[(midIndex + 1)...].binarySearch(value)
         else 
            return self[..<midIndex].binarySearch(value)
        
    


extension Array where Element: Comparable 
    func binarySearch(_ value: Element) -> Int? 
        return self[0...].binarySearch(value)
    

在我看来,这是非常易读的,它利用了 Swift 的 ArraySlice 是 Array 的一个视图,并且保留了与它共享存储的原始 Array 相同的索引,因此在没有突变的情况下(就像在这种情况下),因此非常有效。

【讨论】:

【参考方案10】:

我在Indexable 上使用extension 来实现indexOfFirstObjectPassingTest

它接受一个test 谓词,并返回第一个元素的索引 以通过测试。 如果没有这样的索引,则返回Indexable 中的endIndex。 如果Indexable 为空,您将获得endIndex

示例

let a = [1,2,3,4]

a.map$0>=3
// returns [false, false, true, true]

a.indexOfFirstObjectPassingTest $0>=3
// returns 2

重要

您需要确保 test 永远不会在 false 中为任何索引返回它已表示 true 的索引。这相当于通常的前提条件,即二进制搜索要求您的数据有序。

具体来说,你不能这样做a.indexOfFirstObjectPassingTest $0==3。这将无法正常工作。

为什么?

indexOfFirstObjectPassingTest 很有用,因为它可以让您查找数据范围。通过调整测试,你可以找到“stuff”的下限和上限。

这是一些数据:

let a = [1,1,1, 2,2,2,2, 3, 4, 5]

我们可以像这样在所有2s 中找到Range...

let firstOf2s = a.indexOfFirstObjectPassingTest($0>=2)
let endOf2s = a.indexOfFirstObjectPassingTest($0>2)
let rangeOf2s = firstOf2s..<endOf2s
如果数据中没有2s,我们将返回一个空范围,我们不需要任何特殊处理。 只要有2s,我们就会找到所有的。

例如,我在layoutAttributesForElementsInRect 的实现中使用它。我的UICollectionViewCells 存储在一个数组中垂直排序。很容易编写一对调用来查找特定矩形内的所有单元格并排除任何其他单元格。

代码

extension Indexable 
  func indexOfFirstObjectPassingTest( test: (Self._Element -> Bool) ) -> Self.Index 
    var searchRange = startIndex..<endIndex

    while searchRange.count > 0 
      let testIndex: Index = searchRange.startIndex.advancedBy((searchRange.count-1) / 2)
      let passesTest: Bool = test(self[testIndex])

      if(searchRange.count == 1) 
        return passesTest ? searchRange.startIndex : endIndex
      

      if(passesTest) 
        searchRange.endIndex = testIndex.advancedBy(1)
      
      else 
        searchRange.startIndex = testIndex.advancedBy(1)
      
    

    return endIndex
  

免责声明和警告

我有大约 6 年的 ios 经验,10 年的 Objective C 和 >18 的编程经验……

…但我在 Swift 的第 3 天 :-)

    我在Indexable 协议上使用了一个扩展。这可能是愚蠢的做法——欢迎提供反馈。 Binary searches 是 notoriously hard 以正确编码。您确实应该阅读该链接以了解其实施中的常见错误,但这里是摘录:

当 Jon Bentley 将其作为专业程序员课程中的一个问题时,他发现 90% 的人在经过几个小时的工作后未能正确编码二进制搜索,而另一项研究表明 准确的代码因为它只出现在二十本教科书中的五本中。此外,Bentley 自己在 1986 年出版的《Programming Pearls》一书中实现的二分搜索包含一个二十多年未被发现的错误。

鉴于最后一点,这里是此代码的测试。他们通过。它们不太可能是详尽无遗的——因此肯定仍然存在错误。不能保证测试实际上是正确的!测试没有测试。

测试

class BinarySearchTest: XCTestCase 

  func testCantFind() 
    XCTAssertEqual([].indexOfFirstObjectPassingTest (_: Int) -> Bool in false, 0)
    XCTAssertEqual([1].indexOfFirstObjectPassingTest (_: Int) -> Bool in false, 1)
    XCTAssertEqual([1,2].indexOfFirstObjectPassingTest (_: Int) -> Bool in false, 2)
    XCTAssertEqual([1,2,3].indexOfFirstObjectPassingTest (_: Int) -> Bool in false, 3)
    XCTAssertEqual([1,2,3,4].indexOfFirstObjectPassingTest (_: Int) -> Bool in false, 4)
  

  func testAlwaysFirst() 
    XCTAssertEqual([].indexOfFirstObjectPassingTest (_: Int) -> Bool in true, 0)
    XCTAssertEqual([1].indexOfFirstObjectPassingTest (_: Int) -> Bool in true, 0)
    XCTAssertEqual([1,2].indexOfFirstObjectPassingTest (_: Int) -> Bool in true, 0)
    XCTAssertEqual([1,2,3].indexOfFirstObjectPassingTest (_: Int) -> Bool in true, 0)
    XCTAssertEqual([1,2,3,4].indexOfFirstObjectPassingTest (_: Int) -> Bool in true, 0)
  

  func testFirstMatch() 
    XCTAssertEqual([1].indexOfFirstObjectPassingTest 1<=$0, 0)
    XCTAssertEqual([0,1].indexOfFirstObjectPassingTest 1<=$0, 1)
    XCTAssertEqual([1,2].indexOfFirstObjectPassingTest 1<=$0, 0)
    XCTAssertEqual([0,1,2].indexOfFirstObjectPassingTest 1<=$0, 1)
  

  func testLots() 
    let a = Array(0..<1000)
    for i in a.indices 
      XCTAssertEqual(a.indexOfFirstObjectPassingTest(Int(i)<=$0), i)
    
  

【讨论】:

非常感谢您的详细回答和解释。我阅读了Indexable 的 swift 2.x 文档:“重要提示:在大多数情况下,最好忽略此协议并改用 CollectionType,因为它具有更完整的界面。”因此,根据 Vadim 的回答,我使用了 extension CollectionType where Index: RandomAccessIndexType。另外我想知道在while之前订购(自我)是否值得和可能的傻瓜证明@ 嗨@ajp,感谢您的想法。 1.我不太了解swift标准库(仍然!),但总的来说,我会使用支持所需功能的最小接口。 2. 我理解您的担忧,但我会建议您事先不要这样做。排序是(在大多数情况下)O(n.log(n)) 操作。二分查找是O(log(n)) 操作。如果您使用二进制搜索,您可能需要(巨大的)渐近性能差异。如果您不需要这种差异,则最好使用完全不同的算法来处理无序数据。 谢谢@Benjohn,明白了,很好的逻辑。 字符串版本怎么样? @RajuyourPepe 该代码是通用的,因此它适用于任何已排序的集合,包括已排序的字符串。除非您的意思是在字符串中进行二进制搜索?这需要对字符进行排序,您也可以这样做,而且可能很有用。【参考方案11】:

这里是使用while语法的二分搜索

func binarySearch<T: Comparable>(_ a: [T], key: T) -> Int? 
    var lowerBound = 0
    var upperBound = a.count
    while lowerBound < upperBound 
        let midIndex = lowerBound + (upperBound - lowerBound) / 2
        if a[midIndex] == key 
            return midIndex
         else if a[midIndex] < key 
            lowerBound = midIndex + 1
         else 
            upperBound = midIndex
        
    
    return nil

【讨论】:

【参考方案12】:

这是一个排序的字符串数组的实现。

var arr = ["a", "abc", "aabc", "aabbc", "aaabbbcc", "bacc", "bbcc", "bbbccc", "cb", "cbb", "cbbc", "d" , "defff", "deffz"]

func binarySearch(_ array: [String], value: String) -> String 

    var firstIndex = 0
    var lastIndex = array.count - 1
    var wordToFind = "Not founded"
    var count = 0

    while firstIndex <= lastIndex 

        count += 1
        let middleIndex = (firstIndex + lastIndex) / 2
        let middleValue = array[middleIndex]

        if middleValue == value 
            wordToFind = middleValue
            return wordToFind
        
        if value.localizedCompare(middleValue) == ComparisonResult.orderedDescending 
            firstIndex = middleIndex + 1
        
        if value.localizedCompare(middleValue) == ComparisonResult.orderedAscending 
            print(middleValue)
            lastIndex = middleIndex - 1
        
    
    return wordToFind

//print d
print(binarySearch(arr, value: "d")) 

【讨论】:

【参考方案13】:

这是一个完整的示例,其中包含 Swift 3.1 的几个测试用例。这不可能比默认实现更快,但这不是重点。数组扩展在底部:

//  BinarySearchTests.swift
//  Created by Dan Rosenstark on 3/27/17
import XCTest
@testable import SwiftAlgos

class BinarySearchTests: XCTestCase 

    let sortedArray : [Int] = [-25, 1, 2, 4, 6, 8, 10, 14, 15, 1000]

    func test5() 
        let traditional = sortedArray.index(of: 5)
        let newImplementation = sortedArray.indexUsingBinarySearch(of: 5)
        XCTAssertEqual(traditional, newImplementation)
    

    func testMembers() 
        for item in sortedArray 
            let traditional = sortedArray.index(of: item)
            let newImplementation = sortedArray.indexUsingBinarySearch(of: item)
            XCTAssertEqual(traditional, newImplementation)
        
    

    func testMembersAndNonMembers() 
        for item in (-100...100) 
            let traditional = sortedArray.index(of: item)
            let newImplementation = sortedArray.indexUsingBinarySearch(of: item)
            XCTAssertEqual(traditional, newImplementation)
        
    

    func testSingleMember() 
        let sortedArray = [50]
        for item in (0...100) 
            let traditional = sortedArray.index(of: item)
            let newImplementation = sortedArray.indexUsingBinarySearch(of: item)
            XCTAssertEqual(traditional, newImplementation)
        
    

    func testEmptyArray() 
        let sortedArray : [Int] = []
        for item in (0...100) 
            let traditional = sortedArray.index(of: item)
            let newImplementation = sortedArray.indexUsingBinarySearch(of: item)
            XCTAssertEqual(traditional, newImplementation)
        
    


extension Array where Element : Comparable 
    // self must be a sorted Array
    func indexUsingBinarySearch(of element: Element) -> Int? 
        guard self.count > 0 else  return nil 
        return binarySearch(for: element, minIndex: 0, maxIndex: self.count - 1)
    

    private func binarySearch(for element: Element, minIndex: Int, maxIndex: Int) -> Int? 
        let count = maxIndex - minIndex + 1
        // if there are one or two elements, there is no futher recursion:
        // stop and check one or both values (and return nil if neither)
        if count == 1 
            return element == self[minIndex] ? minIndex : nil
         else if count == 2 
            switch element 
                case self[minIndex]: return minIndex
                case self[maxIndex]: return maxIndex
                default: return nil
            
        

        let breakPointIndex = Int(round(Double(maxIndex - minIndex) / 2.0)) + minIndex
        let breakPoint = self[breakPointIndex]

        let splitUp = (breakPoint < element)
        let newMaxIndex : Int = splitUp ? maxIndex : breakPointIndex
        let newMinIndex : Int = splitUp ? breakPointIndex : minIndex

        return binarySearch(for: element, minIndex: newMinIndex, maxIndex: newMaxIndex)
    

这是相当自制的,所以...告诫购买者。它确实有效并且确实进行了二进制搜索。

【讨论】:

【参考方案14】:

这是一个更好的实现,如果数组中有多个索引,则返回多个索引。

extension Array where Element: Comparable 

/* Array Must be sorted */

func binarySearch(key: Element) -> [Index]? 
    return self.binarySearch(key, initialIndex: 0)


private func binarySearch(key: Element, initialIndex: Index) -> [Index]? 

    guard count > 0 else  return nil 

    let midIndex = count / 2
    let midElement = self[midIndex]

    if key == midElement 

        // Found!

        let foundIndex = initialIndex + midIndex

        var indexes = [foundIndex]

        // Check neighbors for same values

        // Check Left Side

        var leftIndex = midIndex - 1

        while leftIndex >= 0 

            //While there is still more items on the left to check

            print(leftIndex)

            if self[leftIndex] == key 

                //If the items on the left is still matching key

                indexes.append(leftIndex + initialIndex)
                leftIndex--

             else 

                // The item on the left is not identical to key

                break
            
        

        // Check Right side

        var rightIndex = midIndex + 1

        while rightIndex < count 

            //While there is still more items on the left to check

            if self[rightIndex] == key 

                //If the items on the left is still matching key

                indexes.append(rightIndex + initialIndex)
                rightIndex++

             else 

                // The item on the left is not identical to key

                break
            
        

        return indexes.sort return $0 < $1 
    

    if count == 1 

        guard let first = first else  return nil 

        if first == key 
            return [initialIndex]
        
        return nil
    


    if key < midElement 

        return Array(self[0..<midIndex]).binarySearch(key, initialIndex: initialIndex + 0)
    

    if key > midElement 

        return Array(self[midIndex..<count]).binarySearch(key, initialIndex: initialIndex + midIndex)
    

    return nil

【讨论】:

以上是关于Swift:标准数组的二进制搜索?的主要内容,如果未能解决你的问题,请参考以下文章

许多排序数组中的二进制搜索

二进制/顺序搜索

在数组中正确放置的二进制搜索

Swift 5.3的进化:语法标准库调试能力大幅提升

二进制搜索算法:数组中每条记录的文本文件

使用二进制搜索在数组中查找数字的位置