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"]
Related
I have been trying to iterate through an array.
below is the code.
x = ['lemon', 'tea', 'water', ]
def randomShuffle (arr,n):
from random import choices
newList=[]
for item in arr:
r=choices(arr, k=n)
if r.count(item) <= 2:
newList.append(item)
return (newList)
i would like to know the logic for writing it please.
thank you all
Use a while loop: if every item is to appear twice, then teh resulting array should be twice the length of the input one.
And of course check not to add the same item more than twice in the result ;)
Choices return a list of size 1, so I use [0] to get the element
xx = ["a", "b", "c"]
def my_function(x):
res = []
while len(res) < len(x) * 2:
c = choices(x)[0]
if res.count(c) < 2:
res.append(c)
return res
my_function(xx)
> ['c', 'c', 'a', 'b', 'a', 'b']
my_function(xx)
> ['a', 'b', 'b', 'a', 'c', 'c']
I know that I can easily get all the combinations, but is there a way to only get the ones that contain certain element of the list? I'll give an example.
Lets say I have
arr = ['a','b','c','d']
I want to get all combinations with length (n) containing 'a', for example, if n = 3:
[a, b, c]
[a, b, d]
[a, c, d]
I want to know if there is a better way to get it without generating all combinations. Any help would be appreciated.
I would proceed as follow:
Remove 'a' from the array
Generate all combinations of 2 elements from the reduced array
For each combination, insert the 'a' in all three possible places
You can use combination of itertools and list comprehension. Like:
import itertools
import itertools
arr = ['a', 'b', 'c', 'd']
temp = itertools.combinations(arr, 3)
result = [list(i) for i in list(temp) if 'a' in i]
print(result)
output:
[['a', 'b', 'c'], ['a', 'b', 'd'], ['a', 'c', 'd']]
We all know that naming things is one computer science's 2 hardest problems. Here's something for which I'm trying to find the name, if it already has one.
Let's say I have an array comprised of 2 or more equal-length arrays. This array has 4 arrays of 3 items each:
[
[1, 2, 3],
['a', 'b', 'c'],
['i', 'ii', 'iii'],
['one', 'two', 'three']
]
and I want to apply some function to get this resulting array of 3 arrays of 4 items each:
[
[1, 'a', 'i', 'one'],
[2, 'b', 'ii', 'two'],
[3, 'c', 'iii', 'three']
]
Look at the original input and imagine you're taking vertical slices across the child arrays.
Is there a language out there that can do this with a built-in function, and if so, what is the function called? Or, in general, is there a good, succinct name for this operation?
This is called transpose and is a well known operation on matrices in mathematics, see https://en.wikipedia.org/wiki/Transpose.
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.
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.