Dotted strings to properties - groovy

I've got a hash of strings similar to this:
Map map = ['a.b.c': 'Hi']
... that I need to use in gradle to expand an expression like this:
This is a greeting: ${a.b.c}
If I use the gradle copy task with expand I will get an error message 'No such property: a'.
Is there any way to get gradle/groovy to convert that map into the properties I need to resolve?

I couldn't find a built-in answer, but it here is a complete, self-contained method that can be used as a meta method on Map to do what you want:
Map map = ['a.b.c': 'Hi', 'a.b.d': 'Yo', 'f.g.h': 'Howdy']
Map.metaClass.expandKeys = { separator = '.' ->
def mergeMaps = { a, b ->
b.each{ k, v ->
if(a[k] && (v instanceof Map)) {
mergeMaps(a[k], v)
} else {
a[k] = v
}
}
a
}
delegate.inject([:]){ result, k, v ->
mergeMaps(result, k.tokenize(separator).reverse().inject(v){last, subkey -> [(subkey):last] })
}
}
assert map.expandKeys() == [a:[b:[c:"Hi", d:"Yo"]], f:[g:[h:"Howdy"]]]
It also allows for different separators than ., just pass the separator into the expandKeys method
If you want to use it like a normal function, then you can do this instead:
Map map = ['a.b.c': 'Hi', 'a.b.d': 'Yo', 'f.g.h': 'Howdy']
def expandKeys = { Map input, separator = '.' ->
def mergeMaps = { a, b ->
b.each{ k, v ->
if(a[k] && (v instanceof Map)) {
mergeMaps(a[k], v)
} else {
a[k] = v
}
}
a
}
input.inject([:]){ result, k, v ->
mergeMaps(result, k.tokenize(separator).reverse().inject(v){last, subkey -> [(subkey):last] })
}
}
assert expandKeys(map) == [a:[b:[c:"Hi", d:"Yo"]], f:[g:[h:"Howdy"]]]
The main trick, besides merging the maps, is to split then reverse each key. Then the final hierarchy can be built up backwards. Also, there may be a better way to handle the merge, because I don't like the hanging a at the end.

I don't know anything about Gradle but maybe this will help....
If you have a Map
Map map = ['a.b.c': 'Hi']
Then you can't retrieve the value 'Hi' using
map.a.b.c
Instead, you must use:
map.'a.b.c'
or
map['a.b.c']

I'm not exactly sure what your needs are, but if you just need to replace property-like tokens and don't need the full power of Groovy templates, the filter() method in combination with Ant's ReplaceTokens class is a safer (and faster) bet than expand(). See Filtering files in the Gradle User Guide.

Related

Nested comprehension in Kotlin

Suppose I have the following nested for loop:
val test = mutableSetOf<Set<Int>>()
for (a in setA) {
for (b in setB) {
if (a.toString().slice(2..3) == b.toString().slice(0..1)) {
test.add(setOf(a,b))
}
}
}
In python, I could do a simple comprehension as
test = {[a,b] for a in setA for b in setB if a.str()[2:3] == b.str[0:1]}
I'm having a helluva time converting this to Kotlin syntax. I know for a single for loop with a conditional, I could use a filter and map to get the desired results (using the idiom: newSet = oldSet.filter{ conditional }.map { it }, but I cannot for the life of me figure out how to do the nesting this way.
This is what IDEA proposes:
for (a in setA)
setB
.filter { a.toString().slice(2..3) == it.toString().slice(0..1) }
.mapTo(test) { setOf(a, it) }
I do not think there is much to do about it. I think their is no native approach that is similar to the Python one, but it already actually is in terms of length very similar because only the functions and their names make it that long.
If we take a look a this hypothetical example:
for (a in setA) setB.f { a.t().s(2..3) == it.t().s(0..1) }.m(test) { setOf(a, it) }
It is not far from the Python example. The Python syntax is just very different.
(functions for that hypothesis)
fun <T> Iterable<T>.f(predicate: (T) -> Boolean) = filter(predicate)
fun String.s(range: IntRange) = slice(range)
fun <T, R, C : MutableCollection<in R>> Iterable<T>.m(destination: C, transform: (T) -> R) = mapTo(destination, transform)
fun Int.t() = toString()
If Kotlin doesn't have it, add it. Here is a cartesian product of the two sets as a sequence:
fun <F,S> Collection<F>.cartesian(other: Collection<S>): Sequence<Pair<F,S>> =
this.asSequence().map { f -> other.asSequence().map { s-> f to s } }.flatten()
Then use that in one of many ways:
// close to your original nested loop version:
setA.cartesian(setB).filter { (a,b) ->
a.toString().slice(2..3) == b.toString().slice(0..1)
}.forEach{ (a,b) -> test.add(setOf(a,b)) }
// or, add the pair instead of a set if that makes sense as alternative
setA.cartesian(setB).filter { (a,b) ->
a.toString().slice(2..3) == b.toString().slice(0..1)
}.forEach{ test2.add(it) }
// or, add the results of the full expression to the set at once
test.addAll(setA.cartesian(setB).filter { (a,b) ->
a.toString().slice(2..3) == b.toString().slice(0..1)
}.map { (a,b) -> setOf(a,b) } )
// or, the same as the last using a pair instead of 2 member set
test2.addAll(setA.cartesian(setB).filter { (a,b) ->
a.toString().slice(2..3) == b.toString().slice(0..1)
})
The above examples use these variables:
val test = mutableSetOf<Set<Int>>()
val test2 = mutableSetOf<Pair<Int,Int>>()
val setA = setOf<Int>()
val setB = setOf<Int>()

