Get original array indices after #combination - arrays

I am learning Ruby and one issue I have come across in a few practice problems is working with combinations from an array, but getting the original array indices instead of the actual combination as a result. To keep it simple let's just talk about pairs. I know a fast way to get all possible pairs is:
array = [a, b, c]
array.combination(2).to_a
# => [[a, b], [a, c], [b, c]]
Now let's say I want to iterate over these combinations and choose a pair that fits an arbitrary condition. It would be easy enough to return the pair itself:
...select{|pair| condition}
# => [a, c]
# assuming this pair fits the condition
but what if I want to return the indices from the original array instead?
# => [0, 2]
Is there a way of doing this using #combination ? Or would you have to find the combinations yourself in this case? If so is there a more elegant way to do it than the following(which is what I ended up doing to solve these problems)?
array.each.with_index do |s1, i1|
array[(i1 + 1)..-1].each.with_index do |s2, i2|
if condition
result = [i1, (i2 + i1 + 1)]
end
end
end

Try this:
array = ['a', 'b', 'c', 'd']
array.combination(s).to_a.reduce do |memo, pair|
if condition # I tested with pair[0] == 'a' && pair[1] == 'c'
memo = pair.map {|e| array.index(e)}
else
memo
end
end
My test of this yielded:
[0, 2]
edit
To avoid using a call to index, just compute the indexes ahead of time and then create a combination of them, selecting the one(s) that meet the condition:
(0..array.length).to_a.combination(2).to_a.select {|pair| condition}

Related

Find the index by current sort order of an array in ruby

If there is an array
array A = ["a","b","c","d"] #Index is [0,1,2,3]
And it's sorted to.
array A = ["d","c","b","a"]
I need an array that returns me the updated index based on the sorted order
[3,2,1,0]
I'm trying to find a solution to this ruby
UPDATE to the question
If a is sorted to
array A = ["d","b","c","a"] #not a pure reverse
Then the returned index array should be
[3,1,2,0]
You need to create a mapping table that preserves the original order, then use that order to un-map the re-ordered version:
orig = %w[ a b c d ]
orig_order = orig.each_with_index.to_h
revised = %w[ d c b a ]
revised.map { |e| orig_order[e] }
# => [3, 2, 1, 0]
So long as your elements are unique this will be able to track any shift in order.
Here is one way to do this:
original_array = ["a","b","c","d"]
jumbled_array = original_array.shuffle
jumbled_array.map {|i| original_array.index(i)}
#=> [1, 3, 0, 2]
Note:
In this sample, output will change for every run as we are using shuffle to demonstrate the solution.
The solution will work only as long as array has no duplicate values.
If you do wish to solution to work with arrays with duplicate values, then, one possibility is to look at object_id of array members while figuring out the index.
jumbled_array.map {|i| original_array.map(&:object_id).index(i.object_id)}
This solution will work as long as jumbled_array contains element from original_array and no elements were recreated using dup or something that results in change in object_id values
You can use the map and index methods.
arr = ["a","b","c","d"]
sort_arr = ["d","c","b","a"]
sort_arr.map{|s| arr.index(s)}
# => [3, 2, 1, 0]

Difference Between Arrays Preserving Duplicate Elements in Ruby

I'm quite new to Ruby, and was hoping to get the difference between two arrays.
I am aware of the usual method:
a = [...]
b = [...]
difference = (a-b)+(b-a)
But the problem is that this is computing the set difference, because in ruby, the statement (a-b) defines the set compliment of a, relative to b.
This means [1,2,2,3,4,5,5,5,5] - [5] = [1,2,2,3,4], because it takes out all of occurrences of 5 in the first set, not just one, behaving like a filter on the data.
I want it to remove differences only once, so for example, the difference of [1,2,2,3,4,5,5,5,5], and [5] should be [1,2,2,3,4,5,5,5], removing just one 5.
I could do this iteratively:
a = [...]
b = [...]
complimentAbyB = a.dup
complimentBbyA = b.dup
b.each do |bValue|
complimentAbyB.delete_at(complimentAbyB.index(bValue) || complimentAbyB.length)
end
a.each do |aValue|
complimentBbyA.delete_at(complimentBbyA.index(aValue) || complimentBbyA.length)
end
difference = complimentAbyB + complimentBbyA
But this seems awfully verbose and inefficient. I have to imagine there is a more elegant solution than this. So my question is basically, what is the most elegant way of finding the difference of two arrays, where if one array has more occurrences of a single element then the other, they will not all be removed?
I recently proposed that such a method, Ruby#difference, be added to Ruby's core. For your example, it would be written:
a = [1,2,2,3,4,5,5,5,5]
b = [5]
a.difference b
#=> [1,2,2,3,4,5,5,5]
The example I've often given is:
a = [3,1,2,3,4,3,2,2,4]
b = [2,3,4,4,3,4]
a.difference b
#=> [1, 3, 2, 2]
I first suggested this method in my answer here. There you will find an explanation and links to other SO questions where I proposed use of the method.
As shown at the links, the method could be written as follows:
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
.....
ha = a.group_by(&:itself).map{|k, v| [k, v.length]}.to_h
hb = b.group_by(&:itself).map{|k, v| [k, v.length]}.to_h
ha.merge(hb){|_, va, vb| (va - vb).abs}.inject([]){|a, (k, v)| a + [k] * v}
ha and hb are hashes with the element in the original array as the key and the number of occurrences as the value. The following merge puts them together and creates a hash whose value is the difference of the number of occurrences in the two arrays. inject converts that to an array that has each element repeated by the number given in the hash.
Another way:
ha = a.group_by(&:itself)
hb = b.group_by(&:itself)
ha.merge(hb){|k, va, vb| [k] * (va.length - vb.length).abs}.values.flatten

