Golang Binary search - go

I'm practicing an interview algorithm, now coding it in Go. The purpose is to practice basic interview algorithms, and my skills in Go. I'm trying to perform a Binary search of an array of numbers.
package main
import "fmt"
func main() {
searchField := []int{2, 5, 8, 12, 16, 23, 38, 56, 72, 91}
searchNumber := 23
fmt.Println("Running Program")
fmt.Println("Searching list of numbers: ", searchField)
fmt.Println("Searching for number: ", searchNumber)
numFound := false
//searchCount not working. Belongs in second returned field
result, _ := binarySearch2(searchField, len(searchField), searchNumber, numFound)
fmt.Println("Found! Your number is found in position: ", result)
//fmt.Println("Your search required ", searchCount, " cycles with the Binary method.")
}
func binarySearch2(a []int, field int, search int, numFound bool) (result int, searchCount int) {
//searchCount removed for now.
searchCount, i := 0, 0
for !numFound {
searchCount++
mid := i + (field-i)/2
if search == a[mid] {
numFound = true
result = mid
return result, searchCount
} else if search > a[mid] {
field++
//i = mid + 1 causes a stack overflow
return binarySearch2(a, field, search, numFound)
}
field = mid
return binarySearch2(a, field, search, numFound)
}
return result, searchCount
}
The main problems I'm coming across are:
1) When the number is higher in the list than my mid search, am I truly continuing a binary search, or has it turned to a sequential? How can I fix that? The other option I've placed has been commented out because it causes a stack overflow.
2) I wanted to add a step count to see how many steps it takes to finish the search. Something to use with other search methods as well. If I print out the search count as is, it always reads one. Is that because I need to return it (and therefore call for it in the header) in the method?
I understand Go has methods that streamline this process. I'm trying to increase my knowledge and coding skills. I appreciate your input.

You're not doing a binary search properly. First off, your for loop is useless, since each branch in the conditional tree has a return statement in it, so it can never run more than one iteration. It looks like you started to code it iteratively, then swapped to a recursive setup, but only kinda halfway converted it.
The idea of a binary search is that you have a high and low index and search the midway point between them. You're not doing that, you're just incrementing the field variable and trying again (which will cause you to search each index twice until you find the item or segfault by running past the end of the list). In Go, though, you don't need to keep track of the high and low indexes, as you can simply subslice the search field as appropriate.
Here's a more elegant recursive version:
func binarySearch(a []int, search int) (result int, searchCount int) {
mid := len(a) / 2
switch {
case len(a) == 0:
result = -1 // not found
case a[mid] > search:
result, searchCount = binarySearch(a[:mid], search)
case a[mid] < search:
result, searchCount = binarySearch(a[mid+1:], search)
if result >= 0 { // if anything but the -1 "not found" result
result += mid + 1
}
default: // a[mid] == search
result = mid // found
}
searchCount++
return
}
https://play.golang.org/p/UyZ3-14VGB9

func BinarySearch(a []int, x int) int {
r := -1 // not found
start := 0
end := len(a) - 1
for start <= end {
mid := (start + end) / 2
if a[mid] == x {
r = mid // found
break
} else if a[mid] < x {
start = mid + 1
} else if a[mid] > x {
end = mid - 1
}
}
return r
}

Off-topic, but might help others looking for a simple binary search who could land here.
There's a generic binary search module on github since the standard library doesn't offer this common functionality: https://github.com/bbp-brieuc/binarysearch

func BinarySearch(array []int, target int) int {
startIndex := 0
endIndex := len(array) - 1
midIndex := len(array) / 2
for startIndex <= endIndex {
value := array[midIndex]
if value == target {
return midIndex
}
if value > target {
endIndex = midIndex - 1
midIndex = (startIndex + endIndex) / 2
continue
}
startIndex = midIndex + 1
midIndex = (startIndex + endIndex) / 2
}
return -1
}

