Add exactly 2 equal Elements to an array - arrays

I'm trying to add a random number (1...8) into an array.
I want to do this 16 times. The clue is, every number should only be in there twice. I got it working so far, but I don't think this is a clean way to do it. And the way I did it, it is not scaleable. If i wanted it to be, let's say for example 24 elements, and all of them 3 times, I'd have to rewrite all the code. Do You have an idea how to fix that? Thanks in advance!
var Numbers: [Int] = []
var randomNumber: Int = 0
let size: Int = 16
var index1 = 0
var index2 = 0
var index3 = 0
var index4 = 0
var index5 = 0
var index6 = 0
var index7 = 0
var index8 = 0
// Methods / Functions
func createRandomNumber() {
randomNumber = Int(arc4random() % 8 + 1)
}
func checkTwins() {
switch randomNumber {
case 1:
index1++
if index1 <= 2 {
Numbers.append(randomNumber)
}
else {
addRandomNumbertoArray()
}
case 2:
index2++
if index2 <= 2{
Numbers.append(randomNumber)
}
else {
addRandomNumbertoArray()
}
case 3:
index3++
if index3 <= 2 {
Numbers.append(randomNumber)
}
else {
addRandomNumbertoArray()
}
case 4:
index4++
if index4 <= 2 {
Numbers.append(randomNumber)
}
else {
addRandomNumbertoArray()
}
case 5:
index5++
if index5 <= 2 {
Numbers.append(randomNumber)
}
else {
addRandomNumbertoArray()
}
case 6:
index6++
if index6 <= 2 {
Numbers.append(randomNumber)
}
else {
addRandomNumbertoArray()
}
case 7:
index7++
if index7 <= 2 {
Numbers.append(randomNumber)
}
else {
addRandomNumbertoArray()
}
case 8:
index8++
if index8 <= 2 {
Numbers.append(randomNumber)
}
else {
addRandomNumbertoArray()
}
default: println("Generated Number out of Index")
}
}
func addRandomNumbertoArray() {
createRandomNumber()
checkTwins()
}
func fillUpArray() {
for _ in 1...size {
addRandomNumbertoArray()
}
}
// Calls
fillUpArray()
println(Numbers)
This was written in Swift Playground

What you can do is: First fill an array containing each of the
number 1, ..., 8 twice:
var array : [Int] = []
for i in 1 ... 8 {
array.extend(Repeat(count: 2, repeatedValue: i))
}
and then shuffle the array randomly:
array.shuffle()
where shuffle() is a Fisher-Yates shuffle.
A Swift implementation can be found in
How do I shuffle an array in Swift?:
extension Array {
mutating func shuffle() {
for i in 0..<(count - 1) {
let j = Int(arc4random_uniform(UInt32(count - i))) + i
swap(&self[i], &self[j])
}
}
}
This scales without problems to larger arrays and more repeated
elements.

Related

How to compare each element of array to other elements?

I need to find pairs in array, i wanted to do it with comparing arr[i] with others starting from arr[i+1] so i won't check for same value twice, but i can't get that going.
Here's what i've tried
func findPairs(_ ar: [Int]) -> Int {
var pairs = 0
for i in 0..<ar.count {
var k = i + 1
for k in 0..<ar.count {
if ar[i] == ar[k] {
pairs += 1
}
}
}
return pairs/2
}
Upd
to clarify, in the example i have, it was given as in array [1,2,3,1,2,3,4,5,1] we have 3 pairs [1,1] [2,2] [3,3]
You probably need to use i + 1 as the starting index in your inner loop, like that:
func findPairs(_ ar: [Int]) -> Int {
var pairs = 0
for i in 0..<ar.count {
let m = i + 1
for k in m..<ar.count {
if ar[i] == ar[k] {
pairs += 1
}
}
}
return pairs
}
Also, you can use another array to store the indexes of the pairs so that you do not use the same element for another pair:
func findPairs(_ ar: [Int]) -> Int {
var pairs = 0
var pairIndexes = [Int]()
for i in 0..<ar.count where !pairIndexes.contains(i) {
let m = i + 1
for k in m..<ar.count where !pairIndexes.contains(k) {
if ar[i] == ar[k] {
pairs += 1
pairIndexes.append(contentsOf: [i, k])
break
}
}
}
return pairs
}
The functional approach would be to get the total number of occurrences of the elements and sum half of it:
func findPairs(_ ar: [Int]) -> Int {
ar.reduce(into: [Int:Int]()) { $0[$1, default: 0] += 1 }.values.reduce(0) { $0 + $1/2 }
}
findPairs([1,2,3,1,2,3,4,5,1]) // 3
findPairs([4, 4, 4, 4, 4]) // 2
Or extending collection constraining the elements to hashable:
extension Collection where Element: Hashable {
var numberOfPairs: Int {
reduce(into: [Element:Int]()) { $0[$1, default: 0] += 1 }.values.reduce(0) { $0 + $1/2 }
}
}
[1,2,3,1,2,3,4,5,1].numberOfPairs // 3
[4, 4, 4, 4, 4].numberOfPairs // 2
I think you only need to find the number of pairs in the array.
So the following can work for you:
func findPairs(_ ar: [Int]) -> Int {
var pairs = 0
for i in 0..<ar.count {
for k in (i+1)..<ar.count {
if ar[i] == ar[k] {
pairs += 1
break
}
}
}
return pairs
}

