sql.eachRow( query ) { columns ->
println columns.firstname //executes well
Eval.me( "columns.firstname" ) //No such property: columns
}
How do I evaluate the String containing the closure variable columns?
You can use the 3 parameter form of Eval.me:
Eval.me( 'columns', columns, 'columns.firstname' )
Related
can I combine below closures into one or do this in a more functional and elegant way in groovy. I am using the sortMethod in some other places( for testing purpose) too.
for eg : countAndMap should take
["a b c a a c" , "b b c"] and return[x1 : [a:3,c:2,b:1] , x2 : [b:2,c:1]]
def countAndMap(List<String> stringList) {
stringList.withIndex().collect { String s, Integer i -> [(num.call(i)): count.call(s)] }
}
Closure count = {sortMethod.call(it.split().countBy {it}) }
Closure sortMethod = { it.sort { x, y -> x.value <=> y.value } }
Closure num = { "x ${it + 1}".toString()}
there are no errors but I wonder if it's possible to do it in a more functional way
I am not sure what you mean with "more functional", but you could use a fold operation (called inject in groovy):
list = ["a b c a a c" , "b b c"]
def createSortedHistogram(String toCount) {
toCount
.split() // Create list of words
.inject([:]){ acc, word -> acc[word] = 1 + (acc[word] ?: 0);acc} // create histogram
.sort{-it.value} // sort histogram map by value desc
}
def countAndMap(List<String> list) {
list.withIndex().collectEntries{ sublist, i -> ["x ${i+1}": createSortedHistogram(sublist)] }
}
countAndMap(list)
I think the most interesting part is the inject method.
This solution uses the initial value [:] in order to use a map as result. In each iteration the inject operation either adds a new entry with value 1 to the map (if the word/key does not exist in the map) or increases the value of the word/key if it is already present in the map.
See the inject definition from Collections GroovyDoc.
public Object inject(Object initialValue, Closure closure) - Iterates through the given Collection, passing in the initial value to the 2-arg closure along with the first item. The result is passed back (injected) into the closure along with the second item. The new result is injected back into the closure along with the third item and so on until the entire collection has been used. Also known as foldLeft or reduce in functional parlance.
I have been trying to get this dynamic query to work with dates as shown below in ArangoDB 3.1.
This works perfectly when I'm not trying to query dates, but returns an empty list as soon as I try to query with a date like below...
{
query:
'For c IN ##collectionName
FILTER ( c.#p0 == #v0 AND c.#p1 >= #v1 AND c.#p2 <= #v2 )
LIMIT #count RETURN c ',
bindVars: {
'#collectionName': 'Event',
p0: 'isPublished',
v0: true,
p1: 'dates[*].startsAt',
v1: '2018-06-01T04:00:00.000Z',
p2: 'dates[*].startsAt',
v2: '2018-07-01T03:59:59.999Z',
count: 9
}
}
Need some help getting past this
There are mistakes in your query, but they are actually not related to dates:
dates[*].startsAt is not a valid attribute path, but a shorthand expression for FOR date IN dates RETURN date.startsAt, which returns an array
The comparison operator >= does not work on arrays as you may think. null, true, false and every number and string are less than any array, see Type and Value Order. Your timestamp array will always be greater than any given timestamp string. What you probably want instead is an array comparison operator like ALL >=.
An expression dates[*].startsAt can not be used as bind parameter. With a document structure without array like { "date": { "startsAt": "..." } } it would be perfectly fine to bind ["date", "startsAt"] as p1 or p2. Note how the bind parameter value is an array of strings. "date.startsAt" on the other hand would describe the path for a top-level attribute
{ "date.startsAt": ... } and not a nested attribute startsAt of the top-level attribute date like { "date": { "startsAt": ... } }.
What your do with dates[*].startsAt is describing a top-level attribute like
{ "dates[*].startsAt": ... }, which does not exist. ["dates[*]", "startsAt"] does not work either. If you want to use the array expansion expression, then you have to write it like c.#p1a[*].#p1b in your query and use the bind parameters { "p1a": "dates", "p2a": "startsAt" }.
Query:
FOR c IN ##collectionName
FILTER c.#p0 == #v0
FILTER c.#p1a[*].#p1b ALL >= #v1
FILTER c.#p2a[*].#p2b ALL < #v2
LIMIT #count
RETURN c
bindVars:
{
"#collectionName": "Event",
"p0": "isPublished",
"v0": true,
"p1a": "dates",
"p1b": "startsAt",
"v1": "2018-06-01T04:00:00.000Z",
"p2a": "dates",
"p2b": "startsAt",
"v2": "2018-07-01T04:00:00.000Z",
"count": 9
}
When declaring a closure we can query it for the number of accepted parameters using:
Closure#getMaximumNumberOfParameters()
So for example:
def closure = { String param ->
}
println(closure.maximumNumberOfParameters)
Will output:
1
Why does the method declare the number of parameters as a maximum rather than a constant?
In what situation will the return value of this method differ from the actual number of parameters declared in the closure?
Default parameters?
def closure = { String param = 'something' ->
}
So you can technically call
closure()
And
closure('something else')
I'm trying to write a mini DSL for some specific task. For this purpose I've been trying to solve a problem like this below (without using parantheses):
give me 5 like romanLetter
give me 5 like word
where the first line would return "V" and the second "five"
My definitions for the first part give me 5 look like this
def give = { clos -> clos() }
def me = { clos -> [:].withDefault { it
println it}
}
and then give me 5 prints 5
The problem is how to add more metaclass methods on the right. E.g.
give me 5 like romanLetter -> prints V OR
give me 5 like word -> prints five
my intuition is that I define like as
Object.metaClass.like = {orth -> if (orth.equals("roman")){ println "V"}
else {println "five"} }
this metaClass method like works only if there is a returned value from the left to be applied to, right? I tried adding a return statement in all of the closures which are on the left side but I always receive
groovy.lang.MissingPropertyException: No such property: like
for class: com.ontotext.paces.rules.FERulesScriptTest ...
do you have an idea how shall I do?
========================================
Here is the application of what I'm asking for.
I want to make a rule as follows
add FEATURE of X opts A,B,C named Y
where add is a closure, of, opts and named are MetaClass methods (at least that's how i imagine it), X, A, B, C, Y are parameters most probably strings and FEATURE is either a MetaClass property, or a closure without arguments or a closure with arguments.
If FEATURE does not take arguments then it is enough that add takes FEATURE as argument and returns a value on which
Object.metaClass.of will be executed with parameter X
Object.metaClass.opts will be executed on the returned by OF value with parameters A, B, C
Object.metaClass.named will be executed on the returned by opts value with parameter Y
each one of these metaclass methods sets its parameter as a value in a map, which is passed to a JAVA method when named is called.
I'm not sure this is the best solution for such a problem, but it seems to me such for the moment. The problem is if FEATURE is not a property itself but a closure which takes argument (e.g. feature1 ARG1). Then
add feature1 ARG1 of X opts A,B,C named Y
and this is the case which I'm stuck with. add feature1 ARG1 is the give me 5 part and I'm trying to add the rest to it.
========================================================
EXAMPLES:
I need to have both of the following working:
add contextFeature "text" of 1,2,3 opts "upperCase" named "TO_UPPER"
add length named "LENGTH"
where in the first case by parsing the rule, whenever each metaclass method of, opts, named is called I fill in the corresponding value in the following map:
params = [feature: "text",
of: 1,2,3,
opts: "upperCase",
named: "TO_UPPER"]
ones this map is filled in, which happens when named is parsed, I call a java method
setFeature(params.of, params.named, params.opts, params.feature)
In the second case length is predefined as length = "length", params values will be only
params = [feature : length,
of: null,
opts: null,
named: "LENGTH"]
and since of is null another java method will be called which is addSurfaceFeature(params.feature, params.named). The second case is more or less streight forward, but the first one is the one I can't manage.
Thanks in advance! Iv
You can do this sort of thing... Does that get you close?
def contextFeature( type ) {
"FEATURE_$type"
}
// Testing
new IvitaParser().parse {
a = add text of 1,2,3 opts "upperCase" named "TO_UPPER"
b = add length named "LENGTH"
c = add contextFeature( "text" ) of 1,2,3 opts "upperCase" named "TO_UPPER"
}
assert a == [feature:'text', of:[1, 2, 3], opts:'upperCase', named:'TO_UPPER']
assert b == [feature:'length', of:null, opts:null, named:'LENGTH']
assert c == [feature:'FEATURE_text', of:[1, 2, 3], opts:'upperCase', named:'TO_UPPER']
// Implementation
class IvitaParser {
Map result
def parse( Closure c ) {
c.delegate = this
c.resolveMethod = Closure.DELEGATE_FIRST
c()
}
def propertyMissing( String name ) {
name
}
def add( String param ) {
result = [ feature:param, of:null, opts:null, named:null ]
this
}
def of( Object... values ) {
result.of = values
this
}
def named( String name ) {
result.named = name
result
}
def opts( String opt ) {
result.opts = opt
this
}
}
You can even get rid of the quotes on the definition:
a = add text of 1,2,3 opts upperCase named TO_UPPER
b = add length named LENGTH
As the propertyMissing method just converts unknown properties into a String of their name
This is OK
def variables=[
['var1':'test1'],
['var2':'test2'],
['var3':'test3']
]
println "${variables.size()}"
variables.each{entry ->
println "${entry} "
}
I got:
3
[var1:test1]
[var2:test2]
[var3:test3]
but this caused problems
def variables=[
['var1':'test1'],
['var2':'test2'],
['var3':'test3']
]
println "${variables.size()}"
variables.each{entry ->
println "${entry.key} "
}
since I got:
3
null
null
null
I'm expecting:
3
var1
var2
var3
what's wrong with my code?
thank you!!!
You want:
def variables=[
'var1':'test1',
'var2':'test2',
'var3':'test3'
]
println variables.size()
variables.each{entry ->
println entry.key
}
Before you had an ArrayList containing three LinkedHashMap objects. The above code is a single LinkedHashMap with three entries. You also don't need string interpolation, so I removed it.
Matthew's solution works great, and it's probably what you wanted (a simpler data structure to begin with).
However, in case you really wanted variables to be a list of three maps (as per your question), then this how you could get your desired output:
def variables=[
['var1':'test1'],
['var2':'test2'],
['var3':'test3']
]
println "${variables.size()}"
variables.each{ entry->
entry.each {
println it.key
}
}
In the outer closure, every entry is a map. So we iterate through each of those maps using the inner closure. In this inner closure, every it closure-param is a key:value pair, so we just print its key using it.key.
Like Matthew, I've also removed the string interpolation since you don't need it.