I need explanation of method inject - ruby-on-rails

I am trying to figure out how inject method works
Can someone explain this
def mysort
if djeca.any?
djeca.order(name: :asc).inject([]) { |sum, c| sum += c.mysort}.uniq
else
[self]
end
mysort is method of model class Books
In controller I call method mysort :
#mybooks= Books.find_by(name: 'renesansa')
#mybookss= #mybooks.leaf_wms

djeca.order(name: :asc).inject([]) { |sum, c| sum += c.mysort}.uniq
is equivalent to
sum = []
djeca.order(name: :asc).each{|c| sum = sum + c.mysort}
sum.uniq
Adding arrays is actually concatening, so your code just appends all the c.mysort into an array.
If I understand it correctly, you could also write :
djeca.order(name: :asc).map{|c| c.mysort}.flatten.uniq
Depending on your audience, you might want to write one or the other.
Note that you don't need to assign a value to sum in the inject block, it is done automatically.
(1..10).inject(0){|mem,i| mem += i}
#=> 55
(1..10).inject(0){|mem,i| mem + i}
#=> 55

You should follow this link https://samurails.com/tips/ruby-ruby-on-rails-inject/
For example:
result = [1,2,3,4].inject(0) { |sum, number| sum + number }
Here, the process starts from 0 index to the 3 index. First inject adds 1 & 2 to get the sum of two values and stored in sum variable(i.e. sum= 3) and then it takes the sum value and adds 3 to it, to get result(i.e sum=6) and so on. Finally you will get the result like 10.

djeca.order(name: :asc)
Retrieves array of active records
.inject([])
Looping through each active record. The initial output of inject is empty array [].
{ |sum, c| }
c - each active record
sum - Inject output value. Initially its []. On each iteration the value is appended to the array. (sum += [some values])

Related

How to compute each cell of a line in an array with Ruby on Rails 5.2?

While importing from an excel file to a database, I need to format a hierarchy so it appears with leading zeros:
10.1.1.4 must be transformed into 1.010.001.001.004
I tried to iterate through and concatenate the elements:
record.hierarchy = spreadsheet.cell(i,2).split('.').each do |t|
index = index || '1.'
index = index + '.' + (((t.to_i + 1000).to_s).last(3))
end
which actually returns and array of ["10", "1", "1", "4"], not computed. I would expect this to return the last evaluated value: index
I tried to compute it directly inside the array:
record.hierarchy = '1.' + (((spreadsheet.cell(i,2).split('.').each).to_i + 1000).to_s).last(3).join('.')
which raises an undefined method to_i for enumerator.
Can someone explain me how to structure and solve this computation?
Thanks
Use #rjust.
'10.1.1.4'.split('.').map { |l| l.rjust(3, '0') }.join('.')
Your first solution uses assignment with #each. #each will not return modified array.
It is not necessary to convert the string to an array, modify the elements of the array and then join the array back into a string. The string can be modified directly using String#gsub.
str = '10.1.1.4'
('1.' + str).gsub(/(?<=\.)\d+/) { |s| sprintf("%03d", s.to_i) }
#=> "1.010.001.001.004"
See Kernel#sprintf.
(?<=\.) is positive lookbehind that requires the matched digits to be preceded by a period. I've assumed the string is known to contain between one and three digits before and after each period.
You can try different function for leading zeroes and inject to not set default value inside the loop
record.hierarchy = spreadsheet.cell(i,2).split('.').inject('1') do |result, t|
result + '.' + t.rjust(3, '0')
end

Check if multiple (more than two) arrays match, regardless of element order in ruby