Split string every n characters

What would be an idiomatic way to split a string into strings of 2 characters each?
Examples:
"" -> [""]
"ab" -> ["ab"]
"abcd" -> ["ab", "cd"]
We can assume that the string has a length which is a multiple of 2.
I could use a regex like in this Java answer but I was hoping to find a better way (i.e. using one of kotlin's additional methods).
Once Kotlin 1.2 is released, you can use the chunked function that is added to kotlin-stdlib by the KEEP-11 proposal. Example:
val chunked = myString.chunked(2)
You can already try this with Kotlin 1.2 M2 pre-release.
Until then, you can implement the same with this code:
fun String.chunked(size: Int): List<String> {
val nChunks = length / size
return (0 until nChunks).map { substring(it * size, (it + 1) * size) }
}
println("abcdef".chunked(2)) // [ab, cd, ef]
This implementation drops the remainder that is less than size elements. You can modify it do add the remainder to the result as well.
A functional version of chunked using generateSequence:
fun String.split(n: Int) = Pair(this.drop(n), this.take(n))
fun String.chunked(n: Int): Sequence<String> =
generateSequence(this.split(n), {
when {
it.first.isEmpty() -> null
else -> it.first.split(n)
}
})
.map(Pair<*, String>::second)
Output:
"".chunked(2) => []
"ab".chunked(2) => [ab]
"abcd".chunked(2) => [ab, cd]
"abc".chunked(2) => [ab, c]

Grails convert String to Map with comma in string values

I want convert string to Map in grails. I already have a function of string to map conversion. Heres the code,
static def StringToMap(String reportValues){
Map result=[:]
result=reportValues.replace('[','').replace(']','').replace(' ','').split(',').inject([:]){map,token ->
List tokenizeStr=token.split(':');
tokenizeStr.size()>1?tokenizeStr?.with {map[it[0]?.toString()?.trim()]=it[1]?.toString()?.trim()}:tokenizeStr?.with {map[it[0]?.toString()?.trim()]=''}
map
}
return result
}
But, I have String with comma in the values, so the above function doesn't work for me. Heres my String
[program_type:, subsidiary_code:, groupName:, termination_date:, effective_date:, subsidiary_name:ABC, INC]
my function returns ABC only. not ABC, INC. I googled about it but couldnt find any concrete help.
Generally speaking, if I have to convert a Stringified Map to a Map object I try to make use of Eval.me. Your example String though isn't quite right to do so, if you had the following it would "just work":
// Note I have added '' around the values.
​String a = "[program_type:'', subsidiary_code:'', groupName:'', termination_date:'', effective_date:'', subsidiary_name:'ABC']"
Map b = Eval.me(a)​
// returns b = [program_type:, subsidiary_code:, groupName:, termination_date:, effective_date:, subsidiary_name:ABC]
If you have control of the String then if you can create it following this kind of pattern, it would be the easiest solution I suspect.
In case it is not possible to change the input parameter, this might be a not so clean and not so short option. It relies on the colon instead of comma values.
​String reportValues = "[program_type:, subsidiary_code:, groupName:, termination_date:, effective_date:, subsidiary_name:ABC, INC]"
reportValues = reportValues[1..-2]
def m = reportValues.split(":")
def map = [:]
def length = m.size()
m.eachWithIndex { v, i ->
if(i != 0) {
List l = m[i].split(",")
if (i == length-1) {
map.put(m[i-1].split(",")[-1], l.join(","))
} else {
map.put(m[i-1].split(",")[-1], l[0..-2].join(","))
}
}
}
map.each {key, value -> println "key: " + key + " value: " + value}
BTW: Only use eval on trusted input, AFAIK it executes everything.
You could try messing around with this bit of code:
String tempString = "[program_type:11, 'aa':'bb', subsidiary_code:, groupName:, termination_date:, effective_date:, subsidiary_name:ABC, INC]"
List StringasList = tempString.tokenize('[],')
def finalMap=[:]
StringasList?.each { e->
def f = e?.split(':')
finalMap."${f[0]}"= f.size()>1 ? f[1] : null
}
println """-- tempString: ${tempString.getClass()} StringasList: ${StringasList.getClass()}
finalMap: ${finalMap.getClass()} \n Results\n finalMap ${finalMap}
"""
Above produces:
-- tempString: class java.lang.String StringasList: class java.util.ArrayList
finalMap: class java.util.LinkedHashMap
Results
finalMap [program_type:11, 'aa':'bb', subsidiary_code:null, groupName:null, termination_date:null, effective_date:null, subsidiary_name:ABC, INC:null]
It tokenizes the String then converts ArrayList by iterating through the list and passing each one again split against : into a map. It also has to check to ensure the size is greater than 1 otherwise it will break on f[1]

Difference of two maps in groovy using collectEntries

I am trying to find the difference between values in two maps
#Test
void testCollecEntries() {
def mapOne= ["A":900,"B":2000,"C":1500]
def maptwo = ["A":1000,"D":1500,"B":1500]
def balanceMap = maptwo.collectEntries { key, value-> [key:value-mapOne[key]] }
println balanceMap
}
I am trying to find the difference of values from maptwo with that of the values from mapOne. If the entry doesn't exist i need to ignore. This gives me a null pointer exception.
Appreciate any help.
It will throw NPE because you are looking for key "D" in mapOne which is not available.
You can avoid that by a null safe operation and default value to 0.
def one= [A:900, B:2000, C:1500]
def two = [A:1000, D:1500, B:1500]
def result = two.collectEntries{k,v -> [k, (v - (one[k]?:0))]}
println result
//Print
[A:100, D:1500, B:-500]
In case, you want to consider the common keys then use:
def result = two.collectEntries{k,v -> one[k] ? [k, (v - one[k])] : [:]}
//or
//def result = two.collectEntries{k,v -> (k in one.keySet()) ? [k, (v - one[k])] : [:]}
//Print
[A:100, B:-500]
You could look at this good example: http://groovyconsole.appspot.com/script/364002

Groovier way of manipulating the list

I have two list like this :
def a = [100,200,300]
def b = [30,60,90]
I want the Groovier way of manipulating the a like this :
1) First element of a should be changed to a[0]-2*b[0]
2)Second element of a should be changed to a[1]-4*b[1]
3)Third element of a should be changed to a[2]-8*b[2]
(provided that both a and b will be of same length of 3)
If the list changed to map like this, lets say:
def a1 = [100:30, 200:60, 300:90]
how one could do the same above operation in this case.
Thanks in advance.
For List, I'd go with:
def result = []
a.eachWithIndex{ item, index ->
result << item - ((2**index) * b[index])
}
For Map it's a bit easier, but still requires an external state:
int i = 1
def result = a.collect { k, v -> k - ((2**i++) * v) }
A pity, Groovy doesn't have an analog for zip, in this case - something like zipWithIndex or collectWithIndex.
Using collect
In response to Victor in the comments, you can do this using a collect
def a = [100,200,300]
def b = [30,60,90]
// Introduce a list `c` of the multiplier
def c = (1..a.size()).collect { 2**it }
// Transpose these lists together, and calculate
[a,b,c].transpose().collect { x, y, z ->
x - y * z
}
Using inject
You can also use inject, passing in a map of multiplier and result, then fetching the result out at the end:
def result = [a,b].transpose().inject( [ mult:2, result:[] ] ) { acc, vals ->
acc.result << vals.with { av, bv -> av - ( acc.mult * bv ) }
acc.mult *= 2
acc
}.result
And similarly, you can use inject for the map:
def result = a1.inject( [ mult:2, result:[] ] ) { acc, key, val ->
acc.result << key - ( acc.mult * val )
acc.mult *= 2
acc
}.result
Using inject has the advantage that you don't need external variables declared, but has the disadvantage of being harder to read the code (and as Victor points out in the comments, this makes static analysis of the code hard to impossible for IDEs and groovypp)
def a1 = [100:30, 200:60, 300:90]
a1.eachWithIndex{item,index ->
println item.key-((2**(index+1))*item.value)
i++
}

Resources