Why is it saying the index is out of range? - arrays

It is saying "error, index out of range"
I already tried making it a 0..< that still returns the error. Been looking over the code for 30 minutes can't figure out what I messed up
func kidsWithCandies(_ candies: [Int], _ extraCandies: Int) -> [Bool] {
var greatestCandyNum = 0
var arrayOfBools = Array<Bool>()
for kid in candies {
if candies[kid] > greatestCandyNum {
greatestCandyNum = candies[kid]
arrayOfBools.append(false)
}
}
for kid in candies {
if candies[kid] + extraCandies >= greatestCandyNum{
arrayOfBools[kid] = true
}
}
for kid in candies {
if candies[kid] == greatestCandyNum {
arrayOfBools[kid] = true
} else if candies[kid] > greatestCandyNum {
greatestCandyNum = candies[kid]
arrayOfBools[kid] = true
}
}
return arrayOfBools
}

So the main issue is that you cannot pass the value from the for loop into the index of the array. You can use the enumerate function to generate the index of where you are in the array position.
As seen with:
for (index, kid) in candies.enumerated()
I also fixed your initial for loop that would not generate a consistent Bool array if your candies array was not ascending. This was done with an else
Also please see that when iterating you can use the "kid" value rather than trying to find it again in the array.
Disclaimer: There are however much better ways of doing this.
func kidsWithCandies(_ candies: [Int], _ extraCandies: Int) -> [Bool] {
var greatestCandyNum = 0
var arrayOfBools = Array<Bool>()
for kid in candies {
if kid > greatestCandyNum {
greatestCandyNum = kid
arrayOfBools.append(false)
} else {
arrayOfBools.append(true)
}
}
for (index, kid) in candies.enumerated() {
if kid + extraCandies >= greatestCandyNum {
arrayOfBools[index] = true
}
}
for (index, kid) in candies.enumerated() {
if kid == greatestCandyNum {
arrayOfBools[index] = true
} else if kid > greatestCandyNum {
greatestCandyNum = kid
arrayOfBools[index] = true
}
}
return arrayOfBools
}

Related

Why is this array clearing

I have an issue where I am calling a function, it's appending an array, and then I get a random element from it, the problem is that is keeps getting wiped right after the function is called before I can use it, therefore, the random element request is causing a fatal error.
Utilities.getKnockKnockJokes {
self.previousKnockKnockJoke = self.currentKnockKnockJoke
print("currentKnockKnockJoke = \(self.currentKnockKnockJoke)")
self.currentKnockKnockJoke = KnockKnockJokes.knockKnockJokes.randomElement()!
print("newCurrentKnockKnockJoke = \(self.currentKnockKnockJoke)")
self.singleServeText.text = self.currentKnockKnockJoke
}
The function called is below:
static func getKnockKnockJokes(completion: #escaping () -> Void) {
let db = Firestore.firestore()
let group = DispatchGroup()
group.enter()
DispatchQueue.global(qos: .background).async {
print("countKnockKnockJokes = \(self.countKnockKnockJokes)")
if self.countKnockKnockJokes == 0 {
while self.countKnockKnockJokes == 0 {
if self.countKnockKnockJokes == 0 {
self.countKnockKnockJokes = 1
group.leave()
}else {
print("leaving")
group.leave()
}
}
}else {
print("skipped")
group.leave()
}
}
group.notify(queue: .main) {
db.collection("jokes").document("Knock Knock Jokes").addSnapshotListener { document, error in
//check for error
if error == nil {
//check if document exists
if document != nil && document!.exists {
if let JokeNum = document!.get("JokeNum") as? Int {
self.countKnockKnockJokes = JokeNum
UserDefaults.standard.setValue(JokeNum, forKey: "countKnockKnockJokes")
print("KnockKnockJokeNum = \(self.countKnockKnockJokes)")
}
var count = 1
print("count = \(count)/\(self.countKnockKnockJokes)")
print("countKnockKnockJoke = \(self.countKnockKnockJokes)")
//Utilities.knockKnockJokes.removeAll()
KnockKnockJokes.knockKnockJokes.removeAll()
for _ in 0...self.countKnockKnockJokes {
print("count = \(count)/\(self.countKnockKnockJokes)")
if let Joke = document!.get("\(count)") as? String {
print("KnockKnockJokeNum = \(self.countKnockKnockJokes)")
if Utilities.jokes.contains("\(Joke) - From Knock Knock Jokes") {}else {
print("Joke = \(Joke)")
Utilities.jokes.append("\(Joke) - From Knock Knock Jokes")
KnockKnockJokes.knockKnockJokes.append(Joke)
print("KnockKnockJokes = \(KnockKnockJokes.knockKnockJokes)")
UserDefaults.standard.set(KnockKnockJokes.knockKnockJokes, forKey: defaults.knockKnockJokes.rawValue)
Utilities.updateJokesDefaults()
}
print("countKnockKnockFinal = \(count)/\(self.countKnockKnockJokes)")
if count == self.countKnockKnockJokes {
completion()
}
count = count + 1
}
}
}
}
}
}
}
try this:
if count == self.countKnockKnockJokes {
completion()
return // <--- here
}
I fixed it, my remove all was running after all the appends.

