How can I combine these two arrays of arrays in Ruby - ruby

I have two arrays that have the same length and the same format.I am looking for the shortest way to do something like this:
[[:todo],[],['text']].some_operation([[],[:low],[]])
->
[[:todo],[:low],['text']]
as I want the duplicates to be removed:
[[:todo],[],['text']].some_operation([[:todo],[:low],['text','more']])
->
[[:todo],[:low],['text','more']]

If you have the values in a and b, then
a.zip(b).map { |aa, bb| (aa + bb).uniq }
If you really want to put it onto the Array class, you can either monkey-patch it (not really recommended, especially for something this specific), or refine it (new, won't work in older versions).

You could also do
a.each_with_index.map {|aa, i| aa | b[i] }

a.zip(b).map{|x|x.flatten.uniq}

Related

What is the most elegant way to sort ranges in ruby

I need to sort a table of objects of type Rangeby their start point. For that I have the following code which works fine:
ranges = #ranges.sort do |a,b|
(a.min) <=> (b.min)
end
I was just wondering if there was a shorter and elegant way to do the same thing.
How about:
ranges = #ranges.sort_by(&:min)
Or if you actually mean the starting point rather than the minimum, since ranges such as (5..3) can exist:
ranges = #ranges.sort_by(&:first)

ruby > sort images inside directory with multiple conditions

I need to sort the images present inside some directory with the following order:
00a.jpg
00b.jpg
00c.jpg
...
00x.jpg
00y.jpg
00z.jpg
0aa.jpg
0bb.jpg
0cc.jpg
...
0xx.jpg
0yy.jpg
0zz.jpg
001.jpg
002.jpg
003.jpg
...
097.jpg
098.jpg
099.jpg
100.jpg
101.jpg
102.jpg
But I am not getting any logic to put inside my sort_by? Can anyone has any idea what logic would be best suited for sorting all images in the above mentioned order..
I am expecting something like this :
Dir.entries('.').sort_by { |x| ?? }
Thanks,
Dean
Your requested sort order is not apparent, so I'm going to assume that you want all the images which contain a letter to be before those with numbers only.
For this logic, you can return an array from sort_by, which be evaluated in order - firs item first, second one if the first is tied, etc.
In this example this would be something like:
jpgs.sort_by { |j| [j[/.*[a-z].*\.jpg/] ? 0 : 1, j] }
The first item in the array returned answers the question of whether the image name contains a letter before the extension, and if it does returns a smaller number than if it doesn't. This assures us that images with letters in their names will be before images with only numbers in their names.
Will result in this order:
[
"00a.jpg",
"00b.jpg",
"00c.jpg",
"00x.jpg",
"00y.jpg",
"00z.jpg",
"0aa.jpg",
"0bb.jpg",
"0cc.jpg",
"0xx.jpg",
"0yy.jpg",
"0zz.jpg",
...,
"001.jpg",
"002.jpg",
"003.jpg",
"097.jpg",
"098.jpg",
"099.jpg",
"100.jpg",
"101.jpg",
"102.jpg"
]
I would use:
Dir.entries('.').sort { |a,b| a.split('.').first <=> b.split('.').first }
I think it may be faster than regexp option. Also, its simplier and easier to customize (due using 2 iterators and comparator).

Ruby: how to find the next match in an array

I have to search an item in an array and return the value of the next item. Example:
a = ['abc.df','-f','test.h']
i = a.find_index{|x| x=~/-f/}
puts a[i+1]
Is there any better way other than working with index?
A classical functional approach uses no indexes (xs.each_cons(2) -> pairwise combinations of xs):
xs = ['abc.df', '-f', 'test.h']
(xs.each_cons(2).detect { |x, y| x =~ /-f/ } || []).last
#=> "test.h"
Using Enumerable#map_detect simplifies it a litte bit more:
xs.each_cons(2).map_detect { |x, y| y if x =~ /-f/ }
#=> "test.h"
The reason something like array.find{something}.next doesn't exist is that it's an array rather than a linked list. Each item is just it's own value; it doesn't have a concept of "the item after me".
#tokland gives a good solution by iterating over the array with each pair of consecutive items, so that when the first item matches, you have your second item handy. There are strong arguments to be made for the functional style, to be sure. Your version is shorter, though, and I'd argue that yours is also more quickly and easily understood at a glance.
If the issue is that you're using it a lot and want something cleaner and more to the point, then of course you could just add it as a singleton method to a:
def a.find_after(&test)
self[find_index(&test).next]
end
Then
a.find_after{|x| x=~/-f/}
is a clear way to find the next item after the first match.
All of that said, I think #BenjaminCox makes the best point about what appears to be your actual goal. If you're parsing command line options, there are libraries that do that well.
I don't know of a cleaner way to do that specific operation. However, it sure looks like you're trying to parse command-line arguments. If so, I'd recommend using the built-in OptionParser module - it'll save a ton of time and hair-pulling trying to parse them yourself.
This article explains how it works.
Your solution working with indexes is fine, as others have commented. You could use Enumerable#drop_while to get an array from your match on and take the second element of that:
a = ['abc.df','-f','test.h']
f_arg = a.drop_while { |e| e !~ /-f/ }[1]

Combine array of array into all possible combinations, forward only, in Ruby

I have an array of arrays, like so:
[['1','2'],['a','b'],['x','y']]
I need to combine those arrays into a string containing all possible combinations of all three sets, forward only. I have seen lots of examples of all possible combinations of the sets in any order, that is not what I want. For example, I do not want any of the elements in the first set to come after the second set, or any in the third set to come before the first, or second, and so on. So, for the above example, the output would be:
['1ax', '1ay', '1bx', '1by', '2ax', '2ay', '2bx', '2by']
The number of arrays, and length of each set is dynamic.
Does anybody know how to solve this in Ruby?
Know your Array#product:
a = [['1','2'],['a','b'],['x','y']]
a.first.product(*a[1..-1]).map(&:join)
Solved using a recursive, so-called "Dynamic Programming" approach:
For n-arrays, combine the entries of the first array with each result on the remaining (n-1) arrays
For a single array, the answer is just that array
In code:
def variations(a)
first = a.first
if a.length==1 then
first
else
rest = variations(a[1..-1])
first.map{ |x| rest.map{ |y| "#{x}#{y}" } }.flatten
end
end
p variations([['1','2'],['a','b'],['x','y']])
#=> ["1ax", "1ay", "1bx", "1by", "2ax", "2ay", "2bx", "2by"]
puts variations([%w[a b],%w[M N],['-'],%w[x y z],%w[0 1 2]]).join(' ')
#=> aM-x0 aM-x1 aM-x2 aM-y0 aM-y1 aM-y2 aM-z0 aM-z1 aM-z2 aN-x0 aN-x1 aN-x2
#=> aN-y0 aN-y1 aN-y2 aN-z0 aN-z1 aN-z2 bM-x0 bM-x1 bM-x2 bM-y0 bM-y1 bM-y2
#=> bM-z0 bM-z1 bM-z2 bN-x0 bN-x1 bN-x2 bN-y0 bN-y1 bN-y2 bN-z0 bN-z1 bN-z2
You could also reverse the logic, and with care you should be able to implement this non-recursively. But the recursive answer is rather straightforward. :)
Pure, reduce with product:
a = [['1','2'],['a','b'],['x','y']]
a.reduce() { |acc, n| acc.product(n).map(&:flatten) }.map(&:join)
# => ["1ax", "1ay", "1bx", "1by", "2ax", "2ay", "2bx", "2by"]

