J'ai un tableau trié et je veux faire une recherche binaire dessus.
Je demande donc si quelque chose est déjà disponible dans la bibliothèque Swift, comme par exemple, etc.? Ou existe-t-il une version indépendante du type?
Bien sûr, je pourrais l'écrire moi-même, mais j'aime bien éviter de réinventer la roue.
Voici une manière générique d'utiliser la recherche binaire:
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)");
}
Voici mon implémentation préférée de la recherche binaire. C'est utile non seulement pour trouver l'élément mais aussi pour trouver l'index d'insertion. Les détails concernant l'ordre de tri supposé (croissant ou décroissant) et le comportement vis-à-vis des éléments égaux sont contrôlés en fournissant un prédicat correspondant (par exemple { $0 < x }
vs { $0 > x }
vs { $0 <= x }
vs { $0 >= x }
). Le commentaire dit sans ambiguïté ce qu'il fait exactement.
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
}
}
Exemple d'utilisation:
(0 ..< 778).binarySearch { $0 < 145 } // 145
J'utilise un extension
sur Indexable
implémentant indexOfFirstObjectPassingTest
.
test
et renvoie l'index du premier élément pour réussir le test. endIndex
de la Indexable
. Indexable
est vide, vous obtenez la endIndex
.let a = [1,2,3,4]
a.map{$0>=3}
// returns [false, false, true, true]
a.indexOfFirstObjectPassingTest {$0>=3}
// returns 2
Vous devez vous assurer que test
ne retourne jamais dans false
pour tout index après un index pour lequel il a déclaré true
. Cela équivaut à la condition préalable habituelle selon laquelle la recherche binaire exige que vos données soient en ordre.
Plus précisément, vous ne devez pas le fairea.indexOfFirstObjectPassingTest {$0==3}
. Cela ne fonctionnera pas correctement.
indexOfFirstObjectPassingTest
est utile car il vous permet de rechercher des plages des éléments dans vos données. En ajustant le test, vous pouvez trouver les limites inférieure et supérieure de "substance".
Voici quelques données:
let a = [1,1,1, 2,2,2,2, 3, 4, 5]
Nous pouvons trouver la Range
de tous les 2
s comme ceci…
let firstOf2s = a.indexOfFirstObjectPassingTest({$0>=2})
let endOf2s = a.indexOfFirstObjectPassingTest({$0>2})
let rangeOf2s = firstOf2s..<endOf2s
2
s dans les données, on récupérera une plage vide} _ et nous n'aurons besoin d'aucun traitement particulier. 2
s, nous les trouverons tous.Par exemple, je l’utilise dans une implémentation de layoutAttributesForElementsInRect
. Ma UICollectionViewCells
sont stockés triés verticalement dans un tableau. Il est facile d'écrire une paire d'appels qui trouveront toutes les cellules qui se trouvent dans un rectangle particulier et en excluent les autres.
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
}
}
J'ai environ 6 ans d'expérience iOS, 10 ans en Objective C et plus de 18 ans en programmation…
… Mais je suis au jour 3 de Swift :-)
Indexable
. Cette approche pourrait être stupide - les commentaires sont les bienvenus.Lorsque Jon Bentley a attribué ce problème à un cours destiné aux programmeurs professionnels, il a constaté que 90% des personnes interrogées n'avaient pas réussi à coder correctement une recherche binaire après plusieurs heures de travail, et une autre étude a révélé que code précis trouvé que dans cinq manuels sur vingt. En outre, la propre mise en œuvre de la recherche binaire par Bentley, publiée dans son ouvrage de 1986, Programming Pearls, contient une erreur qui n'a pas été détectée pendant plus de vingt ans.
Compte tenu de ce dernier point, voici un test pour ce code. Ils passent. Ils sont peu susceptibles d’être exhaustifs - il peut donc encore y avoir des erreurs. Les tests ne sont pas forcément corrects! Il n'y a pas de tests pour les tests.
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)
}
}
}
Voici une implémentation pour un tableau trié de chaînes.
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"))
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)
}
}
Ceci, à mon avis, est très lisible et exploite le fait que Swift's ArraySlice est une vue sur Array et conserve les mêmes index que le tableau original avec lequel il partage le stockage. est donc très efficace.
Voici un exemple complet avec plusieurs cas de test pour Swift 3.1. Il n'y a aucune chance que cela soit plus rapide que l'implémentation par défaut, mais ce n'est pas le but. L'extension du tableau est en bas:
// 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)
}
}
Ceci est assez fait maison, alors ... caveat emptor. Cela fonctionne et fait une recherche binaire.
Voici une meilleure implémentation qui renvoie plusieurs index, s'il y en a plusieurs dans le tableau.
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
}
}
voici une recherche binaire utilisant la syntaxe 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
}