Search This Blog

Sunday, July 8, 2018

Swift Algorithms. Binary Search

How to solve following task: find element in the array. For this task we can use linear search algorithm - simply iterate over each element of the array until target element is found.

Linear Search

Unsorted array of integers.
let array = [45, 42, 75, 61, 37, 95, 30, 19, 81, 77, 44, 54, 8, 88, 85, 46, 39, 35, 65, 3]
Algorithm of linear search.
func linearSearch(array: [Int], target: Int) -> Bool {
    for i in 0..<(array.count - 1) {
        if array[i] == target {
            return true
        }
    }
    return false
}
Test results.
linearSearch(array: array, target: 65) // true
linearSearch(array: array, target: 68) // false
assert(true == linearSearch(array: array, target: 65), "Error happened")
assert(false == linearSearch(array: array, target: 68), "Error happened")
Time Complexity of Linear Search is O(n).


Binary Search Algorithm

How we can increase time from linear search? The solution is using of Binary Search. The main idea is to splitting the array by half until the target is found. But there is one requirement - array must be sorted.

Steps of Binary Search algorithm is following:
  1. Split the array by half and define whether the target, in the left half or in the right half.
  2. If the target is in the left half, you repeat the process there: split the left half into two even smaller pieces and look in which piece the target is. (The same for the case when it's the right half.)
  3. These steps repeats until the target is found. If the array cannot be split one more time, it means that target is not in the array

Time Complexity

What is the time complexity of Binary Search. How express what number of operations that required to find target in array? For example, there is array with 1000 elements, how many time we need to divide 1000 by 2 to get 1 value?

1000 / 2
500 / 2
250 / 2
125 / 2
65 / 2
33 / 2
17 / 2
9 / 2
5 / 2
3 / 2
1

Or it can be expresses as how many times we need to multiply 1 by 2 to get 1000?
N = 1*2ˆK 
It gives us the following K = logN, because logN is the number of times we need to raise 2 to the power of K to get N.
So, for finding target in array of 1000 elements we need maximum 9 operations instead of 1000 for linear search.

Recursive Implementation

Let's provide recursive implementation of Binary Search. There is entry point.
func binarySearch(array: [Int], target: Int) -> Bool {
    let left = 0
    let right = array.count - 1
    return binarySearchRecursive(array: array, target: target, left: left, right: right)
}
There is recursive function.
func binarySearchRecursive(array: [Int], target: Int, left: Int, right: Int) -> Bool {
    if left > right {
        return false
    }
    let midIndex = (left + right) / 2
    let midValue = array[midIndex]
    if midValue == target {
      return true
    } else if (target > midValue) {
        let newLeft = midIndex + 1
        return binarySearchRecursive(array: array, target: target, left: newLeft, right: right)
    } else {
        let newRight = midIndex - 1
        return binarySearchRecursive(array: array, target: target, left: left, right: newRight)
    }
}
Test results.
let sortedArray = [3, 8, 19, 30, 35, 37, 39, 42, 44, 45, 46, 54, 61, 65, 75, 77, 81, 85, 88, 95]
binarySearch(array: sortedArray, target: 88) // true
binarySearch(array: sortedArray, target: 89) // false
assert(true == binarySearch(array: sortedArray, target: 88), "Error happened")
assert(false == binarySearch(array: sortedArray, target: 89), "Error happened")

Problem with overflow

There is one thing that can cause error. The expression let midIndex = (left + right) / 2 could fail because of overflow when left and right are close to the max values of type Int.  To prevent his situation we can present this expression through difference between right and left. Algorithm is following:
  1. midIndex = (left + right) / 2
  2. (left + right) / 2 = (left + left - left+ right + right - right) / 2
  3. (left + left - left+ right + right - right) / 2 = (right - left + 2*left + (right - right)) / 2
  4. (right - left + 2*left + (right - right)) / 2 = (right - left + 2*left) / 2
  5. (right - left + 2*left) / 2 = (right - left) / 2 + left

So, we will replace this unsafe calculation of mid index
let midIndex = (left + right) / 2
to this safe calculation
let midIndex = left + (right - left) / 2
update algorithm
func binarySearchRecursive(array: [Int], target: Int, left: Int, right: Int) -> Bool {
    if left > right {
        return false
    }
    let midIndex = left + (right - left) / 2
    let midValue = array[midIndex]
    if midValue == target {
      return true
    } else if (target > midValue) {
        let newLeft = midIndex + 1
        return binarySearchRecursive(array: array, target: target, left: newLeft, right: right)
    } else {
        let newRight = midIndex - 1
        return binarySearchRecursive(array: array, target: target, left: left, right: newRight)
    }
}

With Range instead of Left and Right

Instead of two parameters left and right we can use one - range of type Range<Int>. Type range has two variable lowerBound and upperBound.
func binarySearchRecursive(array: [Int], target: Int, range: Range<Int>) -> Bool {
    if range.lowerBound >= range.upperBound {
        return false
    }
    let midIndex = range.lowerBound + (range.upperBound - range.lowerBound) / 2
    print(midIndex)
    let midValue = array[midIndex]
    if midValue == target {
        return true
    } else if (target > midValue) {
        let newRange: Range<Int> = (midIndex + 1)..<range.upperBound
        return binarySearchRecursive(array: array, target: target, range: newRange)
    } else {
        let newRange: Range<Int> = (range.lowerBound)..<midIndex
        return binarySearchRecursive(array: array, target: target, range: newRange)
    }
}

//let sortedArray = [3, 8, 19, 30, 35, 37, 39, 42, 44, 45, 46, 54, 61, 65, 75, 77, 81, 85, 88, 95]
binarySearchRecursive(array: sortedArray, target: 88, range: 0..<sortedArray.count) // true
binarySearchRecursive(array: sortedArray, target: 89, range: 0..<sortedArray.count) // false

Making Generic Function

We can make our function more universal by making it generic. Generic function can be applied to any array where elements are conform to protocol Comparable. For example, we can apply it to array of strings.
func binarySearchRecursive<T: Comparable>(array: [T], target: T, range: Range<Int>) -> Bool {
    if range.lowerBound >= range.upperBound {
        return false
    }
    let midIndex = range.lowerBound + (range.upperBound - range.lowerBound) / 2
    print(midIndex)
    let midValue = array[midIndex]
    if midValue == target {
        return true
    } else if (target > midValue) {
        let newRange: Range<Int> = (midIndex + 1)..<range.upperBound
        return binarySearchRecursive(array: array, target: target, range: newRange)
    } else {
        let newRange: Range<Int> = (range.lowerBound)..<midIndex
        return binarySearchRecursive(array: array, target: target, range: newRange)
    }
}

binarySearchRecursive(array: sortedArray, target: 81, range: 0..<array.count)
let strings = ["aa", "bb", "cc", "dd", "ee"]
binarySearchRecursive(array: strings, target: "dd", range: 0..<strings.count)

Iterative Implementation

Instead of using recursive calls we can implement Binary Search algorithm using iterative approach and while loop. On each iteration of loop we just will change lower or upper bounds of range.
func binarySearchIterative<T: Comparable>(array: [T], target: T) -> Bool {
    var lower = 0
    var upper = array.count
    while (lower <= upper) {
        let midIndex = lower + (upper - lower) / 2
        let midValue = array[midIndex]
        if midValue == target {
            return true
        } else if target > midValue {
            lower = midIndex + 1
        } else {
            upper = midIndex - 1
        }
    }
    return false
}

let sortedArray = [3, 8, 19, 30, 35, 37, 39, 42, 44, 45, 46, 54, 61, 65, 75, 77, 81, 85, 88, 95]
binarySearchIterative(array: sortedArray, target: 81) // true
binarySearchIterative(array: sortedArray, target: 82) // false

Making Extension

Now, using iterative solution that we provided above we can easily make extension for all Array where elements of array conform to Comparable protocol.
extension Array where Element: Comparable {
    
    func binarySearch(target: Element) -> Bool {
        var lower = 0
        var upper = self.count
        while (lower <= upper) {
            let midIndex = lower + (upper - lower) / 2
            let midValue = self[midIndex]
            if midValue == target {
                return true
            } else if target > midValue {
                lower = midIndex + 1
            } else {
                upper = midIndex - 1
            }
        }
        return false
    }
}

let sortedArray = [3, 8, 19, 30, 35, 37, 39, 42, 44, 45, 46, 54, 61, 65, 75, 77, 81, 85, 88, 95]
sortedArray.binarySearch(target: 81) // true
sortedArray.binarySearch(target: 82) // false

Source Code

Source code for this post could be found here on GitHub

Thursday, July 5, 2018

Swift Algorithms. Bubble Sort

Bubble Sort is the simplest sorting algorithm which iterates over elements of the array and swap adjacent elements if they are in wrong order. It repeat iterations until all elements will be in right order.

Example of unsorted and sorted array
let array = [19, 2, 66, 4, 45, 99, 101, 3, 5, 7, 17, 35, 42]
let sortedArray = [2, 3, 4, 5, 7, 17, 19, 35, 42, 45, 66, 99, 101]
First Version
We just iterate over elements and compare adjacent. And swap them in case of wrong order.
Also we use special variable counter for counting number of operations.
func bubbleSort1(array: [Int]) -> [Int] {
    var result = array
    let n = result.count
    var counter = 0
    for _ in 0..<(n - 1) {
        for j in 0..<(n - 1) {
            counter += 1
            if result[j] > result[j + 1] {
                let temp = result[j]
                result[j] = result[j + 1]
                result[j + 1] = temp
            }
        }
    }
    print("Count of operations in bubbleSort1: \(counter)")
    return result
}
Check the result of the algorithm. Time complexity of this algorithm is O(nˆ2) because of two for loops.
let result1 = bubbleSort1(array: array)
// Count of operations in bubbleSort1: 144
assert(result1 == sortedArray, "Error happened!")
Second Version
We can improve first solution. Because after each iteration over the array the biggest element moving to the end of the array. So, after first iteration we can compare only before n-2, after second iteration only before n-3 and etc. So we can change upper limit for inner for loop.
func bubbleSort2(array: [Int]) -> [Int] {
    var result = array
    let n = result.count
    var counter = 0
    for i in 0..<(n - 1) {
        for j in 0..<(n - i - 1) {
            counter += 1
            if result[j] > result[j + 1] {
                result.swapAt(j, j + 1)
            }
        }
    }
    print("Count of operations in bubbleSort2: \(counter)")
    return result
}
After optimisation we can see that number of operation is decreased, now it takes 78 instead of 144 in first solution. But still time complexity of algorithm is O(nˆ2) because there are two for loops.
let result2 = bubbleSort2(array: array)
// Count of operations in bubbleSort2: 78
assert(result2 == sortedArray, "Error happened!")
Third Version
We can optimise our solution even more. If after next iteration there were no swapping operations happened it means that array is sorted and we can exit from for loop. It gives us linear time complexity O(n) in case of array is already sorted, because we will have only one loop over n-elements for comparing elements.
func bubbleSort3(array: [Int]) -> [Int] {
    var result = array
    let n = result.count
    var counter = 0
    var isSwapped: Bool
    for i in 0..<(n - 1) {
        isSwapped = false
        for j in 0..<(n - i - 1) {
            counter += 1
            if result[j] > result[j + 1] {
                result.swapAt(j, j + 1)
                isSwapped = true
            }
        }
        if !isSwapped {
            break
        }
    }
    print("Count of operations in bubbleSort3: \(counter)")
    return result
}
We can see that count of operation is slightly decreased, now it is 63 instead of 78.
let result3 = bubbleSort3(array: array)
// Count of operations in bubbleSort3: 63
assert(result3 == sortedArray, "Error happened!")
But count of operations will be decreased significantly for case when array is sorted: from 63 to 12
print("Comparing, unsored array: \(bubbleSort3(array: array))")
// Count of operations in bubbleSort3: 63
print("Comparing, sorted array: \(bubbleSort3(array: sortedArray))")
// Count of operations in bubbleSort3: 12
Fourth Version
The same optimised algorithm but written with using while loop.
func bubbleSort4(array: [Int]) -> [Int] {
    var result = array
    var counter = 0
    let n = result.count
    var lastUnsorted = n - 1
    var isSorted = false
    while (!isSorted) {
        isSorted = true
        for i in 0..<(lastUnsorted) {
            counter += 1
            if result[i] > result[i + 1] {
                result.swapAt(i, i + 1)
                isSorted = false
            }
         }
        lastUnsorted -= 1
    }
    print("Count of operations in bubbleSort4: \(counter)")
    return result
}
Result of work
let result4 = bubbleSort4(array: array)
// Count of operations in bubbleSort3: 63
assert(result4 == sortedArray, "Error happened!")

Making Generic Function 

We can make generic function and use this algorithm for arrays with any type. Main condition that elements of the array are confirmed to Comparable protocol. Now we can sort array of strings, for example.
// Comparable:
// A type that can be compared using the relational operators <, <=, >=, and >.
func genericBubbleSort<T: Comparable>(list: [T]) -> [T] {
    var result = list
    let n = result.count
    var lastUnsorted = n - 1
    var isSorted = false
    while (!isSorted) {
        isSorted = true
        for i in 0..<lastUnsorted {
            if result[i] > result[i + 1] {
                result.swapAt(i, i + 1)
                isSorted = false
            }
        }
        lastUnsorted -= 1
    }
    return result
}

let stringsArray = ["China", "Japan", "Brazil", "Russia", "Ireland"]
genericBubbleSort(list: stringsArray)
// ["Brazil", "China", "Ireland", "Japan", "Russia"]

Making Extension

We can also make extension for more handy usage of our algorithm.
extension Array where Element: Comparable {
    
    func bubbleSort() -> [Element] {
        var result = self
        let n = result.count
        var lastUnsorted = n - 1
        var isSorted = false
        while (!isSorted) {
            isSorted = true
            for i in 0..<lastUnsorted {
                if result[i] > result[i + 1] {
                    result.swapAt(i, i + 1)
                    isSorted = false
                }
            }
            lastUnsorted -= 1
        }
        return result
    }
}

[19, 2, 66, 4, 45, 99, 101, 3, 5, 7, 17, 35, 42].bubbleSort()
// [2, 3, 4, 5, 7, 17, 19, 35, 42, 45, 66, 99, 101]
["China", "Japan", "Brazil", "Russia", "Ireland"].bubbleSort()
// ["Brazil", "China", "Ireland", "Japan", "Russia"]

Source Code

Source code for this post could be found here on GitHub

Wednesday, July 4, 2018

Coding Task in Swift. Find odd occurring number in array

Task: Given an array of of repeating integer numbers. All numbers are repeated an even number of times, except for one. Find that odd occurring number.

Solution with two for loops

Example of an array, there is one odd occurring number, it is 9.
let array = [1, 2, 3, 9, 3, 2, 1]
Algorithm is following:
  1. Create dictionary (hash table) with keys of type Int and values of type Int. Key is number itself, value is counter of this number.
  2. First for loop. For each number in array
    • If number already exist in dictionary then increment counter for this number by 1
    • Else add this number to dictionary as key and assign value of 1 to this number
  3. Second for loop. For each key/value pair in dictionary
    • If the value is odd (remainder of division by 2 is not 0) then return key (key is the number)
  4. By default return nil from function. So function will return type optional type Int?
func findOdd1(_ array: [Int]) -> Int? {
    var table = [Int: Int]()
    array.forEach {
        if let count = table[$0] {
            table[$0] = count + 1
        } else {
            table[$0] = 1
        }
    }
    for pair in table {
        if pair.value % 2 != 0 {
            return pair.key
        }
    }
    return nil
}
There are two for loop for N elements, so time complexity for each will be 0(n). It will give us O(n) + O(n) that equals to O(2n) that equals to O(n) because we can drop coefficients when estimate the complexity.
Get result and check for errors
findOdd1(array) // 9
assert(findOdd1(array) == 9, "Error happened")
// Time Complexity is O(n) + O(n) because of two for loop,
// but it gives O(2n) that is equivalent to O(n)

Optimised Solution

There are ways to modify and optimize first solution. When we iterate over elements in array we can check if number occur even number of times we can remove it from dictionary. So as result we will have in dictionary only numbers that occurred odd number of times. Here we have only one such number in array, so we return first key from dictionary.
// Optimised solution
func findOdd2(_ array: [Int]) -> Int? {
    var table = [Int: Bool]()
    array.forEach {
        if table[$0] != nil {
            table[$0] = nil
        } else {
            table[$0] = true
        }
    }
    return table.keys.first
}
There is only one for loop over n elements is array. So time complexity will be O(n)
Get result and check for errors
findOdd2(array) // 9
assert(findOdd2(array) == 9, "Error happened")
// Time Complexity is O(n) - we remove one for loop.

Source Code

Source code can be found here on GitHub