generic type version ! (go 1.18)
Time Complexity : log2(n)+1
package main
import "golang.org/x/exp/constraints"
func BinarySearch[T constraints.Ordered](a []T, x T) int {
start, mid, end := 0, 0, len(a)-1
for start <= end {
mid = (start + end) >> 1
switch {
case a[mid] > x:
end = mid - 1
case a[mid] < x:
start = mid + 1
default:
return mid
}
}
return -1
}
full version with iteration counter at playground.

func search(nums []int, target, lo, hi int) int {
if(lo > hi) {
return -1
}
mid := lo + (hi -lo) /2
if(nums[mid]< target){
return search2(nums, target,mid+1, hi)
}
if (nums[mid]> target){
return search2(nums, target,lo, mid -1)
}
return mid
}
https://www.youtube.com/watch?v=kNkeJ3ZtgJA

Related

Is there anyway to make the return text of a boolean function not show in the terminal

Im doing a project for school where I have to use binary search to figure out if a specific number is present in a string of numbers. I am new to coding and am trying to figure out a way to have "true" not show up in the terminal. Is there a way to do this or do I have to remove the boolean function. I know I cannot remove the "return true" from the code because it stops it from working but I want the output to just be the code I am printing not the "true"
func BinarySearch(target int, input []int) bool {
first := 0
last := len(input) - 1
for first <= last{
median := (first + last) / 2
if input[median] < target {
first = median + 1
}else{
last = median - 1
}
}
if first == len(input) || input[first] != target {
fmt.Println("The searched integer", target, "was not found")
} else {
fmt.Println("The searched integer", target, "was found")
}
return true
}
I have input the text I want to print but do not know what to do about the return
You can just do that by removing the bool after the params of the function, because basically you were telling that the function must return a boolean, while you were going to use it as a void. Dont forget also to pre-sort your input prior to passing it to your function. Best of luck
func BinarySearch(target int, input []int) {
first := 0
last := len(input) - 1
for first <= last{
median := (first + last) / 2
if input[median] < target {
first = median + 1
}else{
last = median - 1
}
}
if first == len(input) || input[first] != target {
fmt.Println("The searched integer", target, "was not found")
} else {
fmt.Println("The searched integer", target, "was found")
}
}
I would probably do something like this:
https://goplay.tools/snippet/vBgmo4ASUh5
package main
import (
"fmt"
)
func main() {
orderedList := []int{1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25}
for i := 0; i < 27; i++ {
var slug string
if found := BinarySearch(i, orderedList); !found {
slug = "not "
}
fmt.Printf("%d is %s in the list\n", i, slug)
}
}
func BinarySearch(target int, values []int) (found bool) {
lo := 0
hi := len(values)
for hi > lo {
mid := lo + (hi-lo)/2
if values[mid] < target {
lo = mid + 1
} else {
hi = mid
}
}
found = lo < len(values) && target == values[lo]
return found
}

Binary Search in Go - why the heck is this incorrect

Cant figure out why the heck is this incorrect implementation of Binary Search in go.
Input is ([]int{-1, 0, 3, 5, 9, 12}, 9)
func Search(nums []int, target int) int {
mid := len(nums) / 2
if nums[mid] == target {
return mid
}
if len(nums) >= 1 {
if nums[mid] < target {
return Search(nums[mid+1:], target)
} else {
return Search(nums[:mid], target)
}
}
return -1
}
Binary Search
func Search(nums []int, target int) int {
mid := len(nums) / 2
if nums[mid] == target {
return mid
}
if len(nums) >= 1 {
if nums[mid] < target {
return Search(nums[mid:], target) + mid
} else {
return Search(nums[:mid], target)
}
}
return -1
}
The one line that was changed is the following:
return Search(nums[mid:], target) + mid
Lets say, your slice is nums=[5,12,17,20,30,39,55,67]. Your target number is 55. When you make a recursive call on the right side of the array in the line return Search(nums[mid:], target), the new slice is nums=[30,39,55,67]. The index of 55 was 6 in the original slice but in the new slice, it is 2. That is why you are not getting the correct answer.
When you make a new slice from an existing slice, the index of the elements does not reflect the old slice. To make this up you need to add the current mid when you are selecting the right side of the slice for binary search.

