0

I'm having a few issues with my code and what I am trying to do. I've googled this most of the day. I apologize if this is simple and the solution is simple, well I am looking for a solution I prefer a full explanation.

My code thus far:

 a1  = [ 4,  6,  7,  8,  10, 13, 14, 15, 17, 21, 24, 45, 48, 61]
 a2  = [ 5,  10, 14, 18, 24, 25, 33, 34, 40, 45, 47, 50, 52, 54]
 a3  = [ 1,  5,  12, 17, 23, 24, 25, 29, 33, 39, 40, 44, 46, 48]
 a4  = [ 5,  16, 18, 20, 31, 39, 41, 42, 43, 55, 57, 60, 62, 63]
 a5  = [ 2,  7,  18, 26, 33, 36, 38, 43, 44, 45, 50, 51, 52, 55]
 a6  = [ 2,  12, 14, 15, 18, 24, 32, 35, 41, 43, 46, 47, 48, 51]
 a7  = [ 4,  12, 13, 15, 17, 18, 19, 23, 26, 32, 33, 35, 40, 47]

 all_arrays = [a1, a2, a3, a4, a5, a6, a7]
 total = Array.new
 fun = Array.new
 b = Array.new
 all_arrays.map do |arr3|
  fun = [arr3.include?(41 & 43)] #.count(true)
   total = [fun.count(true)]

Here I'm trying to see if 41 and 43 occur together in an array, which they do in a4 and a6. Now I want to be able to count how many times they occur together. If I was to puts total the out put would be 0001010. If I output puts fun I would get false, false, false, true, false, true. I want to be able to count the trues or the 1's, So that I would have a total of how many times 41 and 43 occur. I don't understand how to turn an out put into an array? I'm sure that total = [fun.count(true)] is not correct. But it works? sort of. Should I have created a class?

    counts = Hash.new(0)
    total.each do |color|
      counts[color] += 1
      #puts counts
      #puts total.count
      #total.uniq.each do |elem|
      #puts "#{elem}\t#{total.count(elem)}"
      #end
    end

Here is code that appears on google and other sites, including stackoverflow, during my research. I have looked at the doc's and the api; I do not yet understand the logic and syntax of the language, so reading a bunch and taking some online classes hasn't worked. I'm hoping to learn by doing, But currently this is new to me and I'm helping my son with a after school project. I would like to learn this so I am teach him. Thank you for your help.

1
  • a = Array.new is the same as a = []. The latter is generally used. Similarly, h = Hash.new is the same as h = {} and again, the latter is normally used. Commented Apr 15, 2017 at 7:20

4 Answers 4

3

Firstly, since you want to create an array (fun) with one element (true or false) for each element of all_arrays, you want to invoke Enumerable#map on all_arrays.

fun = all_arrays.map { |row| row.include?(41) && row.include?(43) }
  #=> [false, false, false, true, false, true, false] 

Since we were only checking to see if each element of all_arrays contain both of two values (41 and 43), this would be reasonably efficient. Let's generalize this.

targets = [41, 43]

then

fun = all_arrays.map { |row| targets.all? { |t| row.include?(t) } }
  #=> [false, false, false, true, false, true, false] 

Let's count the number of operations required for each element row of all_arrays. For each element t of targets, row.include?(t) must be executed. For simplicity, suppose row contains no duplicates and row.include?(t) returns true one-half the time. When it returns true we may assume that the average number of elements of row that are examined is rows.size/2. As all elements of row must be examined when false is returned, the average number of elements of row that are examined is 0.75 * row.size. Therefore the average number of operations required per element of all_arrays is

target.size * 0.75 * row_size

This is obscenely inefficient. What we want to do is make a single pass through each element of all_arrays, looking to see if it is an elements of target, bringing the expected number of operations to something less than row_size. Each operation may take slightly longer, but it will still be vastly more efficient than the sledge hammer approach above. Here are two more efficient approaches.

#1 Convert targets to a set

require 'set'

def check_row(row, set_targets) 
  row.uniq.each { |x| return true if set_targets.delete(x).empty? }
  false
end

set_targets = targets.to_set
fun = all_arrays.map { |row| check_row(row, set_targets.dup) }
  #=> [false, false, false, true, false, true, false] 

If elements of all_arrays contain at most a small number of duplicate values, the time required to compute row.uniq may not be justified, in which case .uniq should be omitted.

