Detect if nested array contains similar elements - ruby

I have a method that gets an array of arrays and detects if any sub array is occurs more than one time, regardless of its order:
def has_similar_content?(array)
array.each.with_index do |prop1, index1|
array.each.with_index do |prop2, index2|
next if index1 == index2
return true if prop1.sort == prop2.sort
end
end
false
end
> has_similar_content?([%w[white xl], %w[red xl]])
=> false
> has_similar_content?([%w[blue xl], %w[xl blue cotton]])
=> false
> has_similar_content?([%w[blue xl], %w[xl blue]])
=> true
> has_similar_content?([%w[xl green], %w[red xl], %w[green xl]])
=> true
My problem is the runtime of this method, it has a quadratic complexity and needs an additional sort of the arrays to detect if the elements are the same.
Is there a more efficient way to do this?

I have assumed the question is as stated in my comment on the question.
Code
def disregarding_order_any_dups?(arr)
arr.map do |a|
a.each_with_object(Hash.new(0)) do |k,h|
h[k] += 1
end
end.uniq.size < arr.size
end
Examples
disregarding_order_any_dups? [%w[white xl], %w[red xl]]
#=> false
disregarding_order_any_dups? [%w[blue xl],
%w[xl blue cotton]]
#=> false
disregarding_order_any_dups? [%w[blue xl], %w[xl blue]]
#=> true
disregarding_order_any_dups? [%w[xl green], %w[red xl],
%w[green xl]]
#=> true
disregarding_order_any_dups? [[1,2,3,2], [3,1,3,2],
[2,3,1,2]]
#=> true
Complexity
If n = arr.size and m = arr.map(&:size).max, the computational complexity is O(n*m). The single statement within map's block could be replaced with a.sort, but that would increase the computational complexity to O(n*m*log(m)).
Explanation
For the last example the steps are as follows.
arr = [[1,2,3,2], [3,1,3,2], [2,3,1,2]]
b = arr.map do |a|
a.each_with_object(Hash.new(0)) do |k,h|
h[k] += 1
end
end
#=> [{1=>1, 2=>2, 3=>1}, {3=>2, 1=>1, 2=>1},
# {2=>2, 3=>1, 1=>1}]
c = b.uniq
#=> [{1=>1, 2=>2, 3=>1}, {3=>2, 1=>1, 2=>1}]
d = c.size
#=> 2
e = arr.size
#=> 3
d < e
#=> true
The expression
h = Hash.new(0)
creates a counting hash. Ruby expands h[k] += 1 to
h[k] = h[k] + 1
The hash instance methods are :[]= on the left, :[] on the right. If h does not have a key k, h[k] on the right is replaced with h's default value, which has been defined to equal zero, resulting in:
h[k] = 0 + 1
If h has a key k, h[k] on the right, the value of k, is not replaced with h's default value. See the version of Hash::new which takes an argument equal to the hash's default value.

this way is simpler:
array.
group_by(&:sort).
transform_values(&:length).
values.any? { |count| count > 1 }

This is still quadratic but it is faster :
def has_similar_content?(array)
# sort subarray only once. O( n * m * log(m) )
sorted_array= array.map(&:sort)
# if you can change the input array, this prevent object allocation :
# array.map!(&:sort!)
# compare each pair only once O( n * n/2 )
nb_elements= sorted_array.size
0.upto(nb_elements - 1).each do |i|
(i + 1).upto(nb_elements - 1).each do |j|
return true if sorted_array[i] == sorted_array[j]
end
end
return false
end

Related

How would you refactor this method to reduce the ABC size to less than 15?

