How can I test that some specific element comes before another specific element in an array? - arrays

I had a problem whereby a list of jobs, each represented by a character "a", "b", "c" etc, had to be sorted given that some of them depended on other jobs. So if job "a" depended on "b", and "b" and "c" didn't depend on any job, the list 'abc' would be sorted as 'bac', simple.
My problem is I want to write a test to check that jobs come after the jobs they depend on in the output array.
Example
If job "c" depends on job "f", how can I test that "f" is before "c" in
array = ["a", "b", "f", "c", "d", "e"]

There is already most of the solution in the comment by Cary Swoveland. Here is a full example which takes care of all the cases (no c, no f, neither, wrong order, ok)
jobs = %w[a b f c d e]
index_c = jobs.index('c')
index_f = jobs.index('f')
if index_c && index_f && index_c > index_f
puts "Everything is fine"
elsif index_c && index_f
puts "job c is before job f"
elsif !index_c && !index_f
puts "jobs are missing"
elsif !index_c
puts "no job c"
else
puts "no job f"
end

You could create a hash with the job and its dependency, for instance and as in your example if job c depends on f and b depends on a, then:
hash = {c: 'f', b: 'a'}
So, over your hash you can use map to get a "plain" array where you check if the index of the hash value in your array is minor than the index of the hash key (to string) also in the array, if so, then you add true, then checking for all values within the array, all must be true:
array = ['a', 'b', 'f', 'c', 'd', 'e']
result = hash.map{|k,v| array.index(v) < array.index(k.to_s)}
p result.all?{|e| e} # => true
So, using Minitest, you can test result.all?{|e| e}.eql? is equal to true:
require 'minitest/autorun'
class Test < Minitest::Test
def test_a_job_is_before_its_dependency
hash = {c: 'f', b: 'a'}
array = ['a', 'b', 'f', 'c', 'd', 'e']
result = hash.map{|k,v| array.index(v) < array.index(k.to_s)}.all?{|e| e}
assert result.equal? true
end
end

The best data format for this problem would be a dependency graph or a directed acyclic graph.
To get a suitable array of jobs, you'd need topological sorting, e.g. using Kahn's algorithm or a depth-first search.
Plexus gem offers a topological sort.
require 'plexus'
include Plexus
dg = Digraph["a","b", "a","f", "f","c", "f","d", "d","e", "b","f"]
p dg.topsort
# ["a", "b", "f", "d", "e", "c"]
require 'plexus/dot'
dg.write_to_graphic_file('png','visualize')
All you need to check is if the resulting array is empty or not:
require 'plexus'
include Plexus
dg = Digraph["c","a", "a","b", "b","a"]
p dg.topsort
# []
If it's empty, there's at least one cycle in your dependencies, and no array can be found. If the array's not empty, it includes every element in a correct order. No need to check it further.

Related

Using TSort in Ruby to reorder and sort arrays

Hoping I can get some help on this particular reordering/sorting question in Ruby.
I've got an array of arrays, like so:
[['b', 'f'],
['f', 'h'],
['a', 'e'],
['b', 'c'],
['b', 'd'],
['e', 'g'],
['c', 'f'],
['d', 'f'],
['f', 'g'],
['g', 'h']]
The second element in each array must occur after the first, so I want to write a program that will sort them into an array that looks like this:
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
I'm trying to use Ruby's built in TSort library, and I'm also working from this stack overflow post.
So I'm doing this:
class Hash
include TSort
alias tsort_each_node each_key
def tsort_each_child(node, &block)
fetch(node).each(&block)
end
end
def flex_sort(arr)
stuff = arr.map do |head, *tail|
{head => tail}
end
stuff.reduce(&:merge).tsort.reverse
end
sorted = flex_sort(flex)
I have several questions about this. First, am I even on the right track? Second, when I run this code, you will notice that the initial array of arrays does not include an array with the first element 'h', so that when I convert them into a hash, and try to run .tsort, I get something like key 'h' does not exist, which forces me to put ['h'] into the array of arrays just so that it doesn't break. Is there a way around this?
fetch takes a second parameter which is the default value, if it doesn't exist.
fetch(node, []).each(&block)
the second problem is when you &:merge your array into each other, you're overwriting previous values. The current result of the merge is
{"b"=>["d"], "f"=>["g"], "a"=>["e"], "e"=>["g"], "c"=>["f"], "d"=>["f"], "g"=>["h"]}
with only the one value per key. If you change that to
def flex_sort(arr)
stuff = Hash.new { |hash, key| hash[key] = [] }
arr.each do |head, tail|
stuff[head] << tail
end
stuff.tsort.reverse
end
your hash looks like
{"b"=>["f", "c", "d"], "f"=>["h", "g"], "a"=>["e"], "e"=>["g"], "c"=>["f"], "d"=>["f"], "g" =>["h"]}
and now running your tsort you end up with
["a", "e", "b", "d", "c", "f", "g", "h"]
which is extremely close to what you're wanting. Not familiar with this sort to know if there's a way to force it to pick some keys before others when there's a multiple possibilities. But this gets you that much closer, at least.