Under the covers, sets are implemented with hashes. As a result, the time required to determine if a set contains an element, and then delete that element (see Set#delete) if it is found, is equivalent to determining whether the underlying hash has a given key and then deleting it if it does. The time required for the latter does not vary greatly with the size of the hash. As a result, set lookups are much more efficient than array lookups.

Note that require 'set' installs an instance method Enumerable#to_set:

Array.instance_methods.include?(:to_set)
  #=> false 

require 'set'

Array.instance_methods.include?(:to_set)
  #=> true 
Array.instance_method(:to_set).owner
  #=> Enumerable 

#2 Use set operations on arrays

fun = all_arrays.map { |row| targets == targets & row }
  #=> [false, false, false, true, false, true, false] 

See Array#&. Note from the doc that the order of the elements of targets & row is consistent with the order of the elements of target.

Alternatively,

fun = all_arrays.map { |row| (targets - row).empty? }
  #=> [false, false, false, true, false, true, false] 

This uses Array#-.

Both of these set-like operations are quite fast, in part because they are coded in C. (Moreover, the array difference calculation may terminate when the conclusion is foregone; that is, when (and if) targets - row becomes empty in the course of the calculations. Anybody know if Array#- does such short-circuiting? )

Sign up to request clarification or add additional context in comments.

2 Comments

Cary, I don't really get your 'set' bit. I like the faster approach, but how do I add my numbers into the code? Say I wanted to check for larger groups than just 41,43. Lets say 4 or 5 number groups on a rather large set. thank you for the help.
Foodie, I edited my answer to explain why it's more efficient to use sets than arrays here. As for checking groups that contain more than two elements (41 and 43), that the purpose of my array targets, which contains an arbitrary number of elements. Does that answer your question?
1

If you’re looking for a count of arrays that contain both 41 and 43, use this:

total = 0
for array in all_arrays do
  if array.include?(41) && array.include?(43)
    total += 1
  end
end

The code starts by setting the variable total, which will hold a count of arrays that contain both 41 and 43, to 0. The plan is to go through all the arrays one by one, checking each array for the presence of both 41 and 43, and if so, add 1 to total.

The line

for array in all_arrays do

defines a loop that will be executed once for each array in all_arrays, with the current array referenced by array. The first time through the loop, array will refer to a1. The second time through the loop, array will refer to a2. The third time through the loop, array will refer to a3, and so on, all the way up to a7.

Inside the loop, we need to see if 41 and 43 are both present in the current array, array. We do this with this line of code...

if array.include?(41) && array.include?(43)

...and if this is the case, we add 1 to the total.

1 Comment

Thanks Joey, that makes sense I can see it now and understand what I'm looking for. Side questions why && and not just &, Also why separate the two? Is that just to make it easier to read? if array.include?(41) && array.include?(43)
0
all_arrays = [a1, a2, a3, a4, a5, a6, a7]
fun = []
all_arrays.each do |arr3|
  if arr3.include?(41) && arr3.include?(43)
    fun << true
  else
    fun << false
  end
end

puts fun

If you want 1s and 0s, just change true to 1 and and false to 0. Is this the whole output you were looking for or did you need something additional?

2 Comments

You could avoid if completely by just doing fun << (arr3.include?(41) && arr3.include?(43)).
yea that's true
0

Food Warrior asks:

why && and not just &?

&& is what we call a logical and. The expression a && b is true if and only if both a and b are true. The && version of “and” is closer to the way we use the word “and” in regular life.

& is a bitwise and, and to save a lot of explanation that’s not useful at the moment, let me just say that it’s only meaningful when performing math operations on the binary representations of numbers. It’s not the “and” you think of when you want to know if both a and b are true.

Food Warrior also asks:

Why use if array.include?(41) && array.include?(43) instead of if array.include?(41 && 43)?

Computers are “dumb”, which means that the language when use when programming has to be more precise than natural (human) language. When we want to know if a given array contains both 41 and 43, we’re actually asking three questions:

  1. Does the array contain the number 41?
  2. Does the array contain the number 43?
  3. Are the answers to questions 1 and 2 both “yes”?

These three questions are contained in this line of code:

if array.include?(41) && array.include?(43)

Of course, if we were talking to a human rather than a computer, we might say “If the current array contains 41 and 43”, which we might be tempted to write as:

if array.include?(41 && 43)

This makes sense to humans, but to Ruby, it interprets this statement as “first, calculate the value of 41 && 43, then see if the array contains that value”. For reasons that are a bit lengthy to get into here, the value of 41 && 43 is 43. This means that if array.include?(41 && 43) is equivalent to if array.include?(43), which isn’t what you want.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.