require 'active_support'
require 'active_support/core_ext'
def subtract_two_ranges(range_a, range_b)
return [range_a] unless range_a.overlaps?(range_b)
[
((range_a.begin..(range_b.begin - 1)) if range_a.begin < range_b.begin),
(((range_b.end + 1)..range_a.end) if range_b.end < range_a.end)
].compact
end
ABC size typically indicates that your method is too complicated, hard to read and, probably, breaks SRP.
It should be a method of a Range.
Move expressions in array to its own private methods.
Use refinements to avoid monkey-patching a base class.
Try smth like this.
require 'active_support'
require 'active_support/core_ext'
class Range
def -(other)
return [self] unless overlaps?(other)
[substract_lower_bound(other), substract_upper_bound(other)].compact
end
private
def substract_lower_bound(other)
first..(other.begin - 1) if first < other.first
end
def substract_upper_bound(other)
(other.last + 1)..last if other.last < last
end
end
I have presented two methods below. Both are pure-Ruby and operate on arbitrary finite ranges.1. The first is the more efficient, but more complex.
Efficient but complex
Code
def subtract_range(r1, r2)
return [r1] if r2.end < r1.begin || r2.begin > r1.end
return [] if r2.begin <= r1.begin && r2.end >= r1.end
if r2.begin <= r1.begin
r2.end < r1.begin ? [r1] : [r2.end.succ..r1.end]
else # r1.begin < r2.begin <= r1.end
e = pred(r1.begin, r2.begin)
r2.end >= r1.end ? [(r1.begin)..e] :
[r1.begin..e, (r2.end.succ)..r1.end]
end
end
def pred(start_at, e)
(return e-1) if e.kind_of? Numeric
loop do
break start_at if start_at.succ == e
start_at = start_at.succ
end
end
All elements of a range (regardless of their class) have a method succ (e.g., String#succ), but in general do not have a comparable predecessor method. Numeric classes are the exception: the predecessor of n is n-1. For non-numeric elements one must compute the predecessor value by starting at some known smaller value and apply succ successively until the predecessor value is found.
Examples
subtract_range 15..25, 20..30 #=> [15..19]
subtract_range 15..25, 10..20 #=> [21..25]
subtract_range 15..25, 17..23 #=> [15..16, 24..25]
subtract_range 15..25, 5..10 #=> [15..25]
subtract_range 15..25, 30..35 #=> [15..25]
subtract_range 15..25, 25..30 #=> [15..24]
subtract_range 15..25, 16..30 #=> [15..15]
subtract_range 15..25, 10..30 #=> []
subtract_range 'd'..'j', 'g'..'m' #=> ["d".."f"]
subtract_range 'd'..'j', 'f'..'h' #=> ["d".."e", "i".."j"]
Less efficient but simpler
Code
def subtract_range(r1, r2)
arr = r1.to_a - r2.to_a
return [] if arr.empty?
return [arr.first..arr.first] if arr.size == 1
arr.slice_when { |m,n| m.succ < n }.
map { |a| a.size == 1 ? a.first..a.first : a.first..a[-1] }
end
Examples
This method produces the same return values for the examples given above.
Explanation
Suppose (from the last example)
r1 = 'd'..'j'
r2 = 'f'..'h'
Then
arr = r1.to_a - r2.to_a
#=> ["d", "e", "f", "g", "h", "i", "j"] - ["f", "g", "h"]
#=> ["d", "e", "i", "j"]
arr.empty?
#=> false (so do not return [])
arr.size == 1
#=> false (so do not return [arr.first..arr.first])
enum = arr.slice_when { |m,n| m.succ < n }
#=> #<Enumerator: #<Enumerator::Generator:0x00000001d2c228>:each>
We can convert enum to an array to see the elements that will be passed to map.
enum.to_a
#=> [["d", "e"], ["i", "j"]]
Lastly,
enum.map { |a| a.size == 1 ? a.first..a.first : a.first..a[-1] }
#=> ["d".."e", "i".."j"]
1 2.0..6.0 - 4.0..8.0 #=> 2.0...4.0, is not a problem (note the three dots). However, 2.0..4.0 - 1.0..3.0is a problem, as the resulting range,3.0..4.0, excluding 3.0, cannot be represented as a Range instance.

How to write a method that finds the most common letter in a string?

This is the question prompt:
Write a method that takes in a string. Your method should return the most common letter in the array, and a count of how many times it appears.
I'm not entirely sure where to go with what I have so far.
def most_common_letter(string)
arr1 = string.chars
arr2 = arr1.max_by(&:count)
end
I suggest you use a counting hash:
str = "The quick brown dog jumped over the lazy fox."
str.downcase.gsub(/[^a-z]/,'').
each_char.
with_object(Hash.new(0)) { |c,h| h[c] += 1 }.
max_by(&:last)
#=> ["e",4]
Hash::new with an argument of zero creates an empty hash whose default value is zero.
The steps:
s = str.downcase.gsub(/[^a-z]/,'')
#=> "thequickbrowndogjumpedoverthelazyfox"
enum0 = s.each_char
#=> #<Enumerator: "thequickbrowndogjumpedoverthelazyfox":each_char>
enum1 = enum0.with_object(Hash.new(0))
#=> #<Enumerator: #<Enumerator:
# "thequickbrowndogjumpedoverthelazyfox":each_char>:with_object({})>
You can think of enum1 as a "compound" enumerator. (Study the return value above.)
Let's see the elements of enum1:
enum1.to_a
#=> [["t", {}], ["h", {}], ["e", {}], ["q", {}],..., ["x", {}]]
The first element of enum1 (["t", {}]) is passed to the block by String#each_char and assigned to the block variables:
c,h = enum1.next
#=> ["t", {}]
c #=> "t"
h #=> {}
The block calculation is then performed:
h[c] += 1
#=> h[c] = h[c] + 1
#=> h["t"] = h["t"] + 1
#=> h["t"] = 0 + 1 #=> 1
h #=> {"t"=>1}
Ruby expands h[c] += 1 to h[c] = h[c] + 1, which is h["t"] = h["t"] + 1 As h #=> {}, h has no key "t", so h["t"] on the right side of the equal sign is replaced by the hash's default value, 0. The next time c #=> "t", h["t"] = h["t"] + 1 will reduce to h["t"] = 1 + 1 #=> 2 (i.e., the default value will not be used, as h now has a key "t").
The next value of enum1 is then passed into the block and the block calculation is performed:
c,h = enum1.next
#=> ["h", {"t"=>1}]
h[c] += 1
#=> 1
h #=> {"t"=>1, "h"=>1}
The remaining elements of enum1 are processed similarly.
A simple way to do that, without worrying about checking empty letters:
letter, count = ('a'..'z')
.map {|letter| [letter, string.count(letter)] }
.max_by(&:last)
Here is another way of doing what you want:
str = 'aaaabbbbcd'
h = str.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 }
max = h.values.max
output_hash = Hash[h.select { |k, v| v == max}]
puts "most_frequent_value: #{max}"
puts "most frequent character(s): #{output_hash.keys}"
def most_common_letter(string)
string.downcase.split('').group_by(&:itself).map { |k, v| [k, v.size] }.max_by(&:last)
end
Edit:
Using hash:
def most_common_letter(string)
chars = {}
most_common = nil
most_common_count = 0
string.downcase.gsub(/[^a-z]/, '').each_char do |c|
count = (chars[c] = (chars[c] || 0) + 1)
if count > most_common_count
most_common = c
most_common_count = count
end
end
[most_common, most_common_count]
end
I'd like to mention a solution with Enumerable#tally, introduced by Ruby 2.7.0:
str =<<-END
Tallies the collection, i.e., counts the occurrences of each element. Returns a hash with the elements of the collection as keys and the corresponding counts as values.
END
str.scan(/[a-z]/).tally.max_by(&:last)
#=> ["e", 22]
Where:
str.scan(/[a-z]/).tally
#=> {"a"=>8, "l"=>9, "i"=>6, "e"=>22, "s"=>12, "t"=>13, "h"=>9, "c"=>11, "o"=>11, "n"=>11, "u"=>5, "r"=>5, "f"=>2, "m"=>2, "w"=>1, "k"=>1, "y"=>1, "d"=>2, "p"=>1, "g"=>1, "v"=>1}
char, count = string.split('').
group_by(&:downcase).
map { |k, v| [k, v.size] }.
max_by { |_, v| v }