How to find ip in range in very large struct

I have a struct like below, with about 100k entires.
I would like to loop over it and check if a ip address is in range.
My current code:
type Users struct {
Id string
Descr string
IpStart string
IpEnd string
}
var users []*Users
func LookUpIP(IpAddress string) (string, string) {
iptocheck := net.ParseIP(IpAddress)
for _, elem := range users {
if bytes.Compare(iptocheck, elem.IpStart) >= 0 && bytes.Compare(iptocheck, elem.IpEnd) <= 0 {
fmt.Printf("%v is between %v and %v\n", IpAddress, elem.IpStart, elem.IpEnd)
return elem.Id, elem.Descr
}
}
return "0", "null"
}
The above works fine with about 40k entires but over that it gets slow. Is there any faster way to find out if a ip address is in range inside my struct?
Update: Now only parsing IP once and storing it as number in struct
There are two simple steps I see.
Do the parsing once and store the IP address as a single number.
Order the ranges by start of the range and use binary search.
As a completion of #Grzegorz Żur suggestion to use binary search for reducing the search time, here is a binary search implementation in go.
But first what is binary search? A binary search divides a range of values into halves, and continues to narrow down the field of search until the unknown value is found. It is the classic example of a "divide and conquer" algorithm.
The algorithm returns the index of some element that equals the given value (if there are multiple such elements, it returns some arbitrary one). It is also possible, when the element is not found, to return the "insertion point" for it (the index that the value would have if it were inserted into the array).
The recursive method
func binarySearch(a []float64, value float64, low int, high int) int {
if high < low {
return -1
}
mid := (low + high) / 2 // calculate the mean of two values
if a[mid] > value {
return binarySearch(a, value, low, mid-1)
} else if a[mid] < value {
return binarySearch(a, value, mid+1, high)
}
return mid
}
The iterative method
func binarySearch(a []float64, value float64) int {
low := 0
high := len(a) - 1
for low <= high {
mid := (low + high) / 2
if a[mid] > value {
high = mid - 1
} else if a[mid] < value {
low = mid + 1
} else {
return mid
}
}
return -1
}
But if you take a look into the sort package you can observe that there is an already implemented sorting algorithm based on binary search. So probably this would be the best option.

Golang: Find two number index where the sum of these two numbers equals to target number

