2

I am currently learning ruby and am very new to it. I usually write code in javascript but because I am entering a bootcamp next year I took it upon myself to learn as much ruby before my cohort starts. So the practice problem that I'm trying to solve is getting the indices of elements that equivalent to a certain number, in which case lets give an example 0.

I can do this with no problem for JS but I am learning how to write in Ruby.

//JS 
for(let i = 0; i < arr.length; i++){
    for(let j = i + 1; j < arr.length; j++){
       if(arr[j] + arr[i] === 0){
           someVar.push([i,j]);
       }
    }
}

The block code above displays where I am getting each unique pair and which ever one of these pairs return a sum 0 their indices get pushed inside the result.

I am still very new with how ruby writes loops and there is a plethora of ways to do it.

  def two_sum(nums)
  result = []
  n = nums.length

  n.times do |i|
    n.times do |j|

      if nums[i] + nums[j] == 0
        result.push([i,j])
      end
    end
  end
  result
end

I've tried doing it this way but it doesn't give me the right output. I also understand that n.times do |j| is not the same as for(let j = i + 1; j < arr.length; j++). But I don't know how else to write this so I did something like:

n.times do |i|
  x = i + 1
  x.times do |j|

My intuition is telling me that this is not the way to write an inner loop.

Mark Thomas
  • 37,131
  • 11
  • 74
  • 101
  • What would be an input and output as example of this? – Sebastián Palma Nov 30 '17 at 23:18
  • So lets say I need the indices of the elements whose sum is 3 so our array is `arr = [3,2,1,0,5]` our return should be `[[0,3],[1,2]]` because 3+0 = 3 and 2+1 = 3. It's really simple its just the darn syntax. –  Nov 30 '17 at 23:22
  • @ken wouldn't [1,2,0] also be a answer? or is the combinations that of only pairs? – Conor Nov 30 '17 at 23:29
  • Do you wish to return an array of pairs of numbers from a given array that sum to a given value (e.g., zero)? – Cary Swoveland Dec 01 '17 at 02:27
  • The problem only wanted a pair not more than two indices. –  Dec 01 '17 at 05:18
  • Is it possible that there will be identical values in the array? e.g. `[1,1,2]` and if so, does it matter which index is used? – Mark Thomas Dec 01 '17 at 15:14
  • Does the order of the indices within each pair matter? In other words, in your first comment would `[[3,0],[2,1]]` be acceptable? I ask because @StefanPochmann is holding me to task :) – Mark Thomas Dec 01 '17 at 15:16
  • I don't think it does matter. If you start looking for the pair from the end of the string you would get `[[3,0],[2,1]]`. As long as the logic is sound in which case the elements of these indices equate to a certain value it should be good. –  Dec 01 '17 at 23:53
  • In that case my second edit produces a correct result. Feel free to select whichever answer best suits your needs. – Mark Thomas Dec 04 '17 at 18:34

2 Answers2

4

Simply select the appropriate sums from all the pairs (combinations of 2).

def two_sum(nums)
  nums.combination(2).select{|pair| pair.sum == 3}
end

Edit: more details on what's going on:

As you know, we pass in an array called nums. If you go to the documentation for Array, you'll see a method called combination. By the way, I highly recommend that you, as a newcomer to Ruby, become familiar with all the methods on Array (as well as Enumerable, which I'll explain in a bit). The combination method "yields all combination of elements of length n" and we will use n=2:

p [3,2,1,0,5].combination(2).to_a
#=> [[3, 2], [3, 1], [3, 0], [3, 5], [2, 1], [2, 0], [2, 5], [1, 0], [1, 5], [0, 5]]

Now if you omitted the .to_a above, you'd see that the output of combination is actually an Enumerator. And if you look carefully at the documentation, you'll see under "Included Modules" it lists Enumerable. Enumerable is designed to be a "mixin", which can add methods to other classes (even your own). [Side note: In fact, if you look at the docs for Array, it includes Enumerable as well. The combination of these methods available to your Ruby arrays make them very powerful, and why I recommend learning them]. The select method takes a block and it will return all the elements for which the given block is true. So, we are checking every element to see if the sum is 3, for example:

[2,1].sum == 3
#=> true (and therefore [2,1] will be included in the output)

The output of select, and therefore the two_sum method, is an array of all the matches (the elements where the block was true).

Edit 2

It was pointed out by Stefan in the comments that the results above are returning values, not indices. One way to fix that is to add an index lookup to the results:

def two_sum(nums)
  nums.combination(2)
      .select{|pair| pair.sum == 3}
      .map{|pair| [nums.index(pair[0]), nums.index(pair[1])] }
end

Not quite as succinct, but at least it outputs indices now :)

Mark Thomas
  • 37,131
  • 11
  • 74
  • 101
  • That is way beyond what I know at the moment. I haven't even touched on classes yet alone these more advanced methods. Can you put into your own words what the 2nd line of code is doing? –  Dec 01 '17 at 01:26
  • I've explained it a bit, see if that helps. – Mark Thomas Dec 01 '17 at 12:48
  • This doesn't do the job. You're returning array elements, not their indices. Also, your explanation isn't right. Yes, `Array` includes `Enumerable`, but `Array` has its own `select`, doesn't use the one from `Enumerable`. But that doesn't even matter, since you're not using `select` on the array but on the `Enumerator` that you get from `combination`. And that `Enumerator` gets its `select` from including `Enumerable`. – Stefan Pochmann Dec 01 '17 at 13:22
  • @Ken mentions indices, but from his comments it is clear he wants the values returned (maybe the values are indices for something else). – Mark Thomas Dec 01 '17 at 13:28
  • Yes, the mention about `Enumerable` being mixed in to `Array` was really a side note, not intended to be part of the explanation of what's happening. Perhaps I should reword it to be more clear. – Mark Thomas Dec 01 '17 at 13:28
  • @MarkThomas What comments of Ken are you talking about? None I see say that. I even see one that reinforces that he wants indices. – Stefan Pochmann Dec 01 '17 at 13:32
  • 1
    His first comment mentions indices but his expected output are the values. – Mark Thomas Dec 01 '17 at 13:37
  • Ha! You are right. That exact output coincidentally works for the values too. I hang my head in shame :). It was very late for me when I answered :) Let's see if I can salvage it. – Mark Thomas Dec 01 '17 at 13:48
  • @MarkThomas Yeah, it's a very bad example, easy to confuse indices with values. I wrote indices-solutions already but sadly they're much less nice. – Stefan Pochmann Dec 01 '17 at 13:54
  • About the update: The answer isn't really the same, not even for that example. `[[0, 3], [1, 2]]` and `[[3, 0], [2, 1]]` differ. And your new code fails for example for input `[1, 1, 2]`, returning `[[0, 2], [0, 2]]` instead of `[[0, 2], [1, 2]]`. – Stefan Pochmann Dec 01 '17 at 14:44
  • You're a tough customer. It is returning _an_ index of each value, who's to say it's wrong? OP didn't specify what happens when two values are the same. – Mark Thomas Dec 01 '17 at 15:04
1

If you want the code to resemble your JS snippet, then here's a version using the appropriate ruby loops.

0.upto(arr.length - 1) do |i|
  (i + 1).upto(arr.length - 1) do |j|
    if arr[i] + arr[j] == 0
      result.push([i, j])
    end
  end
end

But more idiomatic ruby would be that in MarkThomas' answer (or something similar).

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367