Easiest way to convert "a/b/c" to ["a/b/c", "a/b", "a"]

In Ruby, I'd like to convert a slash-separate String such as "foo/bar/baz" into ["foo/bar/baz", "foo/bar", "foo"]. I already have solutions a few lines long; I'm looking for an elegant one-liner. It also needs to work for arbitrary numbers of segments (0 and up).
"foo/bar/baz".enum_for(:scan, %r{/|$}).map {Regexp.last_match.pre_match}
The highest voted answer works, but here is a slightly shorter way to do it that I think will be more readable for those not familiar with all the features used there:
a=[]; s.scan(/\/|$/){a << $`}
The result is stored in a:
> s = 'abc/def/ghi'
> a=[]; s.scan(/\/|$/){a << $`}
> a
["abc", "abc/def", "abc/def/ghi"]
If the order is important, you can reverse the array or use unshift instead of <<.
Thanks to dkubb, and to the OP for the improvements to this answer.
Not quite as efficient as the chosen answer, and gives [] when given an empty string, rather than [""], but its a real one liner :P
s.split('/').inject([]) { |a,d| a.unshift( [a.first,d].compact.join('/') ) }
->(l,s,z){
( t = s[/#{z}.[^\/]*/] ) && [l[l,s,t], t]
}.tap{ |l|
break l[l,'a/b/c','']
}.flatten.compact

Resources