Manipulating a byte array - ruby

I have a simple byte array
["\x01\x01\x04\x00"]
I'm not sure how I can alter just the second value in the string (I know the array only has one item), whilst still keeping the object a byte array.
Something along these lines:
["\x01#{ARGV[0]}\x04\x00"]

I think the secret is that you have a nested array:
irb(main):002:0> x = ["\x01\x02\x01\x01"]
=> ["\001\002\001\001"]
You can index it:
irb(main):003:0> x[0][1]
=> 2
You can assign into it:
irb(main):004:0> x[0][1] = "\x05"
=> "\005"
And it looks like what you want:
irb(main):005:0> x
=> ["\001\005\001\001"]

use each_byte string method:
$ irb --simple-prompt
>> str = "\x01\x01\x04\x00"
=> "\001\001\004\000"
>> str.each_byte {|byte| puts byte}
1
1
4
0
=> "\001\001\004\000"
>>

It might be less confusing to get rid of the array wrapper.
a = ["\x01\x01\x04\x00"]
a = a[0]
a[1] = ...
You can always put the string back inside an array:
a = [a]
Also, technically, it's not a "byte array", it's a single-element Array, with a String object. (And for that matter, strictly speaking, Ruby doesn't really have Array of Type; all Ruby arrays are something like Array of Object elsewhere.)

Related

Why Does the Script Throw an "sort_by: undefined method" error?

I am going over the basics of Ruby, and according to some docs I mistakenly believe this should work:
#Write a function that sorts the keys in a hash by the length of the key as a string.
hash = { abc: 'hello', 'another_key' => 123, 4567 => 'third' }
array = hash.keys.to_s
sorted_array = array.sort_by(&:length)
puts sorted_array
But I'm getting the output:
sort_by: undefined method `sort_by' for "[\"abc\", \"another_key\", 4567]":String
hash = { abc: 'hello', 'another_key' => 123, 4567 => 'third' }
array = hash.keys.to_s
Ah no. You end up with one string (the string representation of the entire array of keys). Try this:
p sorted_array = hash.sort_by{|key, value| key.to_s.length }
which takes every key-value pair, converts the key to a string and returns its size, and sorts on that.
You're calling hash.keys.to_s - #to_s produces a String representation of any given object. Strings are not Enumerables, and thus do not have the #sort_by method.
Instead, you probably want array = hash.keys, which will return an array of the Hash's keys.

Pass arguments by reference to a block with the splat operator

It seems that the arguments are copied when using the splat operator to pass arguments to a block by reference.
I have this:
def method
a = [1,2,3]
yield(*a)
p a
end
method {|x,y,z| z = 0}
#=> this puts and returns [1, 2, 3] (didn't modified the third argument)
How can I pass these arguments by reference? It seems to work if I pass the array directly, but the splat operator would be much more practical, intuitive and maintainable here.
In Ruby when you write x = value you are creating a new local variable x whether it existed previously or not (if it existed the name is simply rebound and the original value remains untouched). So you won't be able to change a variable in-place this way.
Integers are immutable. So if you send an integer there is no way you can change its value. Note that you can change mutable objects (strings, hashes, arrays, ...):
def method
a = [1, 2, "hello"]
yield(*a)
p a
end
method { |x,y,z| z[1] = 'u' }
# [1, 2, "hullo"]
Note: I've tried to answer your question, now my opinion: updating arguments in methods or blocks leads to buggy code (you have no referential transparency anymore). Return the new value and let the caller update the variable itself if so inclined.
The problem here is the = sign. It makes the local variable z be assigned to another object.
Take this example with strings:
def method
a = ['a', 'b', 'c']
yield(*a)
p a
end
method { |x,y,z| z.upcase! } # => ["a", "b", "C"]
This clearly shows that z is the same as the third object of the array.
Another point here is your example is numeric. Fixnums have fixed ids; so, you can't change the number while maintaining the same object id. To change Fixnums, you must use = to assign a new number to the variable, instead of self-changing methods like inc! (such methods can't exist on Fixnums).
Yes... Array contains links for objects. In your code when you use yield(*a) then in block you works with variables which point to objects which were in array. Now look for code sample:
daz#daz-pc:~/projects/experiments$ irb
irb(main):001:0> a = 1
=> 1
irb(main):002:0> a.object_id
=> 3
irb(main):003:0> a = 2
=> 2
irb(main):004:0> a.object_id
=> 5
So in block you don't change old object, you just create another object and set it to the variable. But the array contain link to the old object.
Look at the debugging stuff:
def m
a = [1, 2]
p a[0].object_id
yield(*a)
p a[0].object_id
end
m { |a, b| p a.object_id; a = 0; p a.object_id }
Output:
3
3
1
3
How can I pass these arguments by reference?
You can't pass arguments by reference in Ruby. Ruby is pass-by-value. Always. No exceptions, no ifs, no buts.
It seems to work if I pass the array directly
I highly doubt that. You simply cannot pass arguments by reference in Ruby. Period.

How to get first n elements from Hash in ruby?

I have a Hash and i have sorted it using the values
#friends_comment_count.sort_by{|k,v| -v}
Now i only want to get hash of top five elements .. One way is to use a counter and break when its 5.
What is preferred way to do in ruby ?
Thanks
h = { 'a' => 10, 'b' => 20, 'c' => 30 }
# get the first two
p Hash[*h.sort_by { |k,v| -v }[0..1].flatten]
EDITED:
# get the first two (more concisely)
p Hash[h.sort_by { |k,v| -v }[0..1]]
Can't you just do something like:
h = {"test"=>"1", "test2"=>"2", "test3"=>"3"}
Then if you wanted the first 2:
p h.first(2).to_h
Result:
=> {"test"=>"1", "test2"=>"2"}
New to ruby myself (please be nice if I'm wrong guys!) but does this work?
#friends_comment_count.sort_by{|k,v| -v}.first 5
Works for me in IRB, if I've understood what you're trying to achieve correctly
You can't sort a Hash and that's why sort_by does NOT sort your Hash. It returns a sorted Array of Arrays.
In Ruby 2.2.0 and later, Enumerable#max_by takes an optional integer argument that makes it return an array instead of just one element. This means you can do:
h = { 'a' => 10, 'b' => 20, 'c' => 30 }
n = 2
p h.max_by(n, &:last).to_h # => {"b"=>20, "c"=>30}
Hashes are not ordered by nature (even thought in Ruby implementation they are). Try geting converting your Hash to Array and get [0,4] out of it

What does the syntax [*a..b] mean in Ruby?

NOTE: mischa's splat on GitHub has lots of cool interactive examples of * in action.
By googling, I found that one way to iterate over a range of numbers in Ruby (your classic C-style for loop)
for (i = first; i <= last; i++) {
whatever(i);
}
is to do something like this
[*first..last].each do |i|
whatever i
end
But what exactly is going on with that [*first..last] syntax? I played around with irb and I see this:
ruby-1.9.2-p180 :001 > 0..5
=> 0..5
ruby-1.9.2-p180 :002 > [0..5]
=> [0..5]
ruby-1.9.2-p180 :003 > [*0..5]
=> [0, 1, 2, 3, 4, 5]
ruby-1.9.2-p180 :004 > *0..5
SyntaxError: (irb):4: syntax error, unexpected tDOT2, expecting tCOLON2 or '[' or '.'
*0..5
^
Everything I've read online discusses the unary asterisk as being useful for expanding and collapsing arguments passed to a method, useful for variable length argument lists
def foo(*bar)
bar
end
foo 'tater' # => ["tater"]
foo 'tater', 'tot' # => ["tater", "tot"]
and I get that, but I don't see how it applies to the expansion being done in my block example above.
To be clear, I know that The Ruby Way is to iterate over an array or collection, not to use the array length and iterate with an integer index. However, in this example, I really am dealing with a list of integers. :)
[*1..10]
is the same thing as
(1..10).to_a # call the "to array" method
Instances of the Array class you have created implement Enumerable so your loop works. On classes that define a to_a method, you can use the splat operator syntax with brackets. Splat does a lot more than just call #to_a though, and would be worth a Google search on its own.
Now, in your case, the Range class itself is already an Enumerable so you could just do:
(first..last).each do |v|
...
end
[first..last] is an array containing only 1 range object. [*first..last] is an array containing the elements of that range having been sent in as an argument list. * works in the context of an argument list.
It is called a splat operator. If you use it within certain positions like an argument position or an array, it will expand into its elements:
a = [1]
[*a, 3] # => [1, 3]
b = [1, 2]
[*b, 3] # => [1, 2, 3]
You can't use it bare or in ranges:
*a..3 # => error.
(*a..3) # => error.
When you have something that is not an array, it returns itself:
a = 1
[*a, 3] # => [1, 3]

Why is Array * referencing, and not copying, values in Ruby?

I want to duplicate a hash using the same keys but different values. I coded up the following snippet, and encountered something I didn't expect:
hsh = {:foo => 'foo', :bar => 'bar'}
hsh_copy = Hash[hsh.keys.zip([[]] * hsh.length)] # => {:foo=>[], :bar=>[]}
hsh_copy[:foo] << 1
hsh_copy[:bar] << 2
hsh_copy # => {:foo=>[1, 2], :bar=>[1, 2]}
It seems that instead of copying the nested array when using the * operator, it just continues to reference the first array.
I'd be very happy if someone could explain why this is happening. Additionally, a better way of duplicating the hash would be appreciated, but I'm more concerned with understanding why * doesn't work as expected here.
If Array#* copied the elements of the array, it would break when used on arrays with non-copyable elements (which includes, among others, numbers), which would not be desirable.
As for how to do what you want to do: Replace hsh.keys.zip([[]] * hsh.length) with hsh.map {|k,v| [k, []] }.
The * operator concatenates copies of the array together to meet the new length.
If an array element references an object, when it is duplicated a new array element is in fact created, but it's a new array element that references the same object.
For example:
irb(main):012:0> ([[]] * 3).map { |e| e.object_id }
=> [2149128060, 2149128060, 2149128060]
In your case, you could just create new elements with .map and let Ruby create a new object with [] each time, but for a general solution, start with:
irb(main):013:0> ([[]] * 3).map { |e| e.clone.object_id }
=> [2149106700, 2149106660, 2149106640]

Resources