Trying to understand binary search - ruby

I'm trying to understand this code my pairing partner wrote. I dont understand why she used the until loop stating to loop until (finish - start) == 1. What exactly is she looping until?
def binary_search(object, array)
array.sort!
start = -1
finish = array.length
until (finish - start) == 1 do
median = start + ((finish - start) / 2)
# p start
# p finish
return median if object == array[median]
if object > array[median]
start = median
elsif object < array[median]
finish = median
end
end
-1
end

finish - start is the length of the window left to search (+ 1, for easier arithmetic); it starts of as the entire array and gets halved on every iteration, by setting either the start or the finish to the median.
When it reaches 1, there is nothing left to search, and the input object was not found.

Think about how kids play the "guess a number between 1 and 100" game. "Is it bigger than 50?" "No." You now know it's a number between 1 and 50. "Is it bigger than 25?" "Yes." You now know it's between 26 and 50. And so on...
It's the same with binary search. You check to see if the target is above or below the midrange. Whichever way the answer turns out, you've eliminated half of the possibilities and can focus on the remaining subset. Every time you repeat the process, you cut the range that's still under consideration in half. When the range gets down to size one, you've either found the target value or established it wasn't in the set.

Related

Running a function multiple times and tracking results of the fight simulation

Ive made a function to run a fight simulation. Its got a random element so would like to run it 100 times to check results.
Ive learnt that ruby cant have functions inside functions.
$p1_skill = 10
$p1_health = 10
$p2_skill = 10
$p2_health = 10
def hp_check
if $p2_health >= 1 && $p1_health == 0
return "p2_wins"
elsif $p1_health >= 1 && $p2_health == 0
return "p1_wins"
else
battle
end
end
def battle
p1_fight = $p1_skill + rand(2..12)
p2_fight = $p2_skill + rand(2..12)
if p1_fight > p2_fight
$p2_health -= 2
hp_check
elsif p2_fight > p1_fight
$p1_health -= 2
hp_check
else
battle
end
end
battle
Right now this accurately produces a winner. It rolls two dice and adds them to a players skill. If its higher than the other players the other player loses 2 health.
The skills and hp of players will change throughout the game, this is for a project assignment.
Id like this to produce odds for win chances for balancing issues.
I have several suggestions regarding your implementation. Note that since this is a homework I'm providing the answer in pieces rather than just giving you an entire program. In no particular order...
Don't use global variables. I suspect this is the major hurdle you're running into with trying to achieve multiple runs of your model. The model state should be contained within the model methods, and initial state can be passed to it as arguments. Example:
def battle(p1_skill, p1_health, p2_skill, p2_health)
Unless your instructor has mandated that you use recursion, a simple loop structure will serve you much better. There's no need to check who won until one player or the other drops down to zero (or lower). There's also no need for an else to recursively call battle, the loop will iterate to the next round of the fight if both are still in the running, even if neither player took a hit.
while p1_health > 0 && p2_health > 0
# roll the dice and update health
end
# check who won and return that answer
hp_check really isn't needed, when you lose the recursive calls it becomes a one-liner if you perform the check after breaking out of the loop. Also, it would be more useful to return just the winner, so whoever gets that return value can decide whether they want to print it, use it to update a tally, both, or something else entirely. After you break out of the loop outlined above:
# determine which player won, since somebody's health dropped to 0 or less
p1_health > 0 ? 1 : 2
When you're incrementing or decrementing a quantity, don't do equality testing. p1_health <= 0 is much safer than p1_health == 0, because some day you or somebody else is going to start from an odd number while decrementing by 2's, or decrement by some other (random?) amount.
Generating a number uniformly between 2 and 12 is not the same as summing two 6-sided dice. There are 36 possible outcomes for the two dice. Only one of the 36 yields a 2, only one yields a 12, and at the other extreme, there are six ways to get a sum of 7. I created a little die-roll method which takes the number of dice as an argument:
def roll_dice(n)
n.times.inject(0) { |total| total + rand(1..6) }
end
so, for example, determining player 1's fight score becomes p1_fight = p1_skill + roll_dice(2).
After making these sorts of changes, tallying up the statistics is pretty straightforward:
n = 10000
number_of_p1_wins = 0
n.times { number_of_p1_wins += 1 if battle(10, 10, 10, 10) == 1 }
proportion = number_of_p1_wins.to_f / n
puts "p1 won #{"%5.2f" % (100.0 * proportion)}% of the time"
If you replace the constant 10's in the call to battle by getting user input or iterating over ranges, you can explore a rich set of other scenarios.

Optimal way to get the maximum list of events one can attend

For the problem, the input is an array like this:
[[4,8],[1,3],[7,9],[5,6]]
where the first number of every element represents the start hour and the second number the end hour.
What is the optimal way to get the maximum list of events one can attend?
My approach was:
Sort the array in ascending order by start hour
Iterate trough every element from last to first and check if they overlap (start hour of the current element is lower than the end hour of the element at the left).
If there is an overlap, remove the left element and check again
The code is:
def time_schedule(ar)
#Assuming the ar has been sorted
idx = input.length - 1
while idx >= 1
while idx >= 1 && input[idx - 1][1] > input[idx][0]
input.delete(input[idx - 1])
idx -= 1
end
idx -= 1
end
input
end
Your logic is close, but wrong. Because you can run into a long event when you could have accepted multiple small ones.
Instead sort ascending by end hour. Accept each event that does not overlap with any accepted before it.

Ruby - Finding the third greatest value in an array - need help understanding method.

new to coding! I love it and have been doing a lot of practice problems to help me qualify for a program I am looking to apply to next year. This is probably a very simple question for many of you, thanks in advance for the help.
One of the problems listed was to find the third greatest value in an array of numbers. This is very easy.
However, the coding problems I am doing come with a solution and it's usually a solution that doesn't use a lot of built in methods so it's harder for me to understand.
#my solution
def third_greatest(num)
array = num.sort.reverse
return array[2]
end
What I would like to know is how the while loop works in their solution. I can't seem to understand what is going on.
#their solution
def third_greatest(nums)
first = nil
second = nil
third = nil
idx = 0
while idx < nums.length
value = nums[idx]
if first == nil || value > first
third = second
second = first
first = value
elsif second == nil || value > second
third = second
second = value
elsif third == nil || value > third
third = value
end
idx += 1
end
return third
end
The provided solution keeps track of the first, second, and third largest values in the array. These all start out as nil. The while loop goes through each element in the array one at a time, and compares that element to the current largest 3 values (that is, the largest 3 values that have been seen thus far, as we work our way through the array). If any of those "top 3" values are still nil, or if the current element being examined is larger than any of those values, then the current value is inserted into the correct place in the "top 3" (by assigning it to either first, second, or third, but only after first "shifting over" the preexisting values contained in those "top 3" variables).
For example, if first is currently 7, second is 4, and third is 3, and the current element that I'm checking is 10, then I want to shift 4 (i.e. second) into third place, then shift 7 (i.e. first) into second place, and then assign 10 to be the new first.
Your solution has the advantage of being simpler to read (and to write). You might be wondering if there is any advantage gained by the relative complexity of the provided solution. There is. I don't know if you have learned about time complexity / Big O notation, yet, but the provided solution is O(n) (because it basically just makes a single loop through the array), which will have better performance on average compared to your solution, which is O(n log(n)) (the best/lowest possible time complexity for any algorithm that involves sorting an array).

Code times out Ruby

According as the number of elements in a set of numbers is odd or even, median of that set is defined respectively as the middle value or the average of the two middle values in the list that results when the set is sorted.
Below is code for calculating the "running" median of a list of numbers. "Running" median is a dynamic median which is re-calculated with the appearance of a new number as the list is scanned for all numbers that have appeared thus far. Input is an integer n followed by a list of n integers, and output should be the "running" median of the list as the list is scanned. For example,
3
4
1
5
should yield
4
2.5
4
because 4 is the median of [4], 2.5 ((1+4)/2)is the median of [4,1] and 4 again is the median of [4,1,5].
My program works correctly, but it times out on a certain test on very large inputs. I suspect that this copying step is the problem.
a=(a[0,(k=a.index(a.bsearch{|x|x>=t}))].push(t) + a[k,a.length-k])
But I am not sure because this copy is meant to be a shallow copy as far as I know. Also, I am not doing a regular insert anywhere, which would involved shifting elements and thus result in slowing down the code, into the array that contains the numbers.
n=gets.chomp.to_i
a=[]
n.times do
t=gets.chomp.to_i
a==[]||(t<=a.first) ? a.unshift(t): t>=a.last ? a.push(t) : a=(a[0,(k=a.index(a.bsearch{|x|x>=t}))].push(t) + a[k,a.length-k])
p (l=a.count)%2==0 ? ((a[l/2] + a[l/2-1])/2.0).round(1):a[(l-1)/2].round(1)
end
Can anybody point out where the problem could be? Thank you.
Here is a less obfuscated version.
n=gets.chomp.to_i
a=[]
n.times do
t=gets.chomp.to_i
if a==[]||(t<=a.first)
a.unshift(t)
else
k=a.index(a.bsearch{|x|x>=t})
if k.nil? == true
k=a.length
end
a=a[0,k].push(t)+ a[k,a.length-k]
end
p (l=a.count)%2==0 ? ((a[l/2] + a[l/2-1])/2.0).round(1):a[(l-1)/2].round(1)
end
I think...
a=(a[0,(k=a.index(a.bsearch{|x|x>=t}))].push(t) + a[k,a.length-k])
...because it's creating a new array every time, is likely an expensive operation as the array gets bigger.
Better might actually be something that mutates the original array.
a.insert((a.index{|x|x>t} || -1), t)
It also handles the edge cases of less than first or greater than last, so you can remove those tests. Also works on first pass (empty array a)

algorithm to find blocks of trends

Let's say I have a a 24 lines of data, such that each line represents an hour in the day. What I want to achieve is to implement an algorithm that can detect trends in the data and can divide it into 2 blocks - a "good" block and a "bad" block. for example, in the attached image you can see that at line 6 a good block begins and ends in line 19. line 0 also has a good score but it is not part of a block so the algorithm should know how to handle this situation.
I think it's about clustering but couldn't find something simple enough that fits our needs.
Looking forward for any advice.
start = -1
Append a below-threshold value to the end of the data array x[]
For i from 1 to n:
If x[i] >= thresholdValue:
if start == -1:
start = i
Else:
If start != -1 and i - start >= thresholdLength:
ReportGoodBlock(start, i-1)
start = -1

Resources