How to convert to Crystal ruby's multiple assignments of Array - ruby

I have a small (formerly) ruby blockchain script I'm trying to convert over into Crystal, that looks like this so far:
# build your own blockchain from scratch in crystal!
#
# to run use:
# $ crystal ./blockchain_with_proof_of_work.cr
require "openssl" # for hash checksum digest function SHA256
class Block
getter index : Int32
getter timestamp : Time
getter data : String
getter previous_hash : String
getter nonce : Int32 # # proof of work if hash starts with leading zeros (00)
getter hash : String
def initialize(index, data, previous_hash)
#index = index
#timestamp = Time.now
#data = data
#previous_hash = previous_hash
#nonce, #hash = compute_hash_with_proof_of_work
end
def compute_hash_with_proof_of_work(difficulty = "00")
nonce = 0
loop do
hash = calc_hash_with_nonce(nonce)
if hash.starts_with?(difficulty)
return [nonce, hash] # # bingo! proof of work if hash starts with leading zeros (00)
else
nonce += 1 # # keep trying (and trying and trying)
end
end
end
def calc_hash_with_nonce(nonce = 0)
sha = OpenSSL::Digest.new("SHA256")
sha.update(nonce.to_s + #index.to_s + #timestamp.to_s + #data + #previous_hash)
sha.hexdigest
end
def self.first(data = "Genesis") # create genesis (big bang! first) block
# # uses index zero (0) and arbitrary previous_hash ("0")
Block.new(0, data, "0")
end
def self.next(previous, data = "Transaction Data...")
Block.new(previous.index + 1, data, previous.hash)
end
end # class Block
#####
# # let's get started
# # build a blockchain a block at a time
b0 = Block.first("Genesis")
b1 = Block.next(b0, "Transaction Data...")
b2 = Block.next(b1, "Transaction Data......")
b3 = Block.next(b2, "More Transaction Data...")
blockchain = [b0, b1, b2, b3]
puts blockchain
######
# will print something like:
#
# [#<Block:0x1e204f0
# #data="Genesis",
# #hash="00b8e77e27378f9aa0afbcea3a2882bb62f6663771dee053364beb1887e18bcf",
# #index=0,
# #nonce=242,
# #previous_hash="0",
# #timestamp=2017-09-20 20:13:38 +0200>,
# #<Block:0x1e56e20
# #data="Transaction Data...",
# #hash="00aae8d2e9387e13c71b33f8cd205d336ac250d2828011f5970062912985a9af",
# #index=1,
# #nonce=46,
# #previous_hash=
# "00b8e77e27378f9aa0afbcea3a2882bb62f6663771dee053364beb1887e18bcf",
# #timestamp=2017-09-20 20:13:38 +0200>,
# #<Block:0x1e2bd58
# #data="Transaction Data......",
# #hash="00ea45e0f4683c3bec4364f349ee2b6816be0c9fd95cfd5ffcc6ed572c62f190",
# #index=2,
# #nonce=350,
# #previous_hash=
# "00aae8d2e9387e13c71b33f8cd205d336ac250d2828011f5970062912985a9af",
# #timestamp=2017-09-20 20:13:38 +0200>,
# #<Block:0x1fa8338
# #data="More Transaction Data...",
# #hash="00436f0fca677652963e904ce4c624606a255946b921132d5b1f70f7d86c4ab8",
# #index=3,
# #nonce=59,
# #previous_hash=
# "00ea45e0f4683c3bec4364f349ee2b6816be0c9fd95cfd5ffcc6ed572c62f190",
# #timestamp=2017-09-20 20:13:38 +0200>]
However when I run it I get an error that states:
Error in blockchain.cr/blockchain_with_proof_of_work.cr:57: instantiating
'Block:Class#first(String)'
b0 = Block.first("Genesis")
^~~~~
in blockchain.cr/blockchain_with_proof_of_work.cr:45: instantiating
'Block:Class#new(Int32, String, String)'
Block.new(0, data, "0")
^~~
in blockchain.cr/blockchain_with_proof_of_work.cr:22: instance variable
'#nonce' of Block must be Int32, not (Int32 | String)
#nonce, #hash = compute_hash_with_proof_of_work
^~~~~~
Looking at Crystal docs on multiple assignment, I'm unsure of how I can refactor this method so that it doesn't fail Crystal's automatic static type checking and type inference? The method in question, of an array of two types being returned, doesn't seem covered by the docs:
#nonce, #hash = compute_hash_with_proof_of_work # return [nonce, hash]

When decomposing an Array into a multiple assignment Crystal can't infer the exact type of each element. So the value assigned to the instance variable #nonce could be either Int32 or String. You should use a Tuple instead: return {nonce, hash} (in line 29). A tuple has positional type declarations and is by the way more performant than an Array because it does not allocate memory on the heap.

Related

How to find duplicated Ruby methods with the same name but different code?

The very large Ruby codebase I am working with has many instances of duplicated methods defined with the same name but some of its code is different (causing a large race condition problem). The eventual end goal is to reconcile the duplicates and have just one version of the same-named method. First I need to find all versions of a method that deviate from the "control" version of that method. Is there an optimal way to search and for and find all instances of duplicated same-named methods that deviate from one defined version?
The duplicated methods are spread out across hundreds of different files and contained in one class. These are essentially helper methods that should have been centralized in one file but instead have been duplicated and often altered, but keeping the same method name. Right now I just need a good way to locate all the instances where these methods have been duplicated and are different from what the method should be.
I think Rubocop only searches for duplicated method names which is only moderately helpful since it could find 237 methods with the same name but I don't know how many of those methods are deviations from my "control" method without manually looking and comparing.
Some examples of a method redefined in files across multiple subdirectories:
def get_field(field_name)
return nil unless field = #global_vars.business.fields.find_by_identifier(field_name)
field.value.present? ? field.value : nil
end
def get_field(field_name)
#global_vars.business.fields.find_by_identifier(field_name).try(:value)
end
def get_field(field_name)
return nil unless field #company.fields.find_by_identifier(field_name)
field.value.present? ? field.value : nil
end
def get_field(field_name)
#property.fields.find_by_identifier(field_name).try(:value)
end
Thanks for your help!
My first thought was to execute each file of interest with additional code added on the fly to build a directory of methods and their locations. That clearly would not work, however, as exceptions could be expected to be raised almost immediately. Even if exceptions were avoided there would be no guarantee that that added code would be executed. In addition, there could be unintended adverse consequences of blinding running code.
I think the only reasonable approach would be to parse the files of interest. There may even be gems that do just that. It's certainly worth a search.
I have constructed a method that parses the files to build a hash containing the information desired. The main requirement for its use is that the files are formatted properly; specifically, the key words class, module and def must be indented the same number of spaces as their corresponding end keywords. It will therefore miss modules, classes and methods that are defined in-line, such as the following.
module M; end
class C; end
def im(n) 2*n end
def self.cm(n) 2*n end
If vertical alignment is a problem there certainly are gems that format code properly.
I chose a particular hash structure, but once that hash has been constructed it could be modified as desired. For example, I've adopted the hierarchy "instance methods->files->containers" ("containers" being modules, classes and top-level). One could easily modify that hash to change the hierarchy to, say, "container->module methods->files". Alternatively, one could enter the information into a database to maintain flexibility on how is used.
Code
The following regular expression is used to parse each line of each file of interest.
R = /
\A # match beginning of string
(?<indent>[ ]*) # capture zero or more spaces, name 'indent'
(?: # begin non-capture group
(?<type>class|module) # capture keyword 'class' or 'module', name 'type'
[ ]+ # match one or more spaces
(?<name>\p{Upper}\p{Alnum}*) # capture an uppercase letter followed by
# >= alphanumeric chars, name 'name'
| # or
(?<type>def) # capture keyword 'def', name 'type'
[ ]+ # match one or more spaces
(?<name> # begin capture group named 'name'
(?:self\.)? # optionally match 'self.'
\p{Lower}\p{Alnum}* # match a lowercase letter followed by
# >= 0 zero alphanumeric chars, name 'name'
) # close capture group 'name'
| # or
(?<type>end) # capture keyword 'end', name 'type'
\b # match a word break
) # end non-capture group
/x # free-spacing regex definition mode
The method used for parsing follows.
def find_methods_by_name(files_of_interest)
files_of_interest.each_with_object({ imethod: {}, cmethod: {} }) do |fname, h|
stack = []
File.readlines(fname).each do |line|
m = line.match R
next if m.nil?
indent, type, name = m[:indent].size, m[:type], m[:name]
case type
when "module", "class"
name = stack.any? ? [stack.last[:name], name].join('::') : name
stack << { indent: indent, type: type, name: name }
when "def"
if name =~ /\Aself\./
stack << { indent: indent, type: :cmethod, name: name[5..-1] }
else
stack << { indent: indent, type: :imethod, name: name }
end
when "end"
next if stack.empty? || stack.last[:indent] != indent
type, name = stack.pop.values_at(:type, :name)
next if type == "module" or type == "class"
((h[type][name] ||= {})[fname] ||= []) << (stack.any? ?
[stack.last[:type], stack.last[:name]].join(' ') : :main)
end
end
raise StandardError, "stack = #{stack} after processing file '#{fname}'" if stack.any?
end
end
Example
The files of interest might be, for example, all files in certain directories. In this example we have just two files.
files_of_interest = ['file1.rb', 'file2.rb']
Those files are as follows.
File.write('file1.rb',
<<_)
def mm
end
module M
def m
end
module N
def self.nm
end
def n
end
def a2
end
end
end
class A
def self.a1c
end
def a1
end
def a2
end
end
class B
include M
def b
end
end
_
#=> 327
File.write('file2.rb',
<<_)
def mm
end
module M
def m
end
module N
def n
end
def a2
end
end
end
module P
def p
end
end
class A
include M::N
def self.a1c
end
def a1
end
end
class B
include P
def b
end
end
_
#=> 335
h = find_methods_by_name(files_of_interest)
#=> {
# :imethod=>{
# "mm"=>{
# "file1.rb"=>[:main],
# "file2.rb"=>[:main]
# },
# "m"=>{
# "file1.rb"=>["module M"],
# "file2.rb"=>["module M"]
# },
# "n"=>{
# "file1.rb"=>["module M::N"],
# "file2.rb"=>["module M::N"]
# },
# "a2"=>{
# "file1.rb"=>["module M::N", "class A"],
# "file2.rb"=>["module M::N"]
# },
# "a1"=>{
# "file1.rb"=>["class A"],
# "file2.rb"=>["class A"]
# },
# "b"=>{
# "file1.rb"=>["class B"],
# "file2.rb"=>["class B"]
# },
# "p"=>{
# "file2.rb"=>["module P"]
# }
# },
# :cmethod=>{
# "nm"=>{
# "file1.rb"=>["module M::N"]
# },
# "a1c"=>{
# "file1.rb"=>["class A"],
# "file2.rb"=>["class A"]
# }
# }
# }
To eliminate files that appear only once, we can perform an additional step.
h.transform_values! { |g| g.reject { |k,v| v.size == 1 && v.values.first.size == 1 } }
This removes the instance method p and the class method nm.

Array#sample random numbers generator

How does Array#sample work, if you pass a range as random numbers generator?
As here :
> [*1..10].sample(random: 1..3)
=>9
As per the doc, sample(random: rng), rng is not a Range Object, as you might thought. rng is a Random number generator.
The optional rng argument will be used as the random number generator.
a = (1..10).to_a
r = Random.new
r2 = r.dup
a1 = a.sample(random: r)
a2 = a.sample(random: r2)
a1 == a2 # => true
The point is #sample, takes its second argument as keyword argument. If we use something like foo: 12 or rng: (1..2), it will give ArgumentError: unknown keyword:. The optional argument will be acceptable only when you will supply it a value as random: <any random number generator>. Now, coming to your point :
r = 1..3
a1 = [*1..10].sample(random: r)
a2 = [*1..10].sample(random: r)
a1 == a2 # => false
When you are passing the second argument as random: r, r must be Random object, or an object that responds to #rand. Remember with the second argument, you are telling #sample, to use your random number generator instead of the default one it uses in absence of the optional argument.
Here is one custom implementation of a RNG :
ob = Object.new
def ob.to_int
5000
end
gen_to_int = proc do |max|
ob
end
class << gen_to_int
alias rand call
end
ary = (0...10000).to_a
ary.sample(random: gen_to_int) # => 5000
ary.sample(random: gen_to_int) # => 5000
ary.sample(random: gen_to_int) # => 5000
This will give you the understanding about #sample with the optional argument. Look for more examples from #test_sample_random.
Update
How does Array#sample work, if you pass a range as random numbers generator?
To answer this, I would take a help of the TracePoint class.
trace = TracePoint.new(:c_call) do |tp|
p [tp.lineno, tp.defined_class, tp.method_id, tp.event]
end
trace.enable do
[1,2,3,4,5,66,4].sample(random: 1..3)
end
# >> [6, Array, :sample, :c_call]
# >> [6, Kernel, :rand, :c_call]
# >> [6, Kernel, :respond_to_missing?, :c_call]
So, from the above call stack, you can see - Array#sample method has been called. Now internally, Ruby called Kernel#rand on the Range object. Now, (1..3).respond_to? returned false(because #rand - it is a_private_ instance method for Range), that's why finally #respond_to_missing? method has been called to do the job.

Casting by a variable of class

I can cast a string to float via:
Float("1")
Now I have this:
f = Float
How can I use f to cast a string to float?
Edited
I'm sorry for not being clear.
Here is my scenario:
I'm using Redis DB, via gem redis-rb. Basically redis-db stores values as strings. When I map back values from DB to my objects, I wish to cast them to some primitive types for easy to use.
So I use this:
require 'ostruct'
class Person < OpenStruct
MAP = { :name => String, :age => Fixnum }
##
# This reads data from DB, and converts them to some "primitive" types
# for easy use.
#
def read
MAP.each_pair do |sym, cls|
# read data as string from DB, via key `sym.to_s`
s = ...
# now I have `cls`, how can I "cast" `s` to `cls`?
self[sym] = ???
# I know I can "iterate" all types by this:
#
# if cls.is_a? Float
# self[sym] = s.to_f
# elsif cls.is_a? Fixnum
# self[sym] = s.to_i
# ...
#
# But in Python I can just cast `s` to `cls` in one line...
# So I wonder if there is some way to cast `s` to `cls` in Ruby?
end
end # read
end # Person
Saying "for easy use", I mean I want to:
p = Person.new
p.read
# I want to access `age` as an integer, not a string
if p.age +-*/ ...
You cannot use f to do it. The Float in Float("1") is a method. The Float in f = Float is a class (an object). They are different things.
For what I understood from your question you want this:
result = '1'.to_f #outputs => 1.0
then for string:
result.to_s #outputs => "1.0"
And you can also do it in one step as well:
'1'.to_f.to_s #outputs => "1.0"
For more about strings and ruby you can see ruby doc

Ruby: How to convert IP range to array of IP's

Is there any easy way to convert IP range to array of IPs?
def convertIPrange (start_ip, end_ip)
#output: array of ips end
end
e.g. input
('192.168.1.105', '192.168.1.108')
output
['192.168.1.105','192.158.1.106','192.158.1.107','192.158.1.108']
Use the Ruby standard library IPAddr
# I would suggest naming your function using underscore rather than camelcase
# because of Ruby naming conventions
#
require 'ipaddr'
def convert_ip_range(start_ip, end_ip)
start_ip = IPAddr.new(start_ip)
end_ip = IPAddr.new(end_ip)
# map to_s if you like, you can also call to_a,
# IPAddrs have some neat functions regarding IPs,
# be sure to check them out
#
(start_ip..end_ip).map(&:to_s)
end
def convertIPrange first, last
first, last = [first, last]
.map{|s| s.split(".").inject(0){|i, s| i = 256 * i + s.to_i}}
(first..last).map do |q|
a = []
(q, r = q.divmod(256)) && a.unshift(r) until q.zero?
a.join(".")
end
end
convertIPrange('192.168.1.105', '192.168.1.108')
# => ["192.168.1.105", "192.168.1.106", "192.168.1.107", "192.168.1.108"]
convertIPrange('192.255.255.254', '193.0.0.1')
# => ["192.255.255.254", "192.255.255.255", "193.0.0.0", "193.0.0.1"]

Subclassing Ruby Hash, object has no methods of Hash?

I'm creating a object of hash in order to write a little script that reads in a file a line at a time, and assigns arrays into my hash class. I get wildly different results depending if I subclass Hash or not, plus using super changes things which I don't' understand.
My main issue is that without subclassing hash ( < Hash) it works perfectly, but I get no methods of Hash (like to iterate over the keys and get things out of it.... Subclassing Hash lets me do those things, but it seems that only the last element of the hashed arrays is ever stored.... so any insight into how you get the methods of a subclass. The Dictionary class is a great example I found on this site, and does exactly what I want, so I'm trying to understand how to use it properly.
filename = 'inputfile.txt.'
# ??? class Dictionary < Hash
class Dictionary
def initialize()
#data = Hash.new { |hash, key| hash[key] = [] }
end
def [](key)
#data[key]
end
def []=(key,words)
#data[key] += [words].flatten
#data[key]
# super(key,words)
end
end
listData = Dictionary.new
File.open(filename, 'r').each_line do |line|
line = line.strip.split(/[^[:alpha:]|#|\.]/)
puts "LIST-> #{line[0]} SUB-> #{line[1]} "
listData[line[0]] = ("#{line[1]}")
end
puts '====================================='
puts listData.inspect
puts '====================================='
print listData.reduce('') {|s, (k, v)|
s << "The key is #{k} and the value is #{v}.\n"
}
If anyone understands what is going on here subclassing hash, and has some pointers, that would be excellent.
Running without explicit < Hash:
./list.rb:34:in `<main>': undefined method `reduce' for #<Dictionary:0x007fcf0a8879e0> (NoMethodError)
That is the typical error I see when I try and iterate in any way over my hash.
Here is a sample input file:
listA billg#microsoft.com
listA ed#apple.com
listA frank#lotus.com
listB evanwhite#go.com
listB joespink#go.com
listB fredgrey#stop.com
I can't reproduce your problem using your code:
d = Dictionary.new #=> #<Dictionary:0x007f903a1adef8 #data={}>
d[4] << 5 #=> [5]
d[5] << 6 #=> [6]
d #=> #<Dictionary:0x007f903a1adef8 #data={4=>[5], 5=>[6]}>
d.instance_variable_get(:#data) #=> {4=>[5], 5=>[6]}
But of course you won't get reduce if you don't subclass or include a class/module that defines it, or define it yourself!
The way you have implemented Dictionary is bound to have problems. You should call super instead of reimplementing wherever possible. For example, simply this works:
class Dictionary < Hash
def initialize
super { |hash, key| hash[key] = [] }
end
end
d = Dictionary.new #=> {}
d['answer'] << 42 #=> [42]
d['pi'] << 3.14 #=> [3.14
d #=> {"answer"=>[42], "pi"=>[3.14]}
If you want to reimplement how and where the internal hash is stored (i.e., using #data), you'd have to reimplement at least each (since that is what almost all Enumerable methods call to) and getters/setters. Not worth the effort when you can just change one method instead.
While Andrew Marshall's answer
already correct, You could also try this alternative below.
Going from your code, We could assume that you want to create an object that
act like a Hash, but with a little bit different behaviour. Hence our first
code will be like this.
class Dictionary < Hash
Assigning a new value to some key in the dictionary will be done differently
in here. From your example above, the assignment won't replace the previous
value with a new one, but instead push the new value to the previous or to
a new array that initialized with the new value if the key doesn't exist yet.
Here I use the << operator as the shorthand of push method for Array.
Also, the method return the value since it's what super do (see the if part)
def []=(key, value)
if self[key]
self[key] << value
return value # here we mimic what super do
else
super(key, [value])
end
end
The advantage of using our own class is we could add new method to the class
and it will be accessible to all of it instance. Hence we need not to
monkeypatch the Hash class that considered dangerous thing.
def size_of(key)
return self[key].size if self[key]
return 0 # the case for non existing key
end
Now, if we combine all above we will get this code
class Dictionary < Hash
def []=(key, value)
if self[key]
self[key] << value
return value
else
super(key, [value])
end
end
def size_of(key)
return self[key].size if self[key]
return 0 # the case for non existing key
end
end
player_emails = Dictionary.new
player_emails["SAO"] = "kirito#sao.com" # note no << operator needed here
player_emails["ALO"] = "lyfa#alo.com"
player_emails["SAO"] = "lizbeth#sao.com"
player_emails["SAO"] = "asuna#sao.com"
player_emails.size_of("SAO") #=> 3
player_emails.size_of("ALO") #=> 1
player_emails.size_of("GGO") #=> 0
p listData
#=> {"SAO" => ["kirito#sao.com", "lizbeth#sao.com", "asuna#sao.com"],
#=> "ALO" => ["lyfa#alo.com"] }
But, surely, the class definition could be replaced with this single line
player_emails = Hash.new { [] }
# note that we wont use
#
# player_emails[key] = value
#
# instead
#
# player_emails[key] << value
#
# Oh, if you consider the comment,
# it will no longer considered a single line
While the answer are finished, I wanna comment some of your example code:
filename = 'inputfile.txt.'
# Maybe it's better to use ARGF instead,
# so you could supply the filename in the command line
# and, is the filename ended with a dot? O.o;
File.open(filename, 'r').each_line do |line|
# This line open the file anonimously,
# then access each line of the file.
# Please correct me, Is the file will properly closed? I doubt no.
# Saver version:
File.open(filename, 'r') do |file|
file.each_line do |line|
# ...
end
end # the file will closed when we reach here
# ARGF version:
ARGF.each_line do |line|
# ...
end
# Inside the each_line block
line = line.strip.split(/[^[:alpha:]|#|\.]/)
# I don't know what do you mean by that line,
# but using that regex will result
#
# ["listA", "", "", "billg#microsoft.com"]
#
# Hence, your example will fail since
# line[0] == "listA" and line[1] == ""
# also note that your regex mean
#
# any character except:
# letters, '|', '#', '|', '\.'
#
# If you want to split over one or more
# whitespace characters use \s+ instead.
# Hence we could replace it with:
line = line.strip.split(/\s+/)
puts "LIST-> #{line[0]} SUB-> #{line[1]} "
# OK, Is this supposed to debug the line?
# Tips: the simplest way to debug is:
#
# p line
#
# that's all,
listData[line[0]] = ("#{line[1]}")
# why? using (), then "", then #{}
# I suggest:
listData[line[0]] = line[1]
# But to make more simple, actually you could do this instead
key, value = line.strip.split(/\s+/)
listData[key] = value
# Outside the block:
puts '====================================='
# OK, that's too loooooooooong...
puts '=' * 30
# or better assign it to a variable since you use it twice
a = '=' * 30
puts a
p listData # better way to debug
puts a
# next:
print listData.reduce('') { |s, (k, v)|
s << "The key is #{k} and the value is #{v}.\n"
}
# why using reduce?
# for debugging you could use `p listData` instead.
# but since you are printing it, why not iterate for
# each element then print each of that.
listData.each do |k, v|
puts "The key is #{k} and the value is #{v}."
end
OK, sorry for blabbering so much, Hope it help.

Resources