Combine array items uniquely regarding order and duplication - arrays

I have an array:
a = ['a', 'b', 'a', 'f']
and now want to get possible combinations of two, but the order of the letters is not important. So the result should be:
[['a', 'a'],['a', 'b'],['a', 'f'],['b', 'f']]
I tried
a.product(a)
But that of course returned ALL possible combinations, not regarding that the order is not important and that double results should be removed:
[["a", "a"], ["a", "b"], ["a", "a"], ["a", "f"], ["b", "a"], ["b", "b"], ["b", "a"], ["b", "f"], ["a", "a"], ["a", "b"], ["a", "a"], ["a", "f"], ["f", "a"], ["f", "b"], ["f", "a"], ["f", "f"]]
I could solve these issues in loops, but I wonder if there is some ruby array/hash magic that could help me out.

You can use combination and uniq with sort:
a.combination(2).to_a.uniq(&:sort)
# => [["a", "b"], ["a", "a"], ["a", "f"], ["b", "f"]]
Demonstration

Related

Has Array#sort become stable?

From the docs
"When the block returns zero, the order for a and b is indeterminate, and may be unstable:
a = 'abcde'.split('').shuffle
a # => ["e", "b", "d", "a", "c"]
a1 = a.sort {|a, b| 0 }
a1 # => ["c", "e", "b", "d", "a"]
"
If I run this code the sort appears to be stable:
puts RUBY_VERSION # => 3.0.3
p a = 'abcde'.split('').shuffle # => ["d", "e", "c", "b", "a"]
p a1 = a.sort {|a, b| 0 } # => ["d", "e", "c", "b", "a"]
Has Ruby sneakily got a stable sort?

Create all possibly repeated combinations of letters

I want to create an array of possibly repeated combinations of n elements from the letters A to F. Each combination should be an array. For n = 4 for example, the result should be:
[[A, A, A, A], [A, A, A, B], [A, A, A, C], ..., [F, F, F, F]]
without significance in order among the elements ([A, A, E, E] and [E, E, A, A] would be the same).
I tried it with:
('A'..'F').to_a.combination(4).to_a
but that didn't work. I guess the task is more complicated.
Try repeated_combination:
('A'..'F').to_a.repeated_combination(4).to_a
Example:
> ('A'..'C').to_a.repeated_combination(4).to_a
=> [["A", "A", "A", "A"], ["A", "A", "A", "B"], ["A", "A", "A", "C"], ["A", "A", "B", "B"], ["A", "A", "B", "C"], ["A", "A", "C", "C"], ["A", "B", "B", "B"], ["A", "B", "B", "C"], ["A", "B", "C", "C"], ["A", "C", "C", "C"], ["B", "B", "B", "B"], ["B", "B", "B", "C"], ["B", "B", "C", "C"], ["B", "C", "C", "C"], ["C", "C", "C", "C"]]

Find ungrouped elements in array using Regex in Ruby