Compare sums of elements in an array: Ruby

I need to check whether the sum of any 2 elements of an array equals to the given number. This is what I came up with, but it doesn't seem to do the comparison
def sum_comparison(int_array, x)
n = int_array.length
(0..n).each do |i|
(1..n).each do |j|
if ((int_array[i].to_i + int_array[j].to_i) == x)
return true
else
return false
end
end
end
end
Your solution seems overly complicated and strongly influenced by the programming style of low-level procedural languages like C. One apparent problem is that you write
n = int_array.length
(0..n).each do |i|
# use int_array[i].to_i inside the loop
end
Now inside the each loop, you will get the numbers i = 0, 1, 2, ..., n, for example for int_array = [3,4,5] you get i = 0, 1, 2, 3. Notice that there are four elements, because you started counting at zero (this is called an off by one error). This will eventually lead to an array access at n, which is one beyond the end of the array. This will again result in a nil coming back, which is probably why you use to_i to convert that back to an integer, because otherwise you would get a TypeError: nil can't be coerced into Fixnum whend doing the addition. What you probably wanted instead was simply:
int_array.each do |i|
# use i inside the loop
end
For the example array [3,4,5] this would actually result in i = 3, 4, 5. To get the combinations of an array in a more Ruby way, you can for example use Array#combination. Likewise, you can use Array#any? to detect if any of the combinations satisfy the specified condition:
def sum_comparison(array, x)
array.combination(2).any? do |a, b|
a + b == x
end
end
When your function compare first element, it's immediately returns false. You need to return only true when iterating and return false at the end if nothing were found, to avoid this issue:
def sum_comparison(int_array, x)
n = int_array.size
(0...n).each do |i|
(1...n).each do |j|
if (int_array[i].to_i + int_array[j].to_i) == x
return true
end
end
end
false
end
To simplify this you can use permutation or combination and any? methods as #p11y suggests. To get founded elements you could use find or detect.
def sum_comparison(a, x)
a.combination(2).any? { |i, j| i + j == x }
end
a.combination(2).detect { |i, j| i + j == x }
# sum_comparison([1,2,3, 4], 6) => [2, 4]
Using an enumerator:
#!/usr/bin/env ruby
def sum_comparison(int_array, x)
enum = int_array.to_enum
loop do
n = enum.next
enum.peek_values.each do |m|
return true if (n + m) == x
end
end
false
end
puts sum_comparison([1, 2, 3, 4], 5)
Output:
true
Problem
Your method is equivalent to:
def sum_comparison(int_array, x)
return int_array[0].to_i + int_array[1].to_i == x
end
Therefore,
int_array = [1,2,4,16,32,7,5,7,8,22,28]
sum_comparison(int_array, 3) #=> true, just lucky!
sum_comparison(int_array, 6) #=> false, wrong!
Alternative
Here is a relatively efficient implemention, certainly far more efficient than using Enumerable#combination.
Code
def sum_comparison(int_array, x)
sorted = int_array.sort
smallest = sorted.first
sorted_stub = sorted.take_while { |e| e+smallest <= x }
p "sorted_stub = #{sorted_stub}"
return false if sorted_stub.size < 2
loop do
return false if sorted_stub.size < 2
v = sorted_stub.shift
found = sorted_stub.find { |e| v+e >= x }
return true if found && v+found == x
end
false
end
Examples
sum_comparison([7,16,4,12,-2,5,8], 3)
# "sorted_stub = [-2, 4, 5]"
#=> true
sum_comparison([7,16,4,12,-2,5,8], 7)
# "sorted_stub = [-2, 4, 5, 7, 8]"
#=> false
sum_comparison([7,16,4,22,18,12,2,41,5,8,17,31], 9)
# "sorted_stub = [2, 4, 5, 7]"
#=> true
Notes
The line p "sorted_stub = #{sorted_stub}" is included merely to display the array sorted_stub in the examples.
If e+smallest > x for any elements f and g in sorted for which g >= e and f < g, f+g >= e+smallest > x. Ergo, sorted_stub.last is the largest value in sorted that need be considered.
For a given value v, the line found = sorted_stub.find { |e| v+e >= x } stops the search for a second value e for which v+e = x as soon as it finds e such that v+e >= x. The next line then determines if a match has been found.