Fetch respective Array's value in same loop iteration

void main() {
var n = 5;
var x = 1;
var a = [2, 5];
for (x = 1; x <= n; x++) {
if (x == a[x]) { // on 2nd & 5th iteration it should print 2 & 5 else 0
print(a[x]);
} else {
print(0);
}
}
}
output should be 0,2,0,0,5.
please note: 2 and 5 should get from array's value.
This is a simplified solution for achieving the result. Logic is, to only check if the element is in the List as it iterates.
void main() {
quiz();
}
void quiz() {
var n = 5;
var a = [2, 5];
for(int i = 1;i<=n;i++){
if(a.contains(i) ){
print(i);
}else{
print(0);
}
}
}
Output:
0
2
0
0
5

Changing old C for loop to new Swift loop

I have an old Xcode 7.3 Swift 2 code. I need to change these two for loops to the new swift 3 for loop syntax.
fileprivate func collapseSubItemsAtIndex(_ index : Int) {
var indexPaths = [IndexPath]()
let parent = self.findParent(index)
checkCurrentLanguage()
if lang.isEqual(to: "en")
{
//For loop 1
for (var i = index + 1; i <= index + self.engsubItems[parent].count; i += 1 ){
indexPaths.append(IndexPath(row: i, section: 0))
}
self.engtableview.deleteRows(at: indexPaths, with: UITableViewRowAnimation.fade)
self.engtotal -= self.engsubItems[parent].count
}
else{
//For loop 2
for (var i = index + 1; i <= index + self.subItems[parent].count; i += 1 ){
indexPaths.append(IndexPath(row: i, section: 0))
}
self.tableView.deleteRows(at: indexPaths, with: UITableViewRowAnimation.fade)
self.total -= self.subItems[parent].count
}
}
let count = self.engsubItems[parent].count
let rowsToDelete = ((index + 1) ... (index + count))
.map { IndexPath(row: $0, section: 0) }
self.engtableview.deleteRows(at: rowsToDelete, with: UITableViewRowAnimation.fade)
self.engtotal -= count
for i in (index+1)...(index + self.engsubItems[parent].count) {
...
}
for i in (index+1)...(index + self.subItems[parent].count) {
...
}

Optimizing comparison of values in large arrays - Go