Array Won't Sort Correctly : Swift 4

I was trying to sort an array of custom objects but for some reason, the code that worked a few weeks back won't work anymore. It is supposed to check if $0 and $1 have the same date, and if they do, it is supposed to sort the array by id, but currently it won't sort correctly and when I print the array, I get the following output:
11-07-2017 : 1
11-07-2017 : 10
11-07-2017 : 11
11-07-2017 : 12
11-07-2017 : 13
11-07-2017 : 14
11-07-2017 : 15
11-07-2017 : 16
11-07-2017 : 17
11-07-2017 : 18
11-07-2017 : 19
11-07-2017 : 2
11-07-2017 : 20
11-07-2017 : 3
11-07-2017 : 4
11-07-2017 : 5
11-07-2017 : 7
11-07-2017 : 8
11-07-2017 : 9
11-08-2017 : 1
11-08-2017 : 2
11-09-2017 : 1
11-09-2017 : 2
As can be seen above, the dates that only have a few entries sort correctly, but the dates with more entries (11-07-17) don't.
Below is my code:
Model for the Array:
struct BalanceUser {
var id = ""
var name = ""
var date = ""
}
Current Sorting Code:
self.sortedTableArray.sort(by: {
if $0.date != $1.date {
return $0.date < $1.date
} else {
return $0.id < $1.id
}
})
Firebase Code (As Requested):
ref.child("Admin").child("Balances").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
if value!.count > 1 {
let specificValues = value?.allKeys
for balanceUser in specificValues! {
var user = BalanceUser()
user.date = balanceUser as! String
if balanceUser as? String != "balance" {
var i = 0
var counter = 0
while i < 101 {
self.ref.child("Admin")
.child("Balances")
.child(balanceUser as! String)
.child(String(i))
.observeSingleEvent(of: .value, with: { (snapshot) in
let nameValue = snapshot.value as? NSDictionary
if nameValue != nil {
user.id = counter
var j = 0
while j < (nameValue?.count)! {
let item = nameValue?.allKeys[j] as? String
var aItem = ""
if let item = nameValue?.allValues[j] as? String {
aItem = item
} else if let item = nameValue?.allValues[j] as? NSNumber {
aItem = String(describing: item)
} else if let item = nameValue?.allValues[j] as? Int {
aItem = String(describing: item)
}
if item == "name" {
user.name = aItem
} else if item == "money" {
user.money = aItem
} else if item == "balance" {
user.balance = aItem
} else if item == "status" {
user.status = aItem
}
j += 1
}
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy"
if user.date.components(separatedBy: "-")[0] == dateFormatter.string(from: Date()).components(separatedBy: "-")[0] {
self.sortedTableArray.append(user)
self.sortedTableArray.sort(by: { (object1, object2) -> Bool in
if object1.date == object2.date {
return object1.id < object2.id
} else {
return object1.date < object2.date
}
})
}
self.tableArray.append(user)
self.tableArray.sort(by: { (object1, object2) -> Bool in
if object1.date == object2.date && object1.year == object2.year {
return object2.id > object1.id
} else {
return object1.date < object2.date || object1.year < object2.year
}
})
self.tableView.reloadData()
}
counter += 1
}) { (error) in
print(error.localizedDescription)
}
i += 1
}
}
}
} else {
self.view.makeToast(message: "No Users Found in Database")
}
}) { (error) in
print(error.localizedDescription)
}
It's sorting absolutely correct as you write it.
Strings comparing work for every char from the beginning. If first char is equal then check second and so on.
In your results "10" < "2", cause unicode of "1"-character is less then "2"-character code.
You need to compare do it like this:
self.sortedTableArray.sort(by: {
if $0.date != $1.date {
return $0.date < $1.date
} else {
return Int($0.id) ?? 0 < Int($1.id) ?? 0
}
})
Also your should compare dates as Date not Strings.
Just use the string comparator which sorts numeric strings properly
self.sortedTableArray.sort(by: {
if $0.date != $1.date {
return $0.date < $1.date
} else {
return $0.id.localizedStandardCompare($1.id) == .orderedAscending
}
})
or the standard compare selector with option .numeric
return $0.id.compare($1.id, options: .numeric) == .orderedAscending
You can extend your BalanceUser adding computed properties to return year, month, day and id value. Next just make your struct conform to Comparable protocol:
extension BalanceUser: Comparable {
var year: Int {
return Int(date.suffix(4))!
}
var month: Int {
return Int(date.prefix(2))!
}
var day: Int {
return Int(date.prefix(5).suffix(2))!
}
var idValue: Int {
return Int(id)!
}
static func ==(lhs: BalanceUser, rhs: BalanceUser) -> Bool {
return lhs.date == rhs.date && lhs.id == rhs.id
}
static func <(lhs: BalanceUser, rhs: BalanceUser) -> Bool {
return (lhs.year, lhs.month, lhs.day, lhs.idValue) < (rhs.year, rhs.month, rhs.day, rhs.idValue)
}
}
Now you can simply sort your custom type array:
sortedTableArray.sort()
Please check the following code:
let array = ["11-07-2017", "14-07-2017", "10-07-2017","08-07-2017"]
var convertedArray: [Date] = []
var dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"// yyyy-MM-dd"
for dat in array {
let date = dateFormatter.date(from: dat)
if let date = date {
convertedArray.append(date)
}
}
var ready = convertedArray.sorted(by: { $0.compare($1) == .orderedDescending })
print(ready)