If I have two arrays a and b. I can compare them as a.uniq.sort == b.uniq.sort. This will tell me if the elements in a match those in b.
What if I have four arrays a, b, c, d? I need to make sure they are all equal to each other. What is the best way to do this?
[%w[a b c], %w[c b a], %w[a a b c], %w[b a b c]]
.group_by{|a| a.uniq.sort}.one? # => true
[%w[a b c], %w[c b a], %w[a a b c], %w[b a b c d]]
.group_by{|a| a.uniq.sort}.one? # => false
Take one array, sort it's unique elements and compare the other arrays with that as reference. Return false as soon as one is not equal.
ars = [a,b,c,d]
ref = ars.pop.uniq.sort #take care: pop mutates ars
p ars.all?{|ar| ar.uniq.sort == ref}
Here's a way that doesn't involve sorting.
Let arr be an array of the given arrays. If the arrays contained no duplicates, this would be simple:
first, *rest = arr
rest.all? { |a| (first-a).empty? && (a-first).empty? }
To deal with duplicates, we define a helper Array#difference:
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
In a nutshell, if n elements of an array a equal a given object and m elements of an array b equal the same object, the array a.difference(b) will contain (the first) [n-m,0].max elements of a that equal the object .
Then we can write the following.
first, *rest = arr
rest.all? { |a| first.difference(a).empty? && a.difference(first).empty? }
The block evaluates true if every element of a maps to a unique element of first, and vice-versa.
It may seem a waste of time to create Array#difference for just this one problem. I have found, however, it to be a valuable member of my tool kit, having wide application, so much so that I proposed it be added to the Ruby core. The link gives examples of its uses and also contains a link to an SO answer I gave that contains a longer list of problems where it has found application.
There's a nice reduce solution:
ars.reduce { |a, v| a.uniq.sort == v.uniq.sort ? v.uniq.sort : break }
Which ends up to pretty clean code used in conjunction with map:
ars.map { |v| v.uniq.sort }.reduce { |a, v| a == v ? v : break }
The map -> reduce version is not the the best for performance since all the array elements get uniq.sort'd, but it's readable and functional style.
Since you use uniq you can use intersection, without the uniq intersection couldn't be used to compare arrays.
arr.inject(:&).sort == arr.first.uniq.sort
This would mean that arrays like
[3,2,3,1]
[1,3,2]
[3,2,1]
[2,1,3,1]
would be the same
You can try:
a.uniq.sort == b.uniq.sort and b.uniq.sort == c.uniq.sort and c.uniq.sort == d.uniq.sort
To optimise it, you can store the result of b.uniq.sort and c.uniq.sort in some variable to prevent repeated computation before comparing.

how to represent nil in a table

Let's suppose I want to store a list of element. Including some nil values. The position of the values is significant, and I need to represent the absence of a value in the list at a given position.
Here is a problem:
a = {1,2,3,nil,4}
for k,v in ipairs(a) do
print(k,v)
end
print(a[4])
print(a[5])
The for loop will only print elements 1,2 and 3. It stops at nil. The first print statement prints nil, but I'm not sure if it is actually stored in the table or not. (Who knows?) The second print statement prints 4 - as expected.
So here is the question: how to represent a list of elements in a table, and iterate through them efficiently? Given the conditions above, e.g. the position is significant, and some of the positions are "empty". In other words: have no value, but the absence of that value at that position has a meaning.
This is module "null.lua"
local function null(...)
local t, n = {...}, select('#', ...)
for k = 1, n do
local v = t[k]
if v == null then t[k] = nil
elseif v == nil then t[k] = null
end
end
return (table.unpack or unpack)(t, 1, n)
end
_G.null = null
Use null() as encoder and decoder
require("null")
a = {null(1,2,3,nil,4)}
-- the same could be done element-by-element
-- a = {null(1),null(2),null(3),null(nil),null(4)}
for k,v in ipairs(a) do
v = null(v)
print(k,v)
end
print(null(a[4]))
print(null(a[5]))
Lua tables can be used to create any Abstract Data Structure, in your case you indicated that you want a "list". A Lua table is a data structure that combines numeric index based access with key:value access.
Based on your example, you are using the numeric index feature of tables that let you iterate (with ipairs()) through those values. You will not be able to put nil into the table since the numeric index stops at the first nil entry. The remaining values in the table are stored as key:value pairs.
There are several work-arounds, but it depends on why you want a nil in the list. The simplest approach is to use the string "nil" rather than the native data type nil.
a = {1, 2, 3, "nil", 4}
for k,v in ipairs(a) do
print(k,v)
end
The result of this code is:
1 1
2 2
3 3
4 nil
5 4
Because of the way Lua implements strings, there is not a performance penalty for comparing to the string "nil" versus comparing to the native type nil.
The issue of "holes" (caused by nil) in an array are discussed in Programming in Lua, Chapter 5 Tables. Roberto Ierusalimschy recommendation is to track the size of the array to avoid problems with holes.
The following code shows an Object Oriented approach to tracking the size of the list. There are many possible variations on this theme.
function makeList(...)
local list = table.pack(...)
list.length =
function(self) return self.n
end
list.append =
function(self, value)
self.n = self.n + 1
self[self.n] = value
end
list.print =
function(self)
for i = 1, self.n do print(i, self[i]) end
end
return list
end
a = makeList(1, 2, 3, nil, 4)
a:append(5)
a:print()
print(a:length())
The result is:
1 1
2 2
3 3
4 nil
5 4
6 5
6
Note that the function table.pack creates a field 'n' which contains the correct number of items even when 'nil' is present. See PIL chapter 6.2, Variadic Functions for a complete explanation.
Don't just hack something together, write your own datastructure for this. If you "overload" ipairs (by writing an appropriate iterator) you can use it as a table:
function create(...)
local t = table.pack(...)
local self = {
num = t.n,
elements = { ... }
}
return self
end
function elements(t)
local f = function(s, i)
i = i + 1
if i <= s.num then
return i, s.elements[i]
end
end
return f, t, 0
end
local seq = create(1, 2, nil, 3)
print(seq.num)
for i, e in elements(seq) do
print(i, e)
end
-- results:
-- 4
-- 1 1
-- 2 2
-- 3 nil
-- 4 3
You could know define a metatable for this structure and have it use its own ipairs, so you don't even have to change the name.
Well, you can't store nil in the table without issues.
The most simple solution here would be to introduce your own unique value.
local mynil = {} -- every new table is unique!
a = {1,2,3,mynil,4}
for k,v in ipairs(a) do
if (v == mynil) then
v = nil
end
print(k,v)
end
No more issues with "nil" string that might be stored in the table as well, the minor issue is one more comparison. ipairs or any other iterator will show that the key with mynil value exists. That means you can separate mynil key existence with missing key =nil.
P.S. If you want to shift your list, you may consider table.remove(list, key) function.
The answer to this is rather simple, and these "workaround a" suggested is definitely overkill. Just keep track of the number of items in your table whenever it's changed (note: do not use #, you have too keep track manually to deal with nil values) and use a numeric for loop to iterate over it.

