Sort Array of Arrays by length with tiebreaker - ruby

I have an Array of Arrays that I want to sort by longest length to shortest. I achieved this easily enough with a sort_by
> a = [ [1, 2, 9],
[4, 5, 6, 7],
[1, 2, 3] ]
> a.sort_by(&:length).reverse # or a.sort_by {|e| e.length}.reverse
=> [[4, 5, 6, 7], [1, 2, 3], [1, 2, 9]]
What I want, however is to have a sort of tie-breaker for lists of equal length. If two lists' lengths are equal, the list whose last entry is greater should come first. So in the above, [1, 2, 9] and [1, 2, 3] should be switched.
I don't care abouth the case where two lists have both equal length and equal last element, they can be in whatever order if that occurs. I don't know if/how I can acheive this with ruby built-in sorting.

You can still do this with sort_by, you just need to realize that Ruby arrays compare element-by-element:
ary <=> other_ary → -1, 0, +1 or nil
[...]
Each object in each array is compared (using the <=> operator).
Arrays are compared in an “element-wise” manner; the first two elements that are not equal will determine the return value for the whole comparison.
That means that you can use arrays as the sort_by key, then throw in a bit of integer negation to reverse the sort order and you get:
a.sort_by { |e| [-e.length, -e.last] }
That will give you the [[4, 5, 6, 7], [1, 2, 9], [1, 2, 3]] that you're looking for.
If you're not using numbers so the "negation to reverse the order" trick won't work, then use Shaunak's sort approach.

There you go :
a = [ [1, 2, 9],[4, 5, 6, 7],[1, 2, 3] ]
a.sort { |a, b| (b.count <=> a.count) == 0 ? (b.last <=> a.last): (b.count <=> a.count) }
That should give you:
[[4, 5, 6, 7], [1, 2, 9], [1, 2, 3]]
How this works: we pass a block to sort function, which first checks if the array length is same, if not it continues to check for last element.

You could use
a.sort_by {|i| [i.length, i.last] }.reverse
# => [[4, 5, 6, 7], [1, 2, 9], [1, 2, 3]]

Related

Getting different output from manual vs. programmatic arrays

I’m getting some weird results implementing cyclic permutation on the children of a multidimensional array.
When I manually define the array e.g.
arr = [
[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]
]
the output is different from when I obtain that same array by calling a method that builds it.
I’ve compared the manual array to the generated version and they’re exactly the same (class and values, etc).
I tried writing the same algorithm in JS and encountered the same issue.
Any idea what might be going on?
def Build_array(child_arr, n)
#Creates larger array with arr as element, n times over. For example Build_array([1,2,3], 3) returns [[1,2,3], [1,2,3], [1,2,3]]
parent_arr = Array.new(4)
0.upto(n) do |i|
parent_arr[i] = child_arr
end
return parent_arr
end
def Cylce_child(arr, steps_tocycle)
# example: Cylce_child([1, 2, 3, 4, 5], 2) returns [4, 5, 1, 2, 3]
0.upto(steps_tocycle - 1) do |i|
x = arr.pop()
arr.unshift(x)
end
return arr
end
def Permute_array(parent_array, x, y, z)
#x, y, z = number of steps to cycle each child array
parent_array[0] = Cylce_child(parent_array[0], x)
parent_array[1] = Cylce_child(parent_array[1], y)
parent_array[2] = Cylce_child(parent_array[2], z)
return parent_array
end
arr = Build_array([1, 2, 3, 4, 5], 4)
# arr = [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]]
puts "#{Permute_array(arr, 1, 2, 3)}"
# Line 34: When arr = Build_array([1, 2, 3, 4, 5], 4)
# Result (WRONG):
# [[5, 1, 2, 3, 4], [5, 1, 2, 3, 4], [5, 1, 2, 3, 4], [5, 1, 2, 3, 4]]
#
# Line 5: When arr = [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, # 2, 3, 4, 5]]
# Result (CORRECT):
# [[5, 1, 2, 3, 4], [4, 5, 1, 2, 3], [3, 4, 5, 1, 2], [1, 2, 3, 4, 5]]
#
The problem is in the way you build the array.
This line:
parent_arr[i] = child_arr
does not put in parent_arr[i] a copy of child_arr but a reference to it.
This means your initial array contains four references to the same child array. Later on, when the code changes parent_arr[0], it changes the same array that child_arr was referring to in the build method. And that array is also parent_arr[1] and parrent_arr[2] and so on.
A simple solution to the problem is to put in parent_arr[i] a copy of child_arr:
parent_arr[i] = Array.new(child_arr)
I see where the bug was. Added the clone method to line 8 so that it now reads:
parent_arr[i] = child_arr.clone
#Old: parent_arr[i] = child_arr
Thanks Robin, for pointing me in the right direction.
This is a fairly common mistake to make in Ruby since arrays do not contain objects per-se, but object references, which are effectively pointers to a dynamically allocated object, not the object itself.
That means this code:
Array.new(4, [ ])
Will yield an array containing four identical references to the same object, that object being the second argument.
To see what happens:
Array.new(4, [ ]).map(&:object_id)
# => => [70127689565700, 70127689565700, 70127689565700, 70127689565700]
Notice four identical object IDs. All the more obvious if you call uniq on that.
To fix this you must supply a block that yields a different object each time:
Array.new(4) { [ ] }.map(&:object_id)
# => => [70127689538260, 70127689538240, 70127689538220, 70127689538200]
Now adding to one element does not impact the others.
That being said, there's a lot of issues in your code that can be resolved by employing Ruby as it was intended (e.g. more "idiomatic" code):
def build_array(child_arr, n)
# Duplicate the object given each time to avoid referencing the same thing
# N times. Each `dup` object is independent.
Array.new(4) do
child_arr.dup
end
end
def cycle_child(arr, steps_tocycle)
# Ruby has a rotate method built-in
arr.rotate(steps_tocycle)
end
# Using varargs (*args) you can just loop over how many positions were given dynamically
def permute_array(parent_array, *args)
# Zip is great for working with two arrays in parallel, they get "zippered" together.
# Also map is what you use for transforming one array into another in a 1:1 mapping
args.zip(parent_array).map do |a, p|
# Rotate each element the right number of positions
cycle_child(p, -a)
end
end
arr = build_array([1, 2, 3, 4, 5], 4)
# => [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]]
puts "#{permute_array(arr, 1, 2, 3)}"
# => [[5, 1, 2, 3, 4], [4, 5, 1, 2, 3], [3, 4, 5, 1, 2]]
A lot of these methods boil down to some very simple Ruby so they're not especially useful now, but this adapts the code as directly as possible for educational purposes.

