web-dev-qa-db-ja.com

Swift:標準配列のバイナリ検索?

並べ替えられた配列があり、それに対してバイナリ検索を実行したいと考えています。

Swiftライブラリーでソートなどがすでに利用可能かどうか、またはタイプに依存しないバージョンが利用可能ですか?

もちろん、自分で書くこともできますが、ホイールの再発明を避けたいです。

24
Peter71

バイナリ検索を使用する一般的な方法は次のとおりです。

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){
    println("Element found on index: \(searchIndex)");
}
22
Daniel Krom

これが私のお気に入りのバイナリ検索の実装です。要素の検索だけでなく、挿入インデックスの検索にも役立ちます。想定されるソート順(昇順または降順)と同等の要素に関する動作の詳細は、対応する述語(例:{ $0 < x }{ $0 > x }{ $0 <= x }{ $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
42
Vadim Yelagin

extensionを実装するIndexableindexOfFirstObjectPassingTestを使用しています。

  • これは、test述語を取り、テストに合格するためにインデックス最初の要素のを返します。
  • そのようなインデックスがない場合は、endIndexIndexableを返します。
  • Indexableが空の場合、endIndexを取得します。

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

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

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

重要

testで指定したインデックスの後にあるインデックスの場合、falsetrueに決して戻らないようにする必要があります。これは、バイナリ検索でデータの順序が必要であるという通常の前提条件と同等です。

具体的には、あなたがしてはいけないことa.indexOfFirstObjectPassingTest {$0==3}。これは正しく機能しません。

どうして?

indexOfFirstObjectPassingTestは、データ内のデータを範囲を検索できるため便利です。テストを調整することで、「もの」の下限と上限を見つけることができます。

ここにいくつかのデータがあります:

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

このようにすべての2Rangeを見つけることができます…

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日目です

  1. Indexableプロトコルで拡張機能を使用しました。これは愚かなアプローチかもしれません–フィードバックを歓迎します。
  2. バイナリ検索悪名高く難しい であり、正しくコーディングします。あなたは本当にそのリンクを読んで、それらの実装における一般的な間違いがどれだけあるかを知る必要がありますが、ここに抜粋があります:

Jon Bentleyがプロのプログラマ向けのコースの問題として問題を割り当てたとき、驚異的な90%が数時間作業した後、バイナリ検索を正しくコーディングできなかったことを発見し、別の研究では正確なコードは、20冊の教科書のうち5冊だけに見られます。さらに、1986年の著書 『Programming Pearls』で発表されたBentley独自のバイナリ検索の実装には、20年以上検出されなかったエラーが含まれています。

その最後の点を踏まえて、ここにこのコードのテストがあります。彼らは合格する。それらが網羅的である可能性は低いので、間違いなくまだエラーがある可能性があります。テストが実際に正しいことは保証されません!テスト用のテストはありません。

テスト

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)
    }
  }
}
9
Benjohn
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は配列のビューであり、ストレージを共有する元の配列と同じインデックスを保持するという事実を利用しているため、(この場合のように)変異がない場合は、したがって、非常に効率的です。

3

以下は、文字列のソートされた配列の実装です。

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")) 
1
James Rochabrun

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

これはかなり手作りなので、注意が必要です。それは機能し、二分探索を行います。

0
Dan Rosenstark

配列に複数ある場合、複数のインデックスを返すより良い実装があります。

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
}

}

0
Jacky Wang

これは、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
}
0
Lucy Jeong

再帰的二分探索により、

_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))
_
0
Joyal Clifford

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