Understanding two sums leetcode problem (Ruby) - ruby

I am working on the Leetcode Two Sums problem: "Given an array of integers, return indices of the two numbers such that they add up to a specific target.". This is what I have:
def two_sum(nums, target)
hash = {}
nums.each_with_index do |num, index|
diff = target - num
if hash[diff]
return [hash[diff],index]
else
hash[num] = index
end
end
end
The code works, however, I'm not too sure why this works.
So I understand that in the each statement it goes through the numbers and it finds the difference. For example,
nums = [4,2,5,1]
target = 6
On the first loop, the difference is 6-2 = 4. But the hash is obviously empty so it will register num as a key, with the current index as the value. Thus the hash is,
hash = {
4: 0
}
On the second loop, the difference is 6-4 = 2. hash[4] is nil so it will add the current num and index to the dictionary.
hash = {
4: 0
2: 1
}
Like so, wouldn't this keep adding the nums to the hash, because at least in this case there aren't matching key value pairs?
Maybe I am overcomplicating things. If someone could eli5, I would greatly appreciate it. Thank you!

The trick is that we add the value to the hash using the number as the key:
hash[num] = index
but extract it using the diff as the key:
if hash[diff]
So if you have as input:
nums = [4,2,5,1]
target = 6
Then on first step, the difference is 6 - 4 = 2, there's no key 2 (diff) in the map, and we add the key 4 (number) to the map.
On the second step, the difference is 6 - 2 = 4, and there's already a key 4 (diff) in the map, so we return the value.

Related

Ruby each_with_index iteration. Unsure about why else statement is needed

Why do we need
hash[number] = index
in the following code?
nums = [11, 8, 1, 7]
target = 9
def two_sum(nums, target)
hash = {}
nums.each_with_index do |number, index|
if complement = hash[target - number]
return [complement, index]
end
hash[number] = index
end
end
iteration does:
nums[0] is 11:
target - number = 2
hash[2] doesn't exist --> we should be able to just forget about this number as number 11 at index 0 can not be part of the solution
nums[1] is 8:
target - number = 1
hash[1] DOES exist and we use it's index (hash[1] = 2) as well as the current index (hash[8] = 1). --> this is our solution and will be returned at
return [complement, index]
the answer I keep getting on why
hash[number] = index
is needed is something on the lines of: "The line hash[number] = index assigns the index of the current number to the hash keyed by the current number. This is important because it allows the function to match the current number with a later number that, together, will add up to the target."
but since we get our result in
return [complement, index]
it seems unnecessary to me to add this line?
Answering my own question after reading through the answer comments (thank you for these!!)
Ask was: output the index of nums of the 2 numbers that add up to the target number.
hash is initially blank and needs to be populated via the else statement in .each
at num[0]: target - num[0] = 9-11 = -2. hash[-2] --> false, hence we move to else statement and populate hash with: hash[11] = 0
at num[1]: target - num[1] = 9-8 = 1. hash[1] --> false, hence we move to else statement and populate hash with: hash[8] = 1
at num[2]: target - num[2] = 9-1 = 8. hash[8] --> true, hence we return index of hash[8] and current index of num.each iteration = [1, 2]
at num[3]: target - num[3] = 9-7 = 2. hash[2] --> false, hence we move to else statement and populate hash with: hash[2] = 3

Find the odd int - Ruby Nested Loop Error