The problem is: find the index of two numbers that nums[index1] + nums[index2] == target. Here is my attempt in golang (index starts from 1):
package main
import (
"fmt"
)
var nums = []int{0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 25182, 25184, 25186, 25188, 25190, 25192, 25194, 25196} // The number list is too long, I put the whole numbers in a gist: https://gist.github.com/nickleeh/8eedb39e008da8b47864
var target int = 16021
func twoSum(nums []int, target int) (int, int) {
if len(nums) <= 1 {
return 0, 0
}
hdict := make(map[int]int)
for i := 1; i < len(nums); i++ {
if val, ok := hdict[nums[i+1]]; ok {
return val, i + 1
} else {
hdict[target-nums[i+1]] = i + 1
}
}
return 0, 0
}
func main() {
fmt.Println(twoSum(nums, target))
}
The nums list is too long, I put it into a gist:
https://gist.github.com/nickleeh/8eedb39e008da8b47864
This code works fine, but I find the return 0,0 part is ugly, and it runs ten times slower than the Julia translation. I would like to know is there any part that is written terrible and affect the performance?
Edit:
Julia's translation:
function two_sum(nums, target)
if length(nums) <= 1
return false
end
hdict = Dict()
for i in 1:length(nums)
if haskey(hdict, nums[i])
return [hdict[nums[i]], i]
else
hdict[target - nums[i]] = i
end
end
end
In my opinion if no elements found adding up to target, best would be to return values which are invalid indices, e.g. -1. Although returning 0, 0 would be enough as a valid index pair can't be 2 equal indices, this is more convenient (because if you forget to check the return values and you attempt to use the invalid indices, you will immediately get a run-time panic, alerting you not to forget checking the validity of the return values). As so, in my solutions I will get rid of that i + 1 shifts as it makes no sense.
Benchmarking of different solutions can be found at the end of the answer.
If sorting allowed:
If the slice is big and not changing, and you have to call this twoSum() function many times, the most efficient solution would be to sort the numbers simply using sort.Ints() in advance:
sort.Ints(nums)
And then you don't have to build a map, you can use binary search implemented in sort.SearchInts():
func twoSumSorted(nums []int, target int) (int, int) {
for i, v := range nums {
v2 := target - v
if j := sort.SearchInts(nums, v2); v2 == nums[j] {
return i, j
}
}
return -1, -1
}
Note: Note that after sorting, the indices returned will be indices of values in the sorted slice. This may differ from indices in the original (unsorted) slice (which may or may not be a problem). If you do need indices from the original order (original, unsorted slice), you may store sorted and unsorted index mapping so you can get what the original index is. For details see this question:
Get the indices of the array after sorting in golang
If sorting is not allowed:
Here is your solution getting rid of that i + 1 shifts as it makes no sense. Slice and array indices are zero based in all languages. Also utilizing for ... range:
func twoSum(nums []int, target int) (int, int) {
if len(nums) <= 1 {
return -1, -1
}
m := make(map[int]int)
for i, v := range nums {
if j, ok := m[v]; ok {
return j, i
}
m[target-v] = i
}
return -1, -1
}
If the nums slice is big and the solution is not found fast (meaning the i index grows big) that means a lot of elements will be added to the map. Maps start with small capacity, and they are internally grown if additional space is required to host many elements (key-value pairs). An internal growing requires rehashing and rebuilding with the already added elements. This is "very" expensive.
It does not seem significant but it really is. Since you know the max elements that will end up in the map (worst case is len(nums)), you can create a map with a big-enough capacity to hold all elements for the worst case. The gain will be that no internal growing and rehashing will be required. You can provide the initial capacity as the second argument to make() when creating the map. This speeds up twoSum2() big time if nums is big:
func twoSum2(nums []int, target int) (int, int) {
if len(nums) <= 1 {
return -1, -1
}
m := make(map[int]int, len(nums))
for i, v := range nums {
if j, ok := m[v]; ok {
return j, i
}
m[target-v] = i
}
return -1, -1
}
Benchmarking
Here's a little benchmarking code to test execution speed of the 3 solutions with the input nums and target you provided. Note that in order to test twoSumSorted(), you first have to sort the nums slice.
Save this into a file named xx_test.go and run it with go test -bench .:
package main
import (
"sort"
"testing"
)
func BenchmarkTwoSum(b *testing.B) {
for i := 0; i < b.N; i++ {
twoSum(nums, target)
}
}
func BenchmarkTwoSum2(b *testing.B) {
for i := 0; i < b.N; i++ {
twoSum2(nums, target)
}
}
func BenchmarkTwoSumSorted(b *testing.B) {
sort.Ints(nums)
b.ResetTimer()
for i := 0; i < b.N; i++ {
twoSumSorted(nums, target)
}
}
Output:
BenchmarkTwoSum-4 1000 1405542 ns/op
BenchmarkTwoSum2-4 2000 722661 ns/op
BenchmarkTwoSumSorted-4 10000000 133 ns/op
As you can see, making a map with big enough capacity speeds up: it runs twice as fast.
And as mentioned, if nums can be sorted in advance, that is ~10,000 times faster!
If nums is always sorted, you can do a binary search to see if the complement to whichever number you're on is also in the slice.
func binary(haystack []int, needle, startsAt int) int {
pivot := len(haystack) / 2
switch {
case haystack[pivot] == needle:
return pivot + startsAt
case len(haystack) <= 1:
return -1
case needle > haystack[pivot]:
return binary(haystack[pivot+1:], needle, startsAt+pivot+1)
case needle < haystack[pivot]:
return binary(haystack[:pivot], needle, startsAt)
}
return -1 // code can never fall off here, but the compiler complains
// if you don't have any returns out of conditionals.
}
func twoSum(nums []int, target int) (int, int) {
for i, num := range nums {
adjusted := target - num
if j := binary(nums, adjusted, 0); j != -1 {
return i, j
}
}
return 0, 0
}
playground example
Or you can use sort.SearchInts which implements binary searching.
func twoSum(nums []int, target int) (int, int) {
for i, num := range nums {
adjusted := target - num
if j := sort.SearchInts(nums, adjusted); nums[j] == adjusted {
// sort.SearchInts returns the index where the searched number
// would be if it was there. If it's not, then nums[j] != adjusted.
return i, j
}
}
return 0, 0
}

