equator.bear
equator.bear

Reputation: 13

How do I compare integer indexes in arrays when there are duplicate values?

First, some necessary background. I'm trying to make a number-based version of the game Mastermind as a way of learning to code in Ruby. My code basically works like this:

  1. The computer generates an array (@computer_sequence) of 4 random numbers from 1-5
  2. The user enters a 4 digit sequence, which winds up in an array called @user_array.
  3. A method, called compare, iterates through @user_array, comparing the value and index of each number to those in @computer_sequence. The program then tells the user how many of their numbers have the correct value and the correct position, or how many numbers have the correct value only.

The problem: If there are multiple instances of a number in an array, they get the same index, right? Like if I have the array [1, 3, 3, 4], the number three has an index of 1, even though there are two 3s. For this program to work, though, each number has to have a unique position (is index even the word I want here?) in the array, even if the number occurs multiple times. Does that make sense?

Also, here's the code for the compare method:

def compare
    value_only = 0
    value_and_place = 0
    puts "The computer's values are: #{@computer_sequence}"
    puts "The user's values are: #{@user_array}"


    @user_array.each do |candidate|
        @computer_sequence.each do |computer_number|
            if candidate == computer_number && @user_array.index(candidate) == @computer_sequence.index(computer_number)
                value_and_place +=1
            elsif candidate == computer_number && @user_array.index(candidate) != @computer_sequence.index(computer_number)
                value_only +=1
            end
        end
    end

Upvotes: 1

Views: 1878

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110685

Suppose

n = 4
computer = Array.new(n) { [1,2,3,4,5].sample }
  #=> [3, 2, 3, 3]
user_digits = [2, 4, 2, 3]

First compute pairs of elements at the same index of computer and user_digits.

pairs = computer.zip(user_digits)
  #=> [[3, 2], [2, 4], [3, 2], [3, 3]]

Compute number of values that match at the same position

pairs.count { |c,u| c==u }
  #=> 1

Compute number of values that match at different positions

First remove the matches at the same positions of computer and user_digits.

comp, users = pairs.reject { |c,u| c==u }.transpose
  #=> [[3, 2, 3], [2, 4, 2]] 

meaning

comp  #=> [3, 2, 3] 
users #=> [2, 4, 2] 

Now step through users removing the first matching element in comp (if there is one).

users.each do |n|
  i = comp.index(n)
  comp.delete_at(i) if i
end

So now:

comp #=> [3,3] 

meaning that the number of elements that match at different positions is:

users.size-comp.size
  #=> 1 

Notice that we could alternatively compute the number of values that match at the same position as

n - users.size

For n equal to 4 this doesn’t offer any significant time saving, but it would if we had a problem with the same structure and n were large.

Alternative calculation

After computing

comp, users = pairs.reject { |c,u| c==u }.transpose

we could write

users.size - comp.difference(users).size
  #=> 1

where Array#difference is as I defined it in my answer here.

Here

comp.difference(users)
  #=> [3,3]

Upvotes: 2

Dave Schweisguth
Dave Schweisguth

Reputation: 37617

No, equal elements in an array don't have the same index. Maybe you're thinking that because Array#index only returns the index of the first element equal to its argument. But there are many ways to see that other equal elements have their own indexes. For example,

a = [1, 3, 3, 4]
a[1] == 3 # true
a[2] == 3 # also true

Aside from that issue, your algorithm doesn't quite match the rules of Mastermind. If there is one three in the computer's sequence and the player guesses two threes, both in different positions than the three in the computer's sequence, the player should be told that only one element of their sequence matches the computer's sequence in value but not position.

Given the above, plus that I think it would be clearer to calculate the two numbers separately, I'd do it like this:

value_and_place = 4.times { |i| @user_array[i] == @computer_sequence[i] }
value_only = (@user_array & @computer_sequence).length - value_and_place

That's less efficient than the approach you're taking, but CPU efficiency isn't important for 4-element arrays.

Upvotes: 1

Joseph
Joseph

Reputation: 760

You can pass in the index value to your loop for each candidate using the each_with_index method. So when the first 3 is passed in, index will be 1 and when the second 3 is passed in, index will be 2.

The problem with using .index(candidate) is it returns the first index.

Try this:

@user_array.each_with_index do |candidate, index|
    @computer_sequence.each do |computer_number|
        if candidate == computer_number && candidate == @computer_sequence[index]
            value_and_place +=1
        elsif candidate == computer_number && candidate != @computer_sequence[index]
            value_only +=1
        end
    end
end

Upvotes: 0

Related Questions