Creating an array from variables without nil values in Ruby

What's the most idiomatic way to create an array from several variables without nil values?
Given these variables:
a = 1
b = nil
c = 3
I would like to create an array ary:
ary #=> [1, 3]
I could use Array#compact:
ary = [a, b, c].compact
ary #=> [1, 3]
But putting everything in an array just to remove certain elements afterwards doesn't feel right.
Using if statements on the other hand produces more code:
ary = []
ary << a if a
ary << b if b
ary << c if c
ary #=> [1, 3]
Is there another or a preferred way or are there any advantages or drawbacks using either of the above?
PS: false doesn't necessarily have to be considered. The variables are either truthy (numbers / strings / arrays / hashes) or nil.
If you are concerned about performance, best way would be probably to use destructive #compact! to avoid allocating memory for second array.
I was hoping for a way to somehow "skip" the nil values during array creation. But after thinking about this for a while, I realized that this can't be achieved because of Ruby's way to handle multiple values. There's no concept of a "list" of values, multiple values are always represented as an array.
If you assign multiple values, Ruby creates an array:
ary = 1, nil, 3
#=> [1, nil, 3]
Same for a method taking a variable number of arguments:
def foo(*args)
args
end
foo(1, nil, 3)
#=> [1, nil, 3]
So even if I would patch Array with a class method new_without_nil, I would end up with:
def Array.new_without_nil(*values)
values.compact!
values
end
This just moves the code elsewhere.
Everything is an object
From an OO point of view, there's nothing special about nil - it's an object like any other. Therefore, removing nil's is not different from removing 1's.
Using a bunch of if statements on the other hand is something I'm trying to avoid when writing object oriented code. I prefer sending messages to objects.
Regarding "advantages or drawbacks":
[...] with compact / compact!
creates full array and shrinks it as needed
short code, often fits in one line
is easily recognized
evaluates each item once
faster (compiled C code)
[...] with << and if statements
creates empty array and grows it as needed
long code, one line per item
purpose might not be as obvious
items can easily be commented / uncommented
evaluates each item twice
slower (interpreted Ruby code)
Verdict:
I'll use compact, might have been obvious.
Here is a solution that uses a hash.
With these values put in an array:
a = 1; b = nil; c = 3; d = nil; e = 10;
ary = [a, b, c, d, e]
There are two nil items in the result which would require a compact to remove both "nil" items.
However the same variables added to a hash:
a = 1; b = nil; c = 3; d = nil; e = 10;
hash = {a => nil, b => nil, c => nil, d => nil, e => nil}
There is just one "nil" item in the result which can easily be removed by hash.delete(nil).

Counting matching elements in an array