LUA: Loop in 2d table to display first key name

Currently I'm stuck on this:
t = {['79402d'] = {'-5.4','5','1.6'}, ['5813g1'] = {'3','0.15','18'}}
Now i need to loop through this table to check if name == t[1], but how can i do so?
I tried doing something like: for i=1,#t,1 do print(t[i]) but it doesn't seem to work.
I hope you can help me guys ;)
Not sure why it didn't worked first time but i solved my problem with:
for a,b in pairs(t) do
print(a, b[1], b[2], b[3])
end
Please note that the length operator # will give you the correct number of elements in a table only in a special case. in your case #t will return 0, hence your for loop does nothing.
Please refer to https://www.lua.org/manual/5.3/manual.html Section 3.4.7 – The Length Operator for details on how to use the lenght operator.
For a regular array, with non-nil values from 1 to a given n, its length is exactly that n, the index of its last value. so #t will be zero if you have no t[1] or n-1 if t[n] is the first nil value in your table starting from t[1].
As you have no t[1] but only a t['79402d'] and a t['5813g1'] your for loop indexing t[i] would not work anyway.
Please read 3.4.9 – Table Constructors on how table construction works.
t = {"hello", "world"}
is the same as
t = {}
t[1] = "hello"
t[2] = "world"
(here t[1] is "hello" and #t is 2
whereas t = {['key1'] = "hello", ['key2'] = "world"}
equals
t = {}
t['key1'] = "hello"
t['key2'] = "world"
so t[1] here is nil and #t is 0
# operator returns lenght of array part of the table. Your table is not an array (i.e. a table with non-nil values from index 1 to a given n). Because of that your loop is not iterating any elements.
Use pairs to iterate over all keys in the table regardless of what they are.

Grails adding to a function's return values before returning them

I have a function (the caller) that returns certain values. Before they are returned, they are added to by the result of calling another function (the callee), is there a neater way to add the callee function's resultant values to the caller function's resultant values before the are returned?
def funcA() { // Caller Function
def a,b,c
a = blah
b = blah blah
...
def (d,e) = funcB()
a += d // Is there a neater way to encapsulate
b += e // these additions somehow into the previous line? maybe kind of like
// (a,b) += funcB() ?
return [a,b,c]
}
def funcB() { // Callee Function
def d,e
...
return [d,e]
}
to bad we don't have a zip, then something like [a,b].zip()*.sum() would work.
transpose() only gives you the pairs (not the remainder). so this only works, if your lists are the same length:
assert [43,668]==[[1,2,3],[42,666]].transpose()*.sum()
One could attempt to fill shorter list with zeros in this case, so the sum is not influenced (at that point this depends on the type there).
So this function is a workaround:
def zipadd(a,b) {
def l = [a,b]
// iterate over the size of the longer of the two lists
(0..(l*.size().max()-1)).collect{
// get the pairs, filter null, sum both
l*.getAt(it).findAll{it!=null}.sum()
}
}
assert [43,668,3]==zipadd([1,2,3],[42,666])
assert [43,668,3]==zipadd([42,666],[1,2,3])
assert [24,93,0,1]==zipadd([1,0,0,1], [23,93])
If you use pass l directly (a list of lists) instead of a and b this should also work with more than two elements.

Resources