How to iterate through multiple UILabels

I am looking for an elegant way to iterate through an array and assign each of its values to one or more of five UILabels
This code illustrates what I am trying to do (although it is very long and repetitive)
if touches.count >= 1 {
positionTouch1LBL.text = String(describing: touches[0].location(in: view))
} else {
positionTouch1LBL.text = "0.0 / 0.0"
}
if touches.count >= 2 {
positionTouch2LBL.text = String(describing: touches[1].location(in: view))
} else {
positionTouch2LBL.text = "0.0 / 0.0"
}
if touches.count >= 3 {
positionTouch3LBL.text = String(describing: touches[2].location(in: view))
} else {
positionTouch3LBL.text = "0.0 / 0.0"
}
if touches.count >= 4 {
positionTouch4LBL.text = String(describing: touches[3].location(in: view))
} else {
positionTouch4LBL.text = "0.0 / 0.0"
}
if touches.count >= 5 {
positionTouch5LBL.text = String(describing: touches[4].location(in: view))
} else {
positionTouch5LBL.text = "0.0 / 0.0"
}
You could put your labels inside another array and iterate through them:
let labelsArray = [positionTouch1LBL, positionTouch2LBL, positionTouch3LBL, positionTouch4BL, positionTouch5LBL]
for i in 0..<labelsArray.count {
// Get i-th UILabel
let label = labelsArray[i]
if touches.count >= (i+1) {
label.text = String(describing: touches[i].location(in: view))
}else{
label.text = "0.0 / 0.0"
}
}
This way you are able to group redundant code
You could do that by putting your labels in an array and iterate through them in the following way:
let labelsArray = [UILabel(), UILabel(), ... ] // An array containing your labels
for (index, element) in labelsArray.enumerated() {
if index < touches.count {
element.text = String(describing: touches[index].location(in: view))
} else {
element.text = "0.0 / 0.0"
}
}
Good luck!

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

Sorting an array to avoid neighboring items having duplicate attributes

I have an array of objects. Each object has a color attribute which could be "red", "blue", "yellow", "green", "orange" or "purple". There are 20-30 objects in the array so colors repeat. My goal is to sort the array so that no colors are next to each other. Distribution of colors is not exactly even but close.
This is what I have so far. It checks the next and previous object for a color match and if it finds a match it moves it to the end of the array.
private function sortColors():void
{
var getNext:uint;
var getPrev:uint;
var maxCount:uint = colorArray.length;
for (var i:uint = 0; i < maxCount; i++) {
var cur:ValueObject = colorArray[i];
(i == maxCount-1) ? getNext = 0 : getNext = i+1;
(i == 0) ? getPrev = maxCount-1 : getPrev = i-1;
var next:ValueObject = colorArray[getNext];
var prev:ValueObject = colorArray[getPrev];
if (cur.color == next.color) {
var move:ValueObject = colorArray[getNext];
colorArray.splice(getNext, 1);
colorArray.push(move);
}
if (cur.color == prev.color) {
var move:ValueObject = colorArray[getPrev];
colorArray.splice(getPrev, 1);
colorArray.push(move);
}
}
}
This works OK but if there is more of a certain color they end up repeating at the end. I could add something to the end to throw those back into the mix but I feel like there must be a better way. Someone enlighten me.
Try:
var colorObjects:Array = [/* list of objects with colors - populated below*/];
var jumbled:Array = [];
var lastColor:String = "";
function getDifferentTile():void
{
if(lastColor.length == 0)
{
jumbled.push(colorObjects.pop());
lastColor = jumbled[0].mycolor;
}
else
{
var i:Object;
for each(i in colorObjects)
{
var repeat:uint = 0;
if(i.mycolor != lastColor)
{
jumbled.push(i);
lastColor = i.mycolor;
colorObjects.splice(colorObjects.indexOf(i), 1);
return;
} else {
repeat++;
}
if (repeat > 0 && repeat == colorObjects.length) {
jumbled.push(i);
colorObjects.splice(colorObjects.indexOf(i), 1);
return;
}
}
}
}
// list of random colors
var colors:Array = ["0x000000","0x444444","0xFFFFFF","0xFF00FF"];
// prepare random array for test
var i:uint = 0;
for(i; i<100; i++)
{
var obj:Object =
{
mycolor: colors[uint(Math.random()*colors.length)]
};
colorObjects.push(obj);
}
// fill the jumble array until the original listing is empty
while(colorObjects.length > 0)
{
getDifferentTile();
}
// output jumbled
var j:Object;
for each(j in jumbled)
{
trace(j.mycolor);
}

Resources