I was doing this question on codewars: "Given an array, find the int that appears an odd number of times. There will always be only one integer that appears an odd number of times."
Code:
def find_it(seq)
int = []
for a in seq do
count = 0
for b in seq do
if a == b
count += 1
end
end
if count % 2.0 != 0
int << b
end
end
puts int.uniq[0].to_i
end
It was tested against a couple inputs, but the answers were wrong for these two arrays:
find_it([1,1,2,-2,5,2,4,4,-1,-2,5]) - returns 5 instead of -1
find_it([1,1,1,1,1,1,10,1,1,1,1]) - returns 1 instead of 10
What went wrong with my code?
if count % 2.0 != 0
int << b
end
The problem you have here is that your pushing b instead of a into the integer array, so what's happening is that instead of the value that you counted being pushed in, your pushing in the last value of b which is the last value element in the array regardless as long as the condition that the counter is an odd number, although b and counter have nothing to do with each other. so to fix it you replace b with a so that it pushes in the value you are testing comparing with the other elements in the second loop
fix:
if count % 2.0 != 0
int << a
end
a similar yet simpler code that does a similar job except in a shorter and more understandable way is:
def find_it(seq)
numberWithOddCount = 0
seq.each do |currentElement|
counter = 0
seq.each { |elementToCompare| counter += 1 if currentElement == elementToCompare}
numberWithOddCount = currentElement if counter % 2 != 0
end
numberWithOddCount
end
Just added a few tid-bits that you could also utilize to shorten and simplify code.
Happy Coding!
Note:
You could utilize built in ruby methods in creative ways to make the code do what you want in very few lines (or even one line) such as what #iGian did in the questions comments, but if your still new to ruby then its best to utilize those methods one by one when learning them otherwise you'll be confused. But if your willing to take the time now to learn them, I suggest you take his code and separate each method execution into its own line and output what each method had done to know what's doing what. and practice using each separately.
#aimen_alt is right about your mistake
but let's decompose your problem.
First, you need to calculate the appearances of each number.
Second, you need to find the one with the odd count of the appearances.
Accordingly to the problem, there is only one such number, so you can return it right away.
You can go your way and do it in O(N^2) complexity by scanning your sequence for each item in the sequence (so N items in the sequence multiply by the size of the sequence N = N*N). You can do it linearly* by constructing a Hash and than you'll be able to get the key with odd value:
def find_it(seq)
numbers = {}
seq.each do |item|
numbers[item] = numbers[item].to_i + 1
end
numbers.select{ |k,v| v.odd? }.first.first
end
to be more idiomatic you can use group_by to group the numbers themselves:
seq = [1, 2, 6, 1, 2]
seq.group_by{ |item| item }
#=> {1=>[1, 1], 2=>[2, 2], 6=>[6]}
You can see that each value is an Array, and you just need to get one with the odd amount of items:
seq = [1, 2, 6, 1, 2]
seq.group_by{ |item| item }.select{ |k, v| v.size.odd? }
#=> {6=>[6]}
And the last thing you would like to do is to get the value of the key:
seq.group_by{ |item| item }.select{ |k, v| v.size.odd? }.keys.first
So, the final solution would be
def find_it(seq)
seq.group_by{ |item| item }
.select{ |k, v| v.size.odd? }
.keys
.first
end
as #pascalbetz mentioned:
def find_it(seq)
seq.group_by{ |item| item }
.find{ |k, v| v.size.odd? }
.first
end
def find_it(seq)
seq.group_by{|x| x}.select{|k, v| (v.count % 2.0 !=0)}.first[0]
end
The above code will take a sequence in an array. Here we are grouping by elements:
For example:
[1,1,2,-2,5,2,4,4,-1,-2,5].group_by{|x| x}
# => {1=>[1, 1], 2=>[2, 2], -2=>[-2, -2], 5=>[5, 5], 4=>[4, 4], -1=>[-1]}
after getting the above results, we are finding the whose elements count not odd with the select condition.
ex:
[1,1,2,-2,5,2,4,4,-1,-2,5].group_by{|x| x}.select{|k, v| (v.count % 2.0 !=0)}
we will get the results as {-1=>[-1]}
we are taking the key as result element.
What about this one
def find_it(seq)
seq.reduce(:^)
end
^ -> this symbol is bitwise XOR.
reduce function is taking each value and doing whatever work assigned inside. In this case, it's taking each element and doing an XOR operation. the first element is doing XOR with zero and the next element doing XOR with the previous result and so on.
In this way, we found the odd element.
How XOR operation work
0 ^ 2 = 2
4 ^ 4 = 0
If you want to know more about XOR. kindly refer to this.
Thank you for all the detailed answers, I'm going over everyone's answers now. I'm new to Ruby, and I'm still in the process of learning the methods/rules of using them/Big O notation, so I much appreciated everyone's input. Codewar listed some top ranked solutions. This seems to be the fastest so far:
def find_it(seq)
seq.detect { |n| seq.count(n).odd? }
end