Go recursive binary search

I know that Go has a sort package that contains search functions, but this is for educational purposes. I've been trying to implement a binary search algorithm in Go but I haven't been able to get it to work.
Here is my code:
package main
import "fmt"
func BinarySearch(data []int, target int, low int, high int) (index int, found bool) {
mid := (high + low) / 2
if low > high {
index = -1
found = false
} else {
if target < data[mid] {
BinarySearch(data, target, low, mid - 1)
} else if target > data[mid] {
BinarySearch(data, target, mid + 1, high)
} else if target == data[mid] {
index = mid
found = true
} else {
index = -1
found = false
}
}
return
}
func main() {
data := []int {2, 4, 6, 8, 9, 11, 12, 24, 36, 37, 39, 41, 54, 55, 56,}
index, found := BinarySearch(data, 8, 0, len(data) - 1)
fmt.Println(index, found)
}
It always prints 0 false. Why?
The logic of your binary search is sound. The only problem is that you've forgotten to assign the result of each recursive call to index and found.
Currently you have these recursive calls:
BinarySearch(data, target, low, mid - 1)
//...
BinarySearch(data, target, mid + 1, high)
You merely have to assign the results:
index, found = BinarySearch(data, target, low, mid - 1)
//...
index, found = BinarySearch(data, target, mid + 1, high)
Logic for Binary Search
Target is to search for an item in an array.
Get the middle item of the array.
If more than the desired value => First item till the end item.
If less than the desired value => Middle item till the end item
Repeat process
We implement this using recursion or a loop.
Using recursion to make a binary search
Function will include an array where we will do a search. Then target value is equal to the value we are searching for.
lowIndex will indicate the begining of our search.
highIndex indicates the last position of our search.
Then the function returns the position of the target value we are searching for.
The reason for including the lowIndex and highIndex in the arguments is to search a subset of the array.
func binarySearch(array []int, target int, lowIndex int, highIndex int) int {
//specify condition to end the recursion
if highIndex < lowIndex {
return -1
}
// Define our middle index
mid := int((lowIndex + highIndex) / 2)
if array[mid] > target {
return binarySearch(array, target, lowIndex,mid)
}else if array[mid] < target {
return binarySearch(array, target,mid+1,highIndex)
}else {
return mid
}
}
Using a loop to make a binary search
func iterbinarySearch(array []int, target int, lowIndex int, highIndex int) int {
startIndex := lowIndex
endIndex := highIndex
var mid int
for startIndex < endIndex {
mid = int((lowIndex + highIndex) / 2)
if array[mid] > target {
return binarySearch(array, target, lowIndex, mid)
} else if array[mid] < target {
return binarySearch(array, target, mid+1, highIndex)
} else {
return mid
}
}
return -1
}
http://play.golang.org/p/BbL-y7pJMi
Works fine as far as I can tell.

Resources