Determine if one array contains all elements of another array

I need to tell if an array contains all of the elements of another array with duplicates.
[1,2,3].contains_all? [1,2] #=> true
[1,2,3].contains_all? [1,2,2] #=> false (this is where (a1-a2).empty? fails)
[2,1,2,3].contains_all? [1,2,2] #=> true
So the first array must contain as many or equal of the number of each unique element in the second array.
This question answers it for those using an array as a set, but I need to control for duplicates.
Update: Benchmarks
On Ruby 1.9.3p194
def bench
puts Benchmark.measure {
10000.times do
[1,2,3].contains_all? [1,2]
[1,2,3].contains_all? [1,2,2]
[2,1,2,3].contains_all? [1,2,2]
end
}
end
Results in:
Rohit 0.100000 0.000000 0.100000 ( 0.104486)
Chris 0.040000 0.000000 0.040000 ( 0.040178)
Sergio 0.160000 0.020000 0.180000 ( 0.173940)
sawa 0.030000 0.000000 0.030000 ( 0.032393)
Update 2: Larger Arrays
#a1 = (1..10000).to_a
#a2 = (1..1000).to_a
#a3 = (1..2000).to_a
def bench
puts Benchmark.measure {
1000.times do
#a1.contains_all? #a2
#a1.contains_all? #a3
#a3.contains_all? #a2
end
}
end
Results in:
Rohit 9.750000 0.410000 10.160000 ( 10.158182)
Chris 10.250000 0.180000 10.430000 ( 10.433797)
Sergio 14.570000 0.070000 14.640000 ( 14.637870)
sawa 3.460000 0.020000 3.480000 ( 3.475513)
class Array
def contains_all? other
other = other.dup
each{|e| if i = other.index(e) then other.delete_at(i) end}
other.empty?
end
end
Here's a naive and straightforward implementation (not the most efficient one, likely). Just count the elements and compare both elements and their occurrence counts.
class Array
def contains_all? ary
# group the arrays, so that
# [2, 1, 1, 3] becomes {1 => 2, 2 => 1, 3 => 1}
my_groups = group_and_count self
their_groups = group_and_count ary
their_groups.each do |el, cnt|
if !my_groups[el] || my_groups[el] < cnt
return false
end
end
true
end
private
def group_and_count ary
ary.reduce({}) do |memo, el|
memo[el] ||= 0
memo[el] += 1
memo
end
end
end
[1, 2, 3].contains_all? [1, 2] # => true
[1, 2, 3].contains_all? [1, 2, 2] # => false
[2, 1, 2, 3].contains_all? [1, 2, 2] # => true
[1, 2, 3].contains_all? [] # => true
[].contains_all? [1, 2] # => false
It seems you need a multiset. Check out this gem, I think it does what you need.
You can use is and do something like (if the intersection is equal to the second multiset then the first one includes all of its elements):
#ms1 & #ms2 == #ms2
Counting the number of occurrences and comparing them seems to be the obvious way to go.
class Array
def contains_all? arr
h = self.inject(Hash.new(0)) {|h, i| h[i] += 1; h}
arr.each do |i|
return false unless h.has_key?(i)
return false if h[i] == 0
h[i] -= 1
end
true
end
end
class Array
def contains_all?(ary)
ary.uniq.all? { |x| count(x) >= ary.count(x) }
end
end
test
irb(main):131:0> %w[a b c c].contains_all? %w[a b c]
=> true
irb(main):132:0> %w[a b c c].contains_all? %w[a b c c]
=> true
irb(main):133:0> %w[a b c c].contains_all? %w[a b c c c]
=> false
irb(main):134:0> %w[a b c c].contains_all? %w[a]
=> true
irb(main):135:0> %w[a b c c].contains_all? %w[x]
=> false
irb(main):136:0> %w[a b c c].contains_all? %w[]
=> true
The following version is faster and shorter in code.
class Array
def contains_all?(ary)
ary.all? { |x| count(x) >= ary.count(x) }
end
end
Answering with my own implementation, but definitely want to see if someone can come up with a more efficient way. (I won't accept my own answer)
class Array
def contains_all?(a2)
a2.inject(self.dup) do |copy, el|
if copy.include? el
index = copy.index el
copy.delete_at index
else
return false
end
copy
end
true
end
end
And the tests:
1.9.3p194 :016 > [1,2,3].contains_all? [1,2] #=> true
=> true
1.9.3p194 :017 > [1,2,3].contains_all? [1,2,2] #=> false (this is where (a1-a2).empty? fails)
=> false
1.9.3p194 :018 > [2,1,2,3].contains_all? [1,2,2] #=> true
=> true
This solution will only iterate through both lists once, and hence run in linear time. It might however be too much overhead if the lists are expected to be very small.
class Array
def contains_all?(other)
return false if other.size > size
elem_counts = other.each_with_object(Hash.new(0)) { |elem,hash| hash[elem] += 1 }
each do |elem|
elem_counts.delete(elem) if (elem_counts[elem] -= 1) <= 0
return true if elem_counts.empty?
end
false
end
end
If you can't find a method, you can build one using ruby's include? method.
Official documentation: http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-include-3F
Usage:
array = [1, 2, 3, 4]
array.include? 3 #=> true
Then, you can do a loop:
def array_includes_all?( array, comparision_array )
contains = true
for i in comparision_array do
unless array.include? i
contains = false
end
end
return contains
end
array_includes_all?( [1,2,3,2], [1,2,2] ) #=> true

Ruby multidimensional array

Maybe it's just my lack of abilities to find stuff here that is the problem, but I can't find anything about how to create multidimensional arrays in Ruby.
Could someone please give me an example on how to do it?
Strictly speaking it is not possible to create multi dimensional arrays in Ruby. But it is possible to put an array in another array, which is almost the same as a multi dimensional array.
This is how you could create a 2D array in Ruby:
a = [[1,2,3], [4,5,6], [7,8,9]]
As stated in the comments, you could also use NArray which is a Ruby numerical array library:
require 'narray'
b = NArray[ [1,2,3], [4,5,6], [7,8,9] ]
Use a[i][j] to access the elements of the array. Basically a[i] returns the 'sub array' stored on position i of a and thus a[i][j] returns element number j from the array that is stored on position i.
you can pass a block to Array.new
Array.new(n) {Array.new(n,default_value)}
the value that returns the block will be the value of each index of the first array,
so..
Array.new(2) {Array.new(2,5)} #=> [[5,5],[5,5]]
and you can access this array using array[x][y]
also for second Array instantiation, you can pass a block as default value too. so
Array.new(2) { Array.new(3) { |index| index ** 2} } #=> [[0, 1, 4], [0, 1, 4]]
Just a clarification:
arr = Array.new(2) {Array.new(2,5)} #=> [[5,5],[5,5]]
is not at all the same as:
arr = Array.new(2, Array.new(2, 5))
in the later case, try:
arr[0][0] = 99
and this is what you got:
[[99,5], [99,5]]
There are two ways to initialize multi array (size of 2).
All the another answers show examples with a default value.
Declare each of sub-array (you can do it in a runtime):
multi = []
multi[0] = []
multi[1] = []
or declare size of a parent array when initializing:
multi = Array.new(2) { Array.new }
Usage example:
multi[0][0] = 'a'
multi[0][1] = 'b'
multi[1][0] = 'c'
multi[1][1] = 'd'
p multi # [["a", "b"], ["c", "d"]]
p multi[1][0] # "c"
So you can wrap the first way and use it like this:
#multi = []
def multi(x, y, value)
#multi[x] ||= []
#multi[x][y] = value
end
multi(0, 0, 'a')
multi(0, 1, 'b')
multi(1, 0, 'c')
multi(1, 1, 'd')
p #multi # [["a", "b"], ["c", "d"]]
p #multi[1][0] # "c"
The method given above don't works.
n = 10
arr = Array.new(n, Array.new(n, Array.new(n,0.0)))
arr[0][1][2] += 1
puts arr[0][2][2]
is equivalent to
n = 10
a = Array.new(n,0.0)
b = Array.new(n,a)
arr = Array.new(n, b)
arr[0][1][2] += 1
puts arr[0][2][2]
and will print 1.0, not 0.0, because we are modifiyng array a and printing the element of array a.
Actually this is much quicker than the block method given above:
arr = Array.new(n, Array.new(n, Array.new(n,0.0)))
arr[0][1][2] += 1
I had to reproduce PHP-style multidimensional array in Ruby recently. Here is what I did:
# Produce PHP-style multidimensional array.
#
# Example
#
# arr = Marray.new
#
# arr[1][2][3] = "foo"
# => "foo"
#
# arr[1][2][3]
# => "foo"
class Marray < Array
def [](i)
super.nil? ? self[i] = Marray.new : super
end
end
Perhaps you can simulate your multidimensional Array with a Hash. The Hash-key can by any Ruby object, so you could also take an array.
Example:
marray = {}
p marray[[1,2]] #-> nil
marray[[1,2]] = :a
p marray[[1,2]] #-> :a
Based on this idea you could define a new class.
Just a quick scenario:
=begin rdoc
Define a multidimensional array.
The keys must be Fixnum.
The following features from Array are not supported:
* negative keys (Like Array[-1])
* No methods <<, each, ...
=end
class MArray
INFINITY = Float::INFINITY
=begin rdoc
=end
def initialize(dimensions=2, *limits)
#dimensions = dimensions
raise ArgumentError if limits.size > dimensions
#limits = []
0.upto(#dimensions-1){|i|
#limits << (limits[i] || INFINITY)
}
#content = {}
end
attr_reader :dimensions
attr_reader :limits
=begin rdoc
=end
def checkkeys(keys)
raise ArgumentError, "Additional key values for %i-dimensional Array" % #dimensions if keys.size > #dimensions
raise ArgumentError, "Missing key values for %i-dimensional Array" % #dimensions if keys.size != #dimensions
raise ArgumentError, "No keys given" if keys.size == 0
keys.each_with_index{|key,i|
raise ArgumentError, "Exceeded limit for %i dimension" % (i+1) if key > #limits[i]
raise ArgumentError, "Only positive numbers allowed" if key < 1
}
end
def[]=(*keys)
data = keys.pop
checkkeys(keys)
#content[keys] = data
end
def[](*keys)
checkkeys(keys)
#content[keys]
end
end
This can be used as:
arr = MArray.new()
arr[1,1] = 3
arr[2,2] = 3
If you need a predefined matrix 2x2 you can use it as:
arr = MArray.new(2,2,2)
arr[1,1] = 3
arr[2,2] = 3
#~ arr[3,2] = 3 #Exceeded limit for 1 dimension (ArgumentError)
I could imagine how to handle commands like << or each in a two-dimensional array, but not in multidimensional ones.
It might help to remember that the array is an object in ruby, and objects are not (by default) created simply by naming them or naming a the object reference. Here is a routine for creating a 3 dimension array and dumping it to the screen for verification:
def Create3DimensionArray(x, y, z, default)
n = 0 # verification code only
ar = Array.new(x)
for i in 0...x
ar[i] = Array.new(y)
for j in 0...y
ar[i][j] = Array.new(z, default)
for k in 0...z # verification code only
ar[i][j][k] = n # verification code only
n += 1 # verification code only
end # verification code only
end
end
return ar
end
# Create sample and verify
ar = Create3DimensionArray(3, 7, 10, 0)
for x in ar
puts "||"
for y in x
puts "|"
for z in y
printf "%d ", z
end
end
end
Here is an implementation of a 3D array class in ruby, in this case the default value is 0
class Array3
def initialize
#store = [[[]]]
end
def [](a,b,c)
if #store[a]==nil ||
#store[a][b]==nil ||
#store[a][b][c]==nil
return 0
else
return #store[a][b][c]
end
end
def []=(a,b,c,x)
#store[a] = [[]] if #store[a]==nil
#store[a][b] = [] if #store[a][b]==nil
#store[a][b][c] = x
end
end
array = Array3.new
array[1,2,3] = 4
puts array[1,2,3] # => 4
puts array[1,1,1] # => 0

Resources