1

I have the following hash:

hash = {"1"=>[ 5, 13, "B",  4, 10],
        "2"=>[27, 19, "B", 18, 20],
        "3"=>[45, 41, "B", 44, 31],
        "4"=>[48, 51, "B", 58, 52],
        "5"=>[70, 69, "B", 74, 73]}

Here is my code:

 if hash.values.all? { |array| array[0] == "B" } ||
    hash.values.all? { |array| array[1] == "B" } ||
    hash.values.all? { |array| array[2] == "B" } ||
    hash.values.all? { |array| array[3] == "B" } ||
    hash.values.all? { |array| array[4] == "B" }
      puts "Hello World"

What my code does is iterates through an array such that if the same element appears in the same index position of each array, it will output the string "Hello World" (Since "B" is in the [2] position of each array, it will puts the string. Is there a way to condense my current code without having a bunch of or's connecting each index of the array?

2
  • 3
    I hope you don't mind that I edited your question. I just wanted to show you how much more readable it is when horizontal scrolling is not required. I could have tried to explain and let you edit, but I thought this would be easier for both of us. Commented Oct 23, 2014 at 5:43
  • This is very similar to your earlier question. Phrogz' solution to that one would have to be modified only slightly here: hash.values.transpose.any? { |arr| arr.all? { |e| e == ?B } } #=> true. Commented Oct 23, 2014 at 5:55

5 Answers 5

2

Assuming all arrays are always of the same length, the following gives you the column indexes where all values are equal:

hash.values.transpose.each_with_index.map do |column, index|
  index if column.all? {|x| x == column[0] }
end.compact

The result is [2] for your hash. So you know that for all arrays the index 2 has the same values. You can print "Hello World" if the resulting array has at least one element.

How does it work?

hash.values.transpose gives you all the arrays, but with transposed (all rows are now columns) values:

hash.values.transpose
=> [[5, 27, 45, 48, 70],
    [13, 19, 41, 51, 69],
    ["B", "B", "B", "B", "B"],
    [4, 18, 44, 58, 74],
    [10, 20, 31, 52, 73]]

.each_with_index.map goes over every row of the transposed array while providing an inner array and its index. We look at every inner array, yielding the column index only if all elements are equal using all?.

hash.values.transpose.each_with_index.map {|column, index| index if column.all? {|x| x == column[0] }
=> [nil, nil, 2, nil, nil]

Finally, we compact the result to get rid of the nil values.


Edit: First, I used reduce to find the column with identical elements. @Nimir pointed out, that I re-implemented all?. So I edited my anwer to use all?.

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

2 Comments

I would like to point out two weaknesses with your solution. Firstly, you create an intermediate array with map, which is unnecessary. Secondly, and more importantly, you process every column, even though you could quit as soon as you find one with identical entries.
You're right, especially with the last comment. I must admit I had the question in mind to find all column-indexes which have equal elements. That's a broader question than the one the OP asked for.
2

From @tessi brilliant answer i though of this way:

hash.values.transpose.each_with_index do |column, index|
  puts "Index:#{index} Repeated value:#{column.first}" if column.all? {|x| x == column[0]}
end
#> Index:2 Repeated value:B

How?

Well, the transpose already solves the problem:

hash.values.transpose
=> [[5, 27, 45, 48, 70],
    [13, 19, 41, 51, 69],
    ["B", "B", "B", "B", "B"],
    [4, 18, 44, 58, 74],
    [10, 20, 31, 52, 73]
]

We can do:

column.all? {|x| x == column[0]}

To find column with identical items

1 Comment

Thanks for pointing out that I re-implemented all? with my reduce-block. I've edited my answer accordingly (and gave you the attribution).
1

Assuming that all the values of the hash will be arrays of the same size, how about something like:

hash
=> {"1"=>[5, 13, "B", 4, 10], "2"=>[27, 19, "B", 18, 20], "3"=>[45, 41, "B", 44, 31], "4"=>[48, 51, "B", 58, 52], "5"=>[70, 69, "B", 74, 73]}

arr_of_arrs = hash.values
=> [[5, 13, "B", 4, 10], [27, 19, "B", 18, 20], [45, 41, "B", 44, 31], [48, 51, "B", 58, 52], [70, 69, "B", 74, 73]]

first_array = arr_of_arrs.shift
=> [5, 13, "B", 4, 10]

first_array.each_with_index do |element, index|
  arr_of_arrs.map {|arr| arr[index] == element }.all?
end.any?
=> true

This is not really different from what you have now, as far as performance - in fact, it may be a bit slower. However, it allows for a dynamic number of incoming key/value pairs.

Comments

0

I ended up using the following:

fivebs = ["B","B","B","B","B"]

if hash.values.transpose.any? {|array| array == fivebs}
  puts "Hello World"

2 Comments

This only works for hashes with five elements, but that's an easy fix: any_number_of_bs = ["B"]*hash.size => ["B", "B", "B", "B", "B"].
I should have referenced the docs for the method Array#* in my comment above. That method has two forms, depending on whether * is followed by an integer or a string. Here it's an integer.
0

If efficiency, rather than readability, is most important, I expect this decidedly un-Ruby-like and uninteresting solution probably would do well:

arr = hash.values
arr.first.size.times.any? { |i| arr.all? { |e| e[i] == ?B } }
  #=> true

Only one intermediate array (arr) is constructed (e.g, no transposed array), and it quits if and when a match is found.

More Ruby-like is the solution I mentioned in a comment on your question:

hash.values.transpose.any? { |arr| arr.all? { |e| e == ?B } } 

As you asked for an explanation of @Phrogz's solution to the earlier question, which is similar to this one, let me explain the above line of code, by stepping through it:

a = hash.values
  #=> [[ 5, 13, "B",  4, 10],
  #    [27, 19, "B", 18, 20],
  #    [45, 41, "B", 44, 31],
  #    [48, 51, "B", 58, 52],
  #    [70, 69, "B", 74, 73]]

b = a.transpose
  #=> [[  5,  27,  45,  48,  70],
  #    [ 13,  19,  41,  51,  69],
  #    ["B", "B", "B", "B", "B"],
  #    [  4,  18,  44,  58,  74],
  #    [ 10,  20,  31,  52,  73]]

In the last step:

b.any? { |arr| arr.all? { |e| e == ?B } } 
  #=> true

(where ?B is shorthand for the one-character string "B") an enumerator is created:

c = b.to_enum(:any?)
  #=> #<Enumerator: [[  5,  27,  45,  48,  70],
  #                  [ 13,  19,  41,  51,  69],
  #                  ["B", "B", "B", "B", "B"],
  #                  [  4,  18,  44,  58,  74],
  #                  [ 10,  20,  31,  52,  73]]:any?>

When the enumerator (any enumerator) is acting on an array, the elements of the enumerator are passed into the block (and assigned to the block variable, here arr) by Array#each. The first element passed into the block is:

arr = [5, 27, 45, 48, 70]

and the following is executed:

arr.all? { |e| e == ?B }
  #=> [5, 27, 45, 48, 70].all? { |e| e == ?B }
  #=> false

Notice that false is returned to each right after:

5 == ?B
  #=> false

is evaluated. Since false is returned, we move on to the second element of the enumerator:

[13, 19, 41, 51, 69].all? { |e| e == ?B }
  #=> false

so we continue. But

["B", "B", "B", "B", "B"].all? { |e| e == ?B }
  #=> true

so when true is returned to each, the latter returns true and we are finished.

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.