Calculate missing number

Here's the exercise:
You have been given a list of sequential numbers from 1 to 10,000, but
they are all out of order; furthermore, a single number is missing
from the list. The object of the task is to find out which number is
missing.
The strategy to this problem is to sum the elements in the array, then sum the range 1 to 10,000, and subtract the difference. This is equal to the missing number. The formula for calculating the sum of the range from 1..n being n(n+1)/2.
This is my current approach:
def missing_number(array)
sum = 0
array.each do |element|
sum += element
end
((10000*10001)/2) - sum
end
Where I am getting tripped up is the output when I input an array such as this:
puts missing_number(*1..10000) #=> 0
Why does this happen?
Thanks!
No need to sort the array. An array of length N is supposed to have all but one of the numbers 1..(N+1) so the array length + 1 is the basis for figuring out what the grand_sum would be if all values were there.
def missing_number(array)
grand_sum = (array.length + 1) * (array.length + 2) / 2
grand_sum - array.inject(:+)
end
ADDENDUM
This method takes an array as an argument, not a range. You can't use a range directly because there wouldn't be a missing value. Before calling the method you need some mechanism for generating an array which meets the problem description. Here's one possible solution:
PROBLEM_SIZE = 10_000
# Create an array corresponding to the range
test_array = (1..PROBLEM_SIZE).to_a
# Target a random value for deletion -- rand(N) generates values in
# the range 0..N-1, inclusive, so add 1 to shift the range to 1..N
target_value = rand(PROBLEM_SIZE) + 1
# Delete the value and print so we can check the algorithm
printf "Deleting %d from the array\n", test_array.delete(target_value)
# Randomize the order of remaining values, as per original problem description
test_array.shuffle!
# See what the missing_number() method identifies as the missing number
printf "Algorithm identified %d as the deleted value\n", \
missing_number(test_array)
An alternative approach to solving the problem if it's not performance critical, because of its readability:
def missing_number(array)
(1..10_000).to_a - array
end
Instead of *1..10000, the argument should be (1..10000).to_a.
You shouldn't be using *1..10000, this will just expand to 10,000 arguments. (1..10000).to_a will return zero because there are no elements missing between 1..10000 you need to remove one. Below is some code with a detailed explanation.
def missing_number array
# put elements in order
array.sort!
# get value of last array element
last = array[-1]
# compute the expected total of the numbers
# 1 - last
# (n + 1)(n)/2
expected = (last + 1) * (last / 2)
# actual sum
actual = array.inject{|sum,x| sum + x}
# find missing number by subtracting
(expected - actual)
end
test = (1..10000).to_a
test.delete 45
puts "Missing number is: #{missing_number(test)}"

How does negative index work with `Array#[]=`?