Ruby on Rails - How to know how many time the same Object appears in Array using Active Record?

How to know how many times the same Object appears in Array?
I want to check how many times I found the object, like:
array = ['A','A','A','B','B','C','C','C','D']
So, A appeared three times, B twice, C three too, and only one for D.
I know that if I use "find_all", like:
array.find_all{ |e| array.count(e) > 1 }
I will get with answer
["A", "A", "A", "B", "B", "C", "C", "C"]
but, how I can count this? I want something like:
A = 3, B = 2, C = 3, D = 1.
You can use inject on the array to iterate over the array and pass a hash into each iteration to store data. So to retrieve the count of the array you gave you would do this:
array = ["A", "A", "A", "B", "B", "C", "C", "C"]
array.inject(Hash.new(0)) do |hash, array_item|
hash[array_item] += 1
hash # this will be passed into the next iteration as the hash parameter
end
=> {"A"=>3, "B"=>2, "C"=>3}
Passing in Hash.new(0) rather than {} will mean that the default value for each key when you first encounter it will be 0.

Most efficient way to count duplicated elements between two arrays

As part of a very basic program I am writing in Ruby, I am trying to find the total number of shared elements between two arrays of equal length, but
I need to include repeats.
My current example code for this situation is as follows:
array_a = ["B","A","A","A","B"]
array_b = ["A","B","A","B","B"]
counter = 0
array_a.each_index do |i|
array_a.sort[i] == array_b.sort[i]
counter += 1
end
end
puts counter
I want the return value of this comparison in this instance to be 4, and not 2, as the two arrays share 2 duplicate characters ("A" twice, and "B" twice). This seems to work, but I am wondering if there are any more efficient solutions for this issue. Specifically whether there are any methods you would suggest looking into. I spoke with someone who suggested a different method, inject, but I really don't understand how that applies and would like to understand. I did quite a bit of reading on uses for it, and it still isn't clear to me how it is appropriate. Thank you.
Looking at my code, I have realized that it doesn't seem to work for the situation that I am describing.
Allow me to reiterate and explain what I think the OP's original intent was:
Given arrays of equal size
array_a = ["B","A","A","A","B"]
array_b = ["A","B","A","B","B"]
We need to show the total number of matching pairs of elements between the two arrays. In other words, each B in array_a will "use up" a B in array_b, and the same will be true for each A. As there are two B's in array_a and three in array_b, this leaves us with a count of 2 for B, and following the same logic, 2 for A, for a sum of 4.
(array_a & array_b).map { |e| [array_a.count(e), array_b.count(e)].min }.reduce(:+)
If we get the intersection of the arrays with &, the result is a list of values that exist in both arrays. We then iterate over each match, and select the minimum number of times the element exists in either array --- this is the most number of times the element that can be "used". All that is left is to total the number of paired elements, with reduce(:+)
Changing array_a to ["B", "A", "A", "B", "B"] results in a total of 5, as there are now enough of B to exhaust the supply of B in array_b.
If I understand the question correctly, you could do the following.
Code
def count_shared(arr1, arr2)
arr1.group_by(&:itself).
merge(arr2.group_by(&:itself)) { |_,ov,nv| [ov.size, nv.size].min }.
values.
reduce(0) { |t,o| (o.is_a? Array) ? t : t + o }
end
Examples
arr1 = ["B","A","A","A","B"]
arr2 = ["A","B","A","B","B"]
count_shared(arr1, arr2)
#=> 4 (2 A's + 2 B's)
arr1 = ["B", "A", "C", "C", "A", "A", "B", "D", "E", "A"]
arr2 = ["C", "D", "F", "F", "A", "B", "A", "B", "B", "G"]
count_shared(arr1, arr2)
#=> 6 (2 A's + 2 B's + 1 C + 1 D + 0 E's + 0 F's + 0 G's)
Explanation
The steps are as follows for a slightly modified version of the first example.
arr1 = ["B","A","A","A","B","C","C"]
arr2 = ["A","B","A","B","B","D"]
First apply Enumerable#group_by to both arr1 and arr2:
h0 = arr1.group_by(&:itself)
#=> {"B"=>["B", "B"], "A"=>["A", "A", "A"], "C"=>["C", "C"]}
h1 = arr2.group_by(&:itself)
#=> {"A"=>["A", "A"], "B"=>["B", "B", "B"], "D"=>["D"]}
Prior to Ruby v.2.2, when Object#itself was introduced, you would have to write:
arr.group_by { |e| e }
Continuing,
h2 = h0.merge(h1) { |_,ov,nv| [ov.size, nv.size].min }
#=> {"B"=>2, "A"=>2, "C"=>["C", "C"], "D"=>["D"]}
I will return shortly to explain the above calculation.
a = h2.values
#=> [2, 2, ["C", "C"], ["D"]]
a.reduce(0) { |t,o| (o.is_a? Array) ? t : t + o }
#=> 4
Here Enumerable#reduce (aka inject) merely sums the values of a that are not arrays. The arrays correspond to elements of arr1 that do not appear in arr2 or vise-versa.
As promised, I will now explain how h2 is computed. I've used the form of Hash#merge that employs a block (here { |k,ov,nv| [ov.size, nv.size].min }) to compute the values of keys that are present in both hashes being merged. For example, when the first key-value pair of h1 ("A"=>["A", "A"]) is being merged into h0, since h0 also has a key "A", the array
["A", ["A", "A", "A"], ["A", "A"]]
is passed to the block and the three block variables are assigned values (using "parallel assignment", which is sometimes called "multiple assignment"):
k, ov, nv = ["A", ["A", "A", "A"], ["A", "A"]]
so we have
k #=> "A"
ov #=> ["A", "A", "A"]
nv #=> ["A", "A"]
k is the key, ov ("old value") is the value of "A" in h0 and nv ("new value") is the value of "A" in h1. The block calculation is
[ov.size, nv.size].min
#=> [3,2].min = 2
so the value of "A" is now 2.
Notice that the key, k, is not used in the block calculation (which is very common when using this form of merge). For that reason I've changed the block variable from k to _ (a legitimate local variable), both to reduce the chance of introducing a bug and to signal to the reader that the key is not used in the block. The other elements of h2 that use this block are computed similarly.
Another way
It would be quite simple if we had available an Array method I've proposed be added to the Ruby core:
array_a = ["B","A","A","A","B"]
array_b = ["A","B","A","B","B"]
array_a.size - (array_a.difference(array_b)).size
#=> 4
or
array_a.size - (array_b.difference(array_a)).size
#=> 4
I've cited other applications in my answer here.
This is a perfect job for Enumerable#zip and Enumerable#count:
array_a.zip(array_b).count do |a, b|
a == b
end
# => 2
The zip method pairs up elements, "zippering" them together, and the count method can take a block as to if the element should be counted.
The inject method is very powerful, but it's also the most low-level. Pretty much every other Enumerable method can be created with inject if you work at it, so it's quite flexible, but usually a more special-purpose method is better suited. It's still a useful tool if applied correctly.
In this case zip and count do a much better job and if you know what these methods do, this code is self explanatory.
Update:
If you need to count all overlapping letters regardless of order you need to do some grouping on them. Ruby on Rails provides the handy group_by method in ActiveSupport, but in pure Ruby you need to make your own.
Here's an approach that counts up all the unique letters, grouping them using chunk:
# Convert each array into a map like { "A" => 2, "B" => 3 }
# with a default count of 0.
counts = [ array_a, array_b ].collect do |a|
Hash.new(0).merge(
Hash[a.sort.chunk { |v| v }.collect { |k, a| [ k, a.length ] }]
)
end
# Iterate over one of the maps key by key and count the minimum
# overlap between the two.
counts[0].keys.inject(0) do |sum, key|
sum + [ counts[0][key], counts[1][key] ].min
end

