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

时间:2015-08-09 12:48:34

标签: arrays swift types binary-search

我有一个已排序的数组,并希望对其进行二进制搜索。

所以我问Swift库中是否有类似排序等的东西?或者是否有可用的独立版本?

当然我可以自己写,但我想避免再次重新发明轮子。

13 个答案:

答案 0 :(得分:37)

这是我最喜欢的二进制搜索实现。它不仅可用于查找元素,还可用于查找插入索引。通过提供相应的谓词(例如{ $0 < x } vs { $0 > x } vs { $0 <= x } vs { $0 >= 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

答案 1 :(得分:18)

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

<Link to="portfolio">

答案 2 :(得分:8)

我在实施extension的{​​{1}}上使用Indexable

  • 它需要indexOfFirstObjectPassingTest谓词,并返回第一个元素的索引以通过测试。
  • 如果没有此类索引,则返回test的{​​{1}}。
  • 如果endIndex为空,则会获得Indexable

实施例

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非常有用,因为它可以让您找到数据中的内容。通过调整测试,您可以找到&#34; stuff&#34;的下限和上限。

以下是一些数据:

a.indexOfFirstObjectPassingTest {$0==3}

我们可以找到所有indexOfFirstObjectPassingTest这样的let a = [1,1,1, 2,2,2,2, 3, 4, 5] ......

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

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

代码

layoutAttributesForElementsInRect

免责声明&amp;注意

我有大约6年的iOS经验,10个目标C,以及> 18个编程......

......但是我在Swift的第3天: - )

  1. 我在UICollectionViewCells协议上使用了扩展程序。这可能是愚蠢的做法 - 欢迎反馈。
  2. Binary searches notoriously hard正确编码。你真的应该阅读这个链接,找出它们实现中常见的错误,但这里有一个摘录:
  3.   

    当Jon Bentley在专业程序员的课程中将其作为一个问题时,他发现,经过几个小时的工作后,一个惊人的百分之九十的人无法正确编码二进制搜索,而另一项研究显示准确的代码因为它只能在二十本教科书中的五本中找到。此外,Bentley自己在二十六年出版的“编程珍珠”一书中发表的二元搜索实现包含了一个二十多年来未被发现的错误。

    鉴于最后一点,这里是对此代码的测试。他们过去了。它们不太可能是详尽无遗的 - 所以肯定仍然会有错误。测试不保证实际上是正确的!没有测试测试。

    测试

    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
      }
    }
    

答案 3 :(得分:3)

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相同的索引,因此在没有突变的情况下(例如本例),因此非常高效。

答案 4 :(得分:1)

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

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")) 

答案 5 :(得分:1)

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

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)
        }
    }
}

以上代码应适用于任何类型的集合(切片或未切片,零偏移量或非零偏移量的集合)。

答案 6 :(得分:0)

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

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
}

}

答案 7 :(得分:0)

这是一个包含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)
    }
}

这是非常自制的,所以......告诫者。它确实有效并且可以进行二分查找。

答案 8 :(得分:0)

这里是使用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
}

答案 9 :(得分:0)

通过递归二进制搜索,

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))

答案 10 :(得分:0)

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)

答案 11 :(得分:0)

详细信息

  • 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() }
}

答案 12 :(得分:0)

另一种实现方式:如果希望在不使结构{} {1}的情况下搜索结构或类,则使其成为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