How to generate partially repeated permutations in ruby?

I have a range of numbers R = (1..n). I also have another character 'a'. I want to generate strings of length L (L > n + 2) that have all the numbers in the same order, but go through every repeated permutation of 'a' to fill the length L. For example, if n = 3, and L = 7, then some valid strings would be :
"123aaaa",
"1a23aaa",
"1aa2a3a",
"aaaa123"
while the following strings would be invalid:
"213aaaa", # invalid, because 1,2,3 are not in order
"123a", #invalid, because length < L
"1123aaa", # invalid because a number is repeated
I am currently doing this, which is way too inefficient:
n = 3
L = 7
all_terms = (1..n).to_a + Array.new(L - n, 'a')
all_terms.permutation.each do |permut|
if(valid_permut? permut) # checks if numbers are in their natural order
puts permut.join
end
end
How do I directly generate valid strings more efficiently?
The problem is equivalent to: select n elements from index 0 to L - 1, fill these with 1 to n accordingly, and fill the rest with some constant character.
In your example, it's taking 3 elements from 0..6:
(0..6).to_a.combination(3).to_a
=> [[0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 1, 5], [0, 1, 6], [0, 2, 3], [0, 2, 4],
[0, 2, 5], [0, 2, 6], [0, 3, 4], [0, 3, 5], [0, 3, 6], [0, 4, 5], [0, 4, 6], [0, 5, 6],
[1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 2, 6], [1, 3, 4], [1, 3, 5], [1, 3, 6], [1, 4, 5],
[1, 4, 6], [1, 5, 6], [2, 3, 4], [2, 3, 5], [2, 3, 6], [2, 4, 5], [2, 4, 6], [2, 5, 6],
[3, 4, 5], [3, 4, 6], [3, 5, 6], [4, 5, 6]]
Every subarray here represents a possible result. For example, [0, 2, 3] corresponds to '0a12aaa', [3, 5, 6] corresponds to 'aaa0a12', etc. The code for this conversion is straight-forward.
You can model this as all possible interleavings of two strings, where relative order of the input elements is preserved. Here's a recursive solution. It works by choosing an element from one list, and prepending it to all possible subproblems, then doing it again where an element is chosen from the second list instead, and combining the two solution sets at the end.
# Returns an array of all possible interleaving of two strings
# Maintains relative order of each character of the input strings
def interleave_strings_all(a1, a2)
# Handle base case where at least one input string is empty
return [a1 + a2] if a1.empty? || a2.empty?
# Place element of first string, and prepend to all subproblems
set1 = interleave_strings_all(a1[1..-1], a2).map{|x| a1[0] + x}
# Place element of second string and prepend to all subproblems
set2 = interleave_strings_all(a1, a2[1..-1]).map{|x| a2[0] + x}
# Combine solutions of subproblems into overall problem
return set1.concat(set2)
end
if __FILE__ == $0 then
l = 5
n = 3
a1 = (1..n).to_a.map{|x| x.to_s}.join()
a2 = 'a' * (l - n)
puts interleave_strings_all(a1, a2)
end
The output is:
123aa
12a3a
12aa3
1a23a
1a2a3
1aa23
a123a
a12a3
a1a23
aa123

