Ruby String#unpack - ruby

I have a packed string of 3 strings that is composed in a way so that I have an integer, specifying the byte length of the next item and then that item's bytes and then the next item's bytesize, etc. as if somebody did:
[a.bytesize, a, b.bytesize, b, c.bytesize, c].pack("na*na*na*")
How can I properly unpack that in a simple manner? The Perl solution to this problem was:
my($a, $b, $c) = unpack("(n/a*)3", $data)
For ruby, which apparently doesn't support '/' and parentheses in unpack, I'm using something like:
vals = []
3.times do
size = data.unpack("n").first
data.slice!(0, 2)
vals << data.unpack("a#{size}").first
data.slice!(0, size)
end
Is there an easier way to this?

IMHO it is not as easy as in PERL, but this is some solution I can suggest.
unpacked = []
a, b, c = *unpacked << data.slice!(0, data.slice!(0, 2).unpack('S>').first) \
until data.empty?

I don't see a way to do this as easily as the Perl solution (and agree it would be good to file a feature request to get that added in Ruby's pack/unpack implementation), but I could at least provide the solution in fewer lines if that helps:
vals = []
until data.empty?
vals << data.slice!(0, data.slice!(0,2).unpack('n').first.to_i).unpack("a*").first
end

If you need any serious binary data processing, there's a gem for it:
http://bindata.rubyforge.org/
I think you should use it, instead of forging unpacks un running loops.
You can of course file a feature request and wait for it to be implemented,
but I suggest you use bindata gem instead, which is a much more robust solution IMO.

Related

Is there a way to take an array and modify each individual element in a unique way using a single line of code?

I'm attempting to modify a variable string in Ruby 2.4 that contains both a number and a unit of measurement. My goal is to take the following string, round the numeric portion, and capitalize the unit portion:
my_string = "123.456789 dollars"
There are all sorts of ways that I know of to complete this operation with multiple lines of code, but I'm working with a unique interface and in a unique environment where the goal is to write this using a single line of code that is as short as possible. I know that I can do something like this:
my_array =my_string.split; my_array[0].to_f.round(2).to_s + " " + my_array[1].capitalize
...but my goal is to do away with the semicolons, use less characters, and to avoid having to call the array twice.
My idea was to do something along the lines of:
my_string.split.*do_this*(self.to_f.round(2), self.capitalize).join(" ")
Is there any way to do something like this PRIOR to version 2.5?
I'm open to other ideas as well, but again, the goal is a single line of code that is as short as possible, and the ability to modify the 2 elements using different parameters on each.
Please note: Upgrading to a newer version of Ruby is not an option. 2.4 is the version currently hard coded into the software package we’re working with.
If there are only two elements, you could use then and string interpolation:
my_string.split.then { |f, s| "#{f.to_f.round(2)} #{s.capitalize}" }
Or perhaps one of:
my_string.split.then { |f, s| sprintf("%.2f #{s.capitalize}", f) }
my_string.split.then { |f, s| sprintf('%.2f %s', f, s.capitalize) }
my_string.split.then { |f, s| "%.2f #{s.capitalize}" % f }
Or, if there could be more than two elements, you could combine map and with_index:
my_string.split.map.with_index { |e, i| i == 0 ? e.to_f.round(2) : e.capitalize }.join(' ')
I found that in Ruby 2.4 we can use the instance_eval method in at least a few different ways such as:
my_string.split.instance_eval { |f, s| "#{f.to_f.round(2)} #{s.capitalize}" }
or
my_string.split.instance_eval { |a| "#{a[0].to_f.round(2)} #{a[1].capitalize}" }
or
my_string.split.instance_eval { |f, s| [f.to_f.round(2), s.capitalize]}.join(" ")
We can also use the tap method which requires just a few more characters:
my_string.split.tap { |a| a[0..-1] = [a[0].to_f.round(2), a[1].capitalize]}.join(" ")
Or we can even use the map method by creating a nested array like in these 2 examples:
[my_string.split].map{|a| "#{a[0].to_f.round(2)} #{a[1].capitalize}"}.join
and
[my_string.split].map{|a| [a[0].to_f.round(2)," ",a[1].capitalize]}.join
And just as an added bonus, we can even do something like the following which doesn't require using a block:
(a, b = my_string.split).replace([a.to_f.round(2)," ",b.capitalize ]).join

How can I combine these two arrays of arrays in 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}

when and how to convert section of code to a method in ruby

I had a question regarding identifying all the points next to a given cell or set of cells) in a matrix (see Need a Ruby way to determine the elements of a matrix "touching" another element). Since no suitable ideas were put forth, I decided to proceed via brute force.
The code below successfully does what I sought to do. The array tmpl (template) contains a map of how to get from a given coordinate (provided by atlantis) to the 8 cells surrounding it. I then construct an array sl (shoreline) that contains all the “underwater” land touching the shoreline of atlantis by summing each element of atlantis with all elements of tmpl.
# create method to determine elements contiguous to atlantis
require 'matrix'
atlantis = [[2,3],[3,4]]
tmpl = [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]]
ln = 0
sl = []
while ln < atlantis.length
n = 0
tsl = []
while n < 8
tsl[n] = [atlantis[ln], tmpl[n]].transpose.map { |x| x.reduce(:+) }
n = n+ 1
end
sl = sl + tsl
ln = ln + 1
end
sl = sl - atlantis
sl.uniq!
sl.to_a.each { |r| puts r.inspect }
But I have a problem (one of many remaining) in that I still need 2 levels of loops above what’s shown here (one to keep adding land to atlantis until it reaches a set size and another to make additional islands, Bermuda, Catalina, etc.) and already this is becoming difficult to read and follow. My vague understanding of object oriented programming suggests that this cold be improved by turning some of these loops into methods. However, I learned to program 35 years ago in basic and am struggling to learn Ruby as it is. So my requests are:
Is in fact better to turn these into methods?
If so, would anyone be willing to show me how that’s done by changing something into an method?
What do you do when you add additional levels and discover you need to change something in a lower method as a result? (e.g, after figuring out the simple case of how to create sl with just one value in atlantis, I had to go back and rework it for longer values.)
I hoping by asking the question in this way, it becomes something also useful to other nubies.
BTW, this bit .transpose.map { |x| x.reduce(:+) } I found on Stack Overflow (after hours of trying to do it ‘cause it should be simple and if I couldn’t do it I must be missing something obvious. Yeah, I bet you know too.) lets you add two arrays element by element and I have no idea how it works.)
already this is becoming difficult to read and follow
One way of making it less difficult to read and follow is to try to make the code "self document", by using readable variable names and Ruby idioms to reduce the clutter.
A quick refactor of your code gives this:
require 'matrix'
atlantis = [[2,3],[3,4]]
template = [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]]
shoreline = []
atlantis.each do |atlantum|
shoreline += template.inject([]) do |memo, element|
memo << [atlantum, element].transpose.map { |x| x.reduce(:+) }
memo
end
end
shoreline = shoreline - atlantis
shoreline.uniq!
shoreline.each { |r| puts r.inspect }
The main processing block is half the size, and (hopefully) more readable, and from here you can use the extract method refactor to tidy it further if you still need/want to.

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]

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