By "upgrouped" I mean elements that are uniq to its left and its right elements.
Example:
arr = ["a", "a", "a", "b", "a", "c", "c", "d", "b"]
We can group arr like this [["a", 3], ["b, 1], ["a", 1], ["c", 2], ["d", 1], ["b", 1]]. Let's say once a letter is "grouped" we are not going to pick any other ungrouped instances of that letter.
So our final answer would be arr = ["b", "d"]
What could be a great option to replace the "regex" part in arr.select{|chr| chr(/\regex/)} (if there is no other ways)?
Edit: added "b" in the end of array. arr.count("b") == 2, but since "b" is ungrouped, it is still part of the return array.
You could do it in two steps without regex:
banned = arr.chunk(&:itself).select { |e| e.last.size > 1 }.map(&:first)
#=> ["a", "c"]
arr.uniq - banned
#=> ["b", "d"]
Basically first we get the list of elements that are grouped and need to be rejected, and then remove those elements from original array.
Here is a step by step example (for banned list):
arr.chunk(&:itself).to_a #=> [["a", ["a", "a", "a"]], ["b", ["b"]], ["a", ["a"]], ["c", ["c", "c"]], ["d", ["d"]], ["b", ["b"]]]
.select { |e| e.last.size > 1 } #=> [["a", ["a", "a", "a"]], ["c", ["c", "c"]]]
.map(&:first) #=> ["a", "c"]
Notice that to_a is added only to show the content of the Enumerable that results from chunk, but it is not used in the final solution since map can be executed on any Enumerable object.
Another option to get banned list could be using each_with_object:
arr.chunk(&:itself).each_with_object([]) { |elem, result| result << elem[0] if elem[1].size > 1 }
#=> ["a", "c"]
This will only iterate arr twice (first option will iterate it 3 times).
I don't know how I'd use a regular expression for this problem, but the method Enumerable#each_cons can be used to advantage.
Let grouped and ungrouped respectively be arrays of elements of a given array arr, where grouped are the grouped elements and ungrouped are the ungrouped elements. Every element is grouped or ungrouped.
My understanding of the question is that we wish to construct an array
(ungrouped - grouped).uniq
where Array#- is array difference.
It is clear that, for i=1,..., arr.size-2, arr[i] is ungrouped if arr[i-1] != arr[i] && arr[i] != arr[i+1]. It's not evident, however, how one determines whether the first and last elements of an array are ungrouped. The definition states that, to be ungrouped, the element must differ from both of it's adjacent elements. Since the first and last elements only have one adjacent element, it follows that the first and last elements are never ungrouped.
The alternative interpretation is that an element is ungrouped if it differs from all of its adjacent elements (that being one for the first and last and two for all others). I will consider these two interpretations separately. For both,
arr = [["a", 3], ["b", 1], ["a", 1], ["c", 2], ["d", 1], "b"]
Ungrouped elements must differ from both of their adjacent elements
ungrouped, grouped = [arr.first, *arr, arr.last].each_cons(3).
with_object([[],[]]) do |(p,c,n), (ungrouped, grouped)|
(c==p || c==n ? grouped : ungrouped) << c
end
(ungrouped - grouped).uniq
# => ["d"]
Ungrouped elements must differ from all of their adjacent elements
ungrouped, grouped = [nil, *arr, nil].each_cons(3).
with_object([[],[]]) do |(p,c,n), (ungrouped, grouped)|
(c==p || c==n ? grouped : ungrouped) << c
end
(ungrouped - grouped).uniq
# => ["b", "d"]
This assumes that no elements of arr equal nil.
The steps for this second interpretation are as follows.
a = [nil, *arr, nil]
#=> [nil, "a", "a", "a", "b", "a", "c", "c", "d", "b", nil]
b = a.each_cons(3)
#=> #<Enumerator: [nil, "a", "a",..., "d", "b", nil]:each_cons(3)>
We can see the elements that will be generated by this enumerator by converting it to an array.
b.to_a
#=> [[nil, "a", "a"], ["a", "a", "a"], ["a", "a", "b"], ["a", "b", "a"],
# ["b", "a", "c"], ["a", "c", "c"], ["c", "c", "d"], ["c", "d", "b"],
# ["d", "b", nil]]
Continuing,
d = b.with_object([[],[]])
#=> #<Enumerator: #<Enumerator: [nil, "a", "a",..., "d", "b", nil]:
# each_cons(3)>:with_object([[], []])>
d.to_a
#=> [[[nil, "a", "a"], [[], []]], [["a", "a", "a"], [[], []]],
# [["a", "a", "b"], [[], []]], [["a", "b", "a"], [[], []]],
# [["b", "a", "c"], [[], []]], [["a", "c", "c"], [[], []]],
# [["c", "c", "d"], [[], []]], [["c", "d", "b"], [[], []]],
# [["d", "b", nil], [[], []]]]
If one examines the return value for the construction of this enumerator, you can see it can be thought of as a compound enumerator.
ungrouped, grouped = d.each do |(p,c,n), (ungrouped, grouped)|
(c==p || c==n ? grouped : ungrouped) << c
end
#=> [["b", "a", "d", "b"], ["a", "a", "a", "c", "c"]]
ungrouped
#=> ["b", "a", "d", "b"]
grouped
#=> ["a", "a", "a", "c", "c"]]
e = ungrouped - grouped
#=> ["b", "d", "b"]
e.uniq
#=> ["b", "d"]
Let's take a closer look at the calculation of ungrouped and grouped. The first element of the enumerator d is passed to the block and the block variables are assigned values.
(p,c,n), (ungrouped, grouped) = d.next
#=> [[nil, "a", "a"], [["b", "a", "d", "b"], ["a", "a", "a", "c", "c"]]]
p
#=> nil
c
#=> "a"
n
#=> "a"
ungrouped
#=> []
grouped
#=> []
c==p || c==n
#=> "a"==nil || "a"=="a"
#=> true
Therefore,
grouped << c
#=> ["a"]
The remaining calculations are similar, as are the calculations under the first assumption concerning the first and last elements of the array.

How to extract an element of an array and insert it in another while respecting its original index

How to combine 2 arrays like this
a = ["x","y","z"]
b = [["a","b"],["c","d"],["e","f"]]
expected output:
[["a","b","x" ],["c","d","y"],["e","f","z"]]
Is there any inbuilt method?
There is. You can use Array#zip in conjunction with Array#flatten:
b.zip(a).map(&:flatten)
#=> [["a", "b", "x"], ["c", "d", "y"], ["e", "f", "z"]]
another way is:
[b, a].transpose.map(&:flatten)
#=> [["a", "b", "x"], ["c", "d", "y"], ["e", "f", "z"]]
:)
Here is one more way to do this:
a = ["x","y","z"]
b = [["a","b"],["c","d"],["e","f"]]
b.map.with_index {|arr, idx| arr << a[idx]}
#=> [["a", "b", "x"], ["c", "d", "y"], ["e", "f", "z"]]
enum = a.to_enum
b.map { |arr| arr << enum.next }
#=> [["a", "b", "x"], ["c", "d", "y"], ["e", "f", "z"]]

Convert 2d array into 1d (Ruby)

The code below currently pushes a copy of #startyear into #new. I need to convert this into 1 single array, any ideas?
Forums didn't have much
startyear = [["a", "b", "z"], ["c", "d"], ["e", "f"], ["g", "h", "i", "j"]]
new = []
startyear.each do |n| #.transpose here?
puts "looping #{n}"
new.push(n)
#n.join is needed somewhere
puts "#{startyear.length} is the length of startyear"
break if startyear.length == startyear.length[4]
end
puts "your new array is : #{new}"
You can use Array#flatten:
startyear = [["a", "b", "z"], ["c", "d"], ["e", "f"], ["g", "h", "i", "j"]]
flattened = startyear.flatten
# flattened is now ["a", "b", "z", "c", "d", "e", "f", "g", "h", "i", "j"]
Array#flatten is the obvious method to use here, but as is generally the case with Ruby, there are alternatives. Here are two.
Use Enumerable#flat_map and Object#itself
startyear.flat_map(&:itself)
#=> ["a", "b", "z", "c", "d", "e", "f", "g", "h", "i", "j"]
itself was introduced in Ruby v2.2. For earlier versions, use:
startyear.flat_map { |a| a }
Use Enumerable#reduce (aka inject)
startyear.reduce(:+)
#=> ["a", "b", "z", "c", "d", "e", "f", "g", "h", "i", "j"]

Resources