Inserting elements into new array and then deleting from old array, some elements getting ignored

I'm trying to remove pairs of the smallest and largest elements from an Array and store them in a second one. Is there a better way to do this or a Ruby method I don't know about that could accomplish something like this?
Here's my code:
nums = [1, 2, 3, 4, 5, 6]
pairs = []; for n in nums
pairs << [n, nums.last]
nums.delete nums.last
nums.delete n
end
Current result:
nums
#=> [2, 4]
pairs
#=> [[1, 6], [3, 5]]
Expected result:
nums
#=> []
pairs
#=> [[1, 6], [2, 5], [3, 4]]
Assuming nums is sorted and can be modified, I like this way because it has a mechanical feel about it:
pairs = (nums.size/2).times.map { [nums.shift, nums.pop] }
#=> [[1, 6], [2, 5], [3, 4]]
nums
#=> []
I see #Drenmi has the same idea of using shift and pop.
If you don't want to modify nums, you could of course operate on a copy.
Enumerating over an Array while deleting it's content is generally not advisible. Here's an alternative solution:
nums = *(1..6)
#=> [1, 2, 3, 4, 5, 6]
pairs = []
#=> []
until nums.size < 2 do
pairs << [nums.shift, nums.pop]
end
pairs
#=> [[1, 6], [2, 5], [3, 4]]

How can I interweave items from two arrays?

How can I go from this:
for number in [1,2] do
puts 1+number
puts 2+number
puts 3+number
end
which will return 2,3,4 then 3,4,5 -> 2,3,4,3,4,5. This is just an example, and clearly not the real use.
Instead, I would like it to return 2,3 3,4 4,5 -> 2,3,3,4,4,5. I would like each of the puts to be iterated for each of the possible values of number; In this case 1 and 2 are the two possible values of 'number', before moving on to the next puts.
One way to do this is to create two lists, [2,3,4] and [3,4,5] and then use the zip method to combine them like [2,3,4].zip([3,4,5]) -> [2,3,3,4,4,5].
zip is good. You should also look at each_cons:
1.9.2p290 :006 > [2,3,4].each_cons(2).to_a
=> [[2, 3], [3, 4]]
1.9.2p290 :007 > [2,3,4,5,6].each_cons(2).to_a
=> [[2, 3], [3, 4], [4, 5], [5, 6]]
1.9.2p290 :008 > [2,3,4,5,6].each_cons(3).to_a
=> [[2, 3, 4], [3, 4, 5], [4, 5, 6]]
Because each_cons returns an Enumerator, you can use a block with it, as mentioned in the documentation for it, or convert it to an array using to_a like I did above. That returns the array of arrays, which can be flattened to get a single array:
[2,3,4,5].each_cons(2).to_a.flatten
=> [2, 3, 3, 4, 4, 5]
From the ri docs:
Iterates the given block for each array of consecutive elements. If no
block is given, returns an enumerator.
e.g.:
(1..10).each_cons(3) {|a| p a}
# outputs below
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]
[8, 9, 10]
Maybe not the most readable code but you could use inject on the first range to create an array based on the summed up second range.
(1..3).inject([]){|m,n| (1..2).each{|i| m<<n+i }; m }
=> [2, 3, 3, 4, 4, 5]
This might be a little more readable
res=[]
(1..3).each{|r1| (1..2).each{|r2| res<<r1+r2 } }
[1, 2, 3].each { |i| [1, 2].each { |y| puts i + y } }

Subract values of 2 arrays and get a new array with subtracted values

Basically, I want to do this:
[1,2,3,4,5].some_method([1,5,8,7,8])
=> [0,-3,-5,-3,-3]
What is the best way to do this in Ruby?
[1,2,3,4,5].zip([1,5,8,7,8]).map { |a, b| a - b }
p [1,2,3,4,5].zip([1,5,8,7,8]).map{|el| el.first-el.last}
#=> [0, -3, -5, -3, -3]
.zip combines both arrays like [[1, 1], [2, 5], [3, 8], [4, 7], [5, 8]]. With .map there isnt much work left for the block - just subtract the 2 values.
[1,2,3,4,5].zip([1,5,8,7,8]).map{|x|x[0]-x[-1]}
or the usual array iteration
0.upto(a.size-1).map{|x| a[x]-b[x] }

Resources