Given two arrays of equal size, how can I find the number of matching elements disregarding the position?
For example:
[0,0,5] and [0,5,5] would return a match of 2 since there is one 0 and one 5 in common;
[1,0,0,3] and [0,0,1,4] would return a match of 3 since there are two matches of 0 and one match of 1;
[1,2,2,3] and [1,2,3,4] would return a match of 3.
I tried a number of ideas, but they all tend to get rather gnarly and convoluted. I'm guessing there is some nice Ruby idiom, or perhaps a regex that would be an elegant answer to this solution.
You can accomplish it with count:
a.count{|e| index = b.index(e) and b.delete_at index }
Demonstration
or with inject:
a.inject(0){|count, e| count + ((index = b.index(e) and b.delete_at index) ? 1 : 0)}
Demonstration
or with select and length (or it's alias – size):
a.select{|e| (index = b.index(e) and b.delete_at index)}.size
Demonstration
Results:
a, b = [0,0,5], [0,5,5] output: => 2;
a, b = [1,2,2,3], [1,2,3,4] output: => 3;
a, b = [1,0,0,3], [0,0,1,4] output => 3.
(arr1 & arr2).map { |i| [arr1.count(i), arr2.count(i)].min }.inject(0, &:+)
Here (arr1 & arr2) return list of uniq values that both arrays contain, arr.count(i) counts the number of items i in the array.
Another use for the mighty (and much needed) Array#difference, which I defined in my answer here. This method is similar to Array#-. The difference between the two methods is illustrated in the following example:
a = [1,2,3,4,3,2,4,2]
b = [2,3,4,4,4]
a - b #=> [1]
a.difference b #=> [1, 3, 2, 2]
For the present application:
def number_matches(a,b)
left_in_b = b
a.reduce(0) do |t,e|
if left_in_b.include?(e)
left_in_b = left_in_b.difference [e]
t+1
else
t
end
end
end
number_matches [0,0,5], [0,5,5] #=> 2
number_matches [1,0,0,3], [0,0,1,4] #=> 3
number_matches [1,0,0,3], [0,0,1,4] #=> 3
Using the multiset gem:
(Multiset.new(a) & Multiset.new(b)).size
Multiset is like Set, but allows duplicate values. & is the "set intersection" operator (return all things that are in both sets).
I don't think this is an ideal answer, because it's a bit complex, but...
def count(arr)
arr.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
end
def matches(a1, a2)
m = 0
a1_counts = count(a1)
a2_counts = count(a2)
a1_counts.each do |e, c|
m += [a1_counts, a2_counts].min
end
m
end
Basically, first write a method that creates a hash from an array of the number of times each element appears. Then, use those to sum up the smallest number of times each element appears in both arrays.

Insert items into an array while iterating

How would I modify (add/remove elements) an array while iterating over it and have the iterator be aware of it?
For example I would think this code:
a = "1234567890".split("")
a.each_with_index{|d, i|
if d.eql?('5')
a.delete_at(i)
a.insert(i, ['A', 'B', 'C'] )
end
print d
}
would produce: 1234ABC67890 but instead produces 1234567890
Is there a workaround or different method to make this work?
(I know this example is pretty simple example but I am doing some complicated text processing where I need to insert some text when I hit a key word. I do a bunch of functions before and after I would do the expansion so doing the insert outside of the each loop [aka map!] would really complicate my code)
Actually, your code works, you just need to replace print d with print a[i] since what you're printing is the variable d not the actual array element at index i
Rather than deleting and inserting, why not change the element on that index?
a = "1234567890".split("")
a.each_with_index{|d, i|
if d.eql?('5')
a[i] = ['A','B','C']
end
print a[i] #a[i] rather than d, since d contains the old value
}
or
...
if d.eql?('5')
a[i] = ['A','B','C']
d = a[i]
end
print d
Deleting/Inserting on an Array during iterations is discourage since it may cause headaches haha... Resort to other methods if possible :)
Note:
I've just used the current logic in your code based on my understanding, and the given desired output
the array will become [1,2,3,4,['A','B','C'],6,7,8,9,0] and not [1,2,3,4,'A','B','C',6,7,8,9,0]. If you want the other, just leave a comment :)
If what you want is just to change a value in the string, you can just use .tr or .gsub to do the job
Here is one option. If you want to return array then remove join otherwise keep it to return a String
a = "1234567890".split("")
a.collect! {|i| i == '5' ? ['A', 'B', 'C'] : i }.flatten!.join #=> "1234ABC67890"
Inserting and deleting while iterating is best avoided. Some problems disappear however when iterating in reverse order:
a = "1234567890".split("")
a.reverse_each.with_index{|d, i|
if d.eql?('5')
a.delete_at(i)
a.insert(i, ['A', 'B', 'C'] )
end
}
puts a.join # => 12345ABC7890
You can't in general modify an Enumerable while iterating over its members. In most such cases, you need to construct a new Enumerable as you go.
b = []
a.each_with_index do |d, i|
if d.eql?('5')
b << 'A' << 'B' << 'C'
else
b << d
end
end

Resources