Custom Sort Groovy JSONArray - Custom Values First, Then Alphabetical

I have a static list of values that is in a JSONArray. Here is my example array:
JSONArray json = new JSONArray()
json = ["B", "E", "C", "Z", "A", "X", "F", "H"]
I need to sort this json array in a custom way. I need to put "E" first, "F" second, and then sort the rest by alphabetical order.
I want my end result to be this:
json = ["E", "F", "A", "B", "C", "H", X", "Z"]
Groovy has the basic sort functionality that I can sort alphabetically or reverse alphabetically using:
json.sort()
or
json.reverse()
I'm looking for an easy way to do a custom sort.
in my 5-min experiment I used weights:
def json = ["B", "E", "C", "Z", "A", "X", "F", "H"]
def weights = [ E:10, F:9 ]
json.sort{
it.charAt( 0 ) - ( weights[ it ] ?: 0 )
}
assert '[E, F, A, B, C, H, X, Z]' == json.toString()
you might want to include some error checking
You can use closures if you define your own sort method, but what you're actually asking for is some array splitting with a little normal sorting.
json.findAll{it = 'E'} + json.findAll{it = 'F'} + json.findAll{!(it in ['E', 'F'])}.sort()
If you're worried about the efficiency of looping through your json 3 times you can iterate through your json once, adding it to different arrays as you go.
The below example is a little fancier. The inject method will iterate over a collection, passing a value between each iteration (in our case a list of 3 lists. The first list will hold our E's, the second our F's, and the 3rd for everything else. After sorting our catchall list we use .flatten() to transform the 3 lists back into one list.
List organizedList = json.inject([[],[],[]]) {List<List> result, String jsonValue ->
select(jsonValue) {
case 'E':
result.get(0).add(jsonValue) // Could have just added 'E' but I like symmetry
break;
case 'F':
result.get(1).add(jsonValue)
break;
default:
result.get(2).add(jsonValue)
}
return result // Gets passed to each iteration over json
}
organizedList.get(2).sort() // sort on a list modifies the original list
organizedList.flatten()
It's also possible using sort with a closure where you define your own sorting; but as you can see, it doesn't flow quite as easily.
json.sort {String a, String b ->
if (a = b) return 0 // For efficiency's sake
def letterFirst = {String priority -> // Closure to help sort against a hardcoded value
if (a = priority) return 1
if (b = priority) return -1
return 0
}
def toReturn = letterFirst('E')
if (!toReturn) toReturn = letterFirst('F') // groovy evaluates 0 as false
if (!toReturn) toReturn = a <=> b
return toReturn
}

Remove one object from an array with multiple matching objects

I have an array:
array = ['a', 'b', 'c', 'a', 'b', 'a', 'a']
sorted, just to make it easier to look at:
array = ['a', 'a', 'a', 'a', 'b', 'b', 'c']
I want to remove, for example, three of the a's. array.delete('a') removes every a.
The following code 'works' but I think you'll agree it's absolutely hideous.
new_array = array.sort.join.sub!('aaa', '').split(//)
How do I do this more cleanly?
To give a bit more information on what I'm doing here, I have some strings being pushed into an array asynchronously. Those strings can be (and often are) identical to each other. If there are a certain number of those matching strings, an action is triggered, the matching object is removed (like Tetris, I guess), and the process continues.
Before the following code is run, array could be ['a', 'a', 'a', 'b']:
while array.count(product[:name]) >= product[:quantity]
# trigger an event
product[:quantity].times do
array.slice!(array.index(product[:name]))
end
end
assuming that product[:name] is a and product[:quantity] is 3, after the code above runs, array should be ['b'].
I think you have an XY-problem. Instead of an array, you should use a hash with number of occurrences as the value.
hash = Hash.new(0)
When you want to add an entity, you should do:
hash["a"] += 1
If you want to limit the number to a certain value, say k, then do:
hash["a"] += 1 unless hash["a"] == k
slice may be the thing you're looking for:
3.times {array.slice!(array.index('a'))}
If you want to maintain, or convert, an array so it only one instance of each element, you can use uniq or a Set instead of an array.
array = ['a', 'b', 'c', 'a', 'b', 'a', 'a']
array.uniq # => ["a", "b", "c"]
require 'set'
array.to_set # => #<Set: {"a", "b", "c"}>
A Set will automatically maintain the uniqueness of all the elements for you, which is useful if you're going to have a huge number of potentially repetitive elements and don't want to accumulate them in memory before doing a uniq on them.
#sawa mentioned that this looks like an "XY problem", and I agree.
The source of the problem is using an array instead of a hash as your basic container. An array is good when you have a queue or list of things to process in order but it's horrible when you need to keep track of the count of things, because you have to walk that array to find out how many of a certain thing you have. There are ways to coerce the information you want out of an array when you get the array as your source.
Since it looks like he identified the real problem, here are some building blocks to use around the problem.
If you have an array and want to figure out how many different elements there are, and their count:
array = ['a', 'a', 'a', 'a', 'b', 'b', 'c', 'c']
array_count = array.group_by { |i| i }.map{ |k, v| [k, v.size] }.to_h
# => {"a"=>4, "b"=>2, "c"=>2}
From that point it's easy to find out which ones exceed a certain count:
array_count.select{ |k, v| v >= 3 } # => {"a"=>4}
For a quick way to remove all elements of something from the array, after processing you can use a set "difference" operation:
array = ['a', 'a', 'a', 'a', 'b', 'b', 'c']
array -= ['a']
# => ["b", "b", "c", "c"]
or delete_if:
array.delete_if { |i| i == 'a' }
array # => ["b", "b", "c"]

Resources