I am trying to learn Go, and made an attempt at solving the problem explained here https://projecteuler.net/problem=215
I made a solution that works, but it takes a very long time to compute once the problem gets complex, literally running for hours without solving the question so i assume i need to optimize my solution somehow. Below is my code
package main
import "fmt"
func main() {
fmt.Println(wall(18, 10))
}
func wall(width, height int) int64 {
var total int64
combinations := findCombinations(width)
combiPointer := &combinations
for i := 0; i < 4; i++ {
for _, a := range combinations[i] {
if i%2 == 0 {
buildWall(a, combiPointer[i+1], 0, height, i+1, &total, combiPointer)
} else {
buildWall(a, combiPointer[i-1], 0, height, i-1, &total, combiPointer)
}
}
}
return total
}
func buildWall(a []int, combi [][]int, level, height, index int, total *int64, pointer *[4][][]int) {
level++
var canCombine bool
for _, a1 := range combi {
canCombine = true
for _, value := range a {
if canCombine == false {
break
}
for _, value1 := range a1 {
if value == value1 {
canCombine = false
break
}
}
}
if canCombine == true && level < height {
if index%2 == 0 {
buildWall(a1, pointer[index+1], level, height, index+1, total, pointer)
} else {
buildWall(a1, pointer[index-1], level, height, index-1, total, pointer)
}
} else if level == height {
*total++
break
}
}
}
findCombinations works fine and returns a three dimensional array with all possible solutions. The arrays in [0] and [1] can (maybe) be put on top of each other without a crack occuring. Same for [2] and [3]. I choose to cut the array of all solutions up in 4 arrays by looking at the position of the first and last block used to build the wall as i assumed it would increase performance to loop over smaller arrays.
func findCombinations(width int) [4][][]int {
var i int
var tmp int
var tmpInt1 int
var tmpInt2 int
open := make([][]int, 0, 100)
var solutionsHolder [4][][]int
open = append(open, []int{3, 2})
tmpArray := make([]int, 0, 100)
for {
if len(open[i]) > 0 {
tmpArray = append(tmpArray[:i], open[i][0])
open[i] = append(open[i][:0], open[i][1:]...)
counter := 0
for _, x := range tmpArray {
counter += x
}
if counter == width {
solutionArray := make([]int, len(tmpArray)-1)
counter2 := 0
for n := 0; n < len(tmpArray)-1; n++ {
if n == 0 {
tmpInt1 = tmpArray[n] % 2
}
counter2 += tmpArray[n]
solutionArray[n] = counter2
}
tmpInt2 = counter2 % 2
if tmpInt1 == 0 && tmpInt2 == 0 {
solutionsHolder[0] = append(solutionsHolder[0], solutionArray)
} else if tmpInt1 == 1 && tmpInt2 == 1 {
solutionsHolder[1] = append(solutionsHolder[1], solutionArray)
} else if tmpInt1 == 1 && tmpInt2 == 0 {
solutionsHolder[2] = append(solutionsHolder[2], solutionArray)
} else {
solutionsHolder[3] = append(solutionsHolder[3], solutionArray)
}
for _, v := range open {
tmp += len(v)
}
if tmp == 0 {
return solutionsHolder
}
tmp = 0
} else if counter > width {
for _, v := range open {
tmp += len(v)
}
if tmp == 0 {
return solutionsHolder
}
tmp = 0
} else if counter < width {
i++
if len(open) <= i {
open = append(open, []int{3, 2})
} else {
open[i] = append(open[i], []int{3, 2}...)
}
}
} else {
i--
}
}
}
Its the wall function that calls a recursive function which will run for a very long time (i've had it run for an hour without a result) if you insert a width and height of wall(32,10) as they want you to compute on the website.
buildWall() which is called by wall checks whether there is no crack on the same position for two solutions and if this is the case it runs buildWall again for the next level of the wall until the walls height is reached. It does this for every solutions comparing it to all other possible solutions, though limited to one subset of the 3d array.
I'm thinking maybe there is another approach to find all the possible walls that can be combined, but i can't seem to wrap my head around it.

Swift: Binary search for standard array?

I have a sorted array and want to do binary search on it.
So I'm asking if something is already available in Swift library like sort etc.? Or is there a type independend version available?
Of course I could write it by my own, but I like to avoid reinventing the wheel again.
Here's my favorite implementation of binary search. It's useful not only for finding the element but also for finding the insertion index. Details about assumed sorting order (ascending or descending) and behavior with respect to equal elements are controlled by providing a corresponding predicate (e.g. { $0 < x } vs { $0 > x } vs { $0 <= x } vs { $0 >= x }). The comment unambiguously says what exactly does it do.
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
}
}
Example usage:
(0 ..< 778).binarySearch { $0 < 145 } // 145
Here's a generic way to use binary search:
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)")
}
I use an extension on RandomAccessCollection implementing bisectToFirstIndex(where:) and taking a predicate.
It takes a test predicate, and returns the index of the first element to pass the test.
If there is no such index, it returns nil.
If the Collection is empty, it returns nil.
Example
let a = [1,2,3,4]
a.map{$0>=3}
// returns [false, false, true, true]
a.bisectToFirstIndex {$0>=3}
// returns 2
Important
You need to ensure test never returns a false for any index after an index it has said true for. This is equivalent to the usual precondition that binary search requires your data to be in order.
Specifically, you must not do a.bisectToFirstIndex {$0==3}. This will not work correctly.
Why?
bisectToFirstIndex is useful because it lets you find ranges of stuff in your data. By adjusting the test, you can find the lower and upper limits of "stuff".
Here's some data:
let a = [1,1,1, 2,2,2,2, 3, 4, 5]
We can find the Range of all the 2s like this…
let firstOf2s = a.bisectToFirstIndex { $ 0>= 2 }
let endOf2s = a.bisectToFirstIndex { $0 > 2 }
let rangeOf2s = firstOf2s ..< endOf2s
Example Application
I use this in an implementation of layoutAttributesForElementsInRect. My UICollectionViewCells are stored sorted vertically in an array. It's easy to write a pair of calls that will find all cells that are within a particular rectangle and exclude any others.
Code
extension RandomAccessCollection {
public func bisectToFirstIndex(where predicate: (Element) throws -> Bool) rethrows -> Index? {
var intervalStart = startIndex
var intervalEnd = endIndex
while intervalStart != intervalEnd {
let intervalLength = distance(from: intervalStart, to: intervalEnd)
guard intervalLength > 1 else {
return try predicate(self[intervalStart]) ? intervalStart : nil
}
let testIndex = index(intervalStart, offsetBy: (intervalLength - 1) / 2)
if try predicate(self[testIndex]) {
intervalEnd = index(after: testIndex)
}
else {
intervalStart = index(after: testIndex)
}
}
return nil
}
}
Updates…
The implementation here extends RandomAccessCollection and I've updated the code to build with the current Swift version (5 or something).
A Binary Search Caution
Binary searches are notoriously hard to correctly code. You really should read that link to find out just how common mistakes in their implementation are, but here is an extract:
When Jon Bentley assigned it as a problem in a course for professional programmers, he found that an astounding ninety percent failed to code a binary search correctly after several hours of working on it, and another study shows that accurate code for it is only found in five out of twenty textbooks. Furthermore, Bentley's own implementation of binary search, published in his 1986 book Programming Pearls, contains an error that remained undetected for over twenty years.
Given that last point, here is a test for this code. It passes! The testing isn't exhaustive – so there may certainly still be errors.
Tests
final class Collection_BisectTests: XCTestCase {
func test_bisect() {
for length in 0...100 {
let collection = 0 ... length
let targets = -4 ... length + 4
for toFind in targets {
let bisectIndex = collection.bisectToFirstIndex { $0 > toFind }
let expectIndex = collection.firstIndex { $0 > toFind }
XCTAssertEqual(bisectIndex, expectIndex, "Finding \(toFind+1) in 0...\(length)")
}
}
}
}
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)
}
}
This is, in my opinion, very readable and leverages the fact that Swift's ArraySlice is a view on Array and retains the same indexes as the original Array with which it shares the storage so, in absence of mutations (like in this case), it is therefore very efficient.
here is binary search using while syntax
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
}
Here is an implementation for a sorted array of strings.
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"))
Another implementation: if you want to have your structs or classes searchable without making them Comparable, make them BinarySearchable instead:
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
}
}
Example usage for a struct that should be sorted and searched by 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")
And for completeness, here's a entirely pattern matching based implementation:
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)
}
}
}
The above code should work with any kind of collections, sliced or not sliced, zero offset-ed or non-zero offset-ed.
Here is how you create a binary search function in swift 5, in this example I assume that the item you are looking for is guaranteed to be in the list, however if your item is not guaranteed to be in the list then you can run this code to check first:
yourList.contains(yourItem) //will return true or false
Here is the binary search function:
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
}
I have made a youtube video explaining this here
Here's a better implementation that returns more than one index, if there are more than 1 in the array.
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
}
}
By recursive binary search,
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)
}
}
Input : let arry = Array(0...5) // [0,1,2,3,4,5]
print(binarySearch(data: arry, search: 0, high: arry.count-1, low: 0))
Here's a full example with several test cases for Swift 3.1. There is no chance that this is faster than the default implementation, but that's not the point. Array extension is at the bottom:
// 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)
}
}
This is quite homemade, so... caveat emptor. It does work and does do binary search.
Simple solution in 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)
Details
Swift 5.2, Xcode 11.4 (11E146)
Solution
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)
}
}
Usage
//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))
Tests
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() }
}

Resources