I tried to see how Array#[]= works, and played around:
enum[int] = obj → obj
enum[start, length] = obj → obj
enum[range] = obj → obj
Question 1
I have one array b holding nil at its 0 index.
b = []
b[0] # => nil
I tried to replace nil with integer 10 in the code below.
b[-1] = 10 # => IndexError: index -1 too small for array; minimum: 0
Why doesn't the code above work, but the ones below do? In case of an array with size 1, why are the indices 0 and -1 treated differently?
b[0] = 5 # => 5
b[-1] = 10 # => 10
Question 2
I created an array of size 2, and did the following:
a = [1,2]
a[-3] = 3 # => IndexError: index -3 too small for array; minimum: -2
a[-3] = [3] # => IndexError: index -3 too small for array; minimum: -2
a[-3..-4] = [3] # => RangeError: -3..-4 out of range
I believe that negative index never increases the size of an array, but I don't know why. Why did the code below succeed?
a[-2..-3] = [3,4] #=> [3, 4]
I would suggest you to take a look at the first para in Array documentation. It surprisingly says: “A negative index is assumed to be relative to the end of the array—that is, an index of -1 indicates the last element of the array, -2 is the next to last element in the array, and so on.”
That means, that you may set a[-N]th element if and only |N| <= a.size. That’s why a = [1,2] ; a[-3] = 3 fails (3 > 2).
On the other hand, there is [likely] not documented feature for ruby arrays: a[INBOUNDS_IDX..NONSENSE_IDX]=SMTH will insert SMTH before INBOUNDS_IDX index:
a=[1,2]
a[2..0]='a'
a[2..1]='b'
a[2..-100]='c'
# ⇒ [1, 2, "c", "b", "a"]
a[2..-1]='q'
# ⇒ [1, 2, "q"]
Nonsense here means “less than INBOUNDS_IDX, and not treatable as index in an negative notation” (that’s why a[2..-1] in the example above is treated as a[2..(a.size - 1)].)
Q1:
An empty array has 0 elements, so when you try to set its element 0, with negative index -1, it will give an error.
Because negative index cycles through the array from the end.
So
a = []; a[-1] = 3 makes it impossible to
a) get the element at last position, since its null
b) set its value. since it was never captured.
a[0] = 5 will work because you are telling the compiler to
a) grab the first element,
b) create one if not present, and then assign that to the value you requested.
See official api doc specifically mentioning positive index can grow size, negative index past the beginning of the array raises an error.
Q2:
The above explanation almost answers the second question as well.
Given a = [1,2]
a[-3] = 3 causes the first point of break. You are trying to access the 3rd element from the end, which does not exist. By design it breaks down.
While a[-2..-3] is within the capture range of the defined array.
You ask the interpreter to capture the second element from the last (1st from the front in this case), and try to invoke a range which is asking it to increase the array's size, and populate it with whatever you requested.
Happily, all is still well and as desired. Good to know.
Observation #1
The -1 index is related to the last element, if the array has no size, [], you can't use it until you initialize it with one or more elements.
Observation #2:
Yes, you are right, the negative index never increases size of the array, it only references a concrete existing position in the array.
Don't think the array is circular—the 0 index clued to the N-1 index—so you can't use any negative index thinking that it's valid.

Ruby : Finding lowest free ID in an ID array

I have an array with different IDs going from 1 to 4000. I need to add some elements in a database with an ID that would go in that array. Since the biggest ID possible is 4000 (which is not that much in my case), I'd like to be able to find the lowest unused ID possible I could use for my new element.
I would know how to do that in C++, but since I'm pretty new in Ruby, I'm asking for help. in C++, I would write a loop in which I would check if array[i] == array[i+1] - 1. If not the case, then the new id would be array[i] + 1.
I have just no idea how to write that in Ruby.
Using a range, you can find the first element that is not part of your array:
array = [1,2,3,5,6]
(1..4000).find { |i| !array.include?(i) }
# => 4
array = [1, 2, 3, 5, 6]
(1..4000).to_a.-(array).min
def first_unused_id(ids)
index = ids.each_index.find{|i| ids[i] + 1 != ids[i+1] }
ids[index] + 1
end
Some explanation:
each_index will transform the array into an Enumerator giving the arrays indices.
find will return the first element that returns true from the block passed to it.
how about this one:
(1..4000).find { |i| array[i-1] != i }
similar to Dylan's answer but in this case, it simply checks whether the [n-1]th member of the array is n. If not, that index is "open" and is returned. This solution only requires one check per index, not 4000...
so for
array = [1,2,3,5,6]
this would find that array[4-1] != 4 (because array[3] = 5) and return 4 as the first available id.
(this requires a sorted array of indices but that has been assumed so far)
array = [1, 2, 3, 5, 6]
def lowest_unused(ids)
ids.find { |e| ids.index(e) + 1 != e } - 1
end
p lowest_unused(array) # 4

Resources