Transform while loop into closure in Groovy - groovy

How to transform the following while code in groovy into groovy closures. Is it possible ?
List<String> list = ["s1", "s2", "s3:", "s4", "s5"]
List<String> res = []
int index = 0
while (index < res.size()) {
String cur = list[index]
if (cur.endsWith(":")) {
String next = list[index + 1] /*out of bounds won't happen*/
res.add(cur + "\n" + next)
index = index + 2
} else {
res.add(cur)
index++
}
}
assert res == ["s1", "s2", "s3:s4", "s5"]
I thought of the following solution, but seems like its not working:
List<String> list = ["s1", "s2", "s3:", "s4", "s5"]
List<String> res = list.eachWithIndex { int index, String cur ->
if (cur.endsWith(":")) {
String next = list[index + 1] /*Out of bounds won't happen*/
index = index + 2
return cur + "\n" + next
}
return cur
} as List<String>
assert res == ["s1", "s2", "s3:s4", "s5"]
I get this remark in my above code that "index = index + 2 is never used"

Consider the inject method (though this does mutate elements of the list, which seems dicey):
def list = ["s1", "s2", "s3:", "s4", "s5"]
def res = list.inject([], { acc, item ->
def lastItem = acc.isEmpty() ? null : acc.last()
if (lastItem?.endsWith(":")) {
acc[-1] += item
} else {
acc << item
}
acc
})
assert res == ["s1", "s2", "s3:s4", "s5"]
Here, acc is the "accumulated result list" and item is the current item in the original list. From here: the inject method is also known as reduce or fold in other languages.

Another answer similar to Michael Easter's with inject:
def list = ["s1", "s2", "s3:", "s4", "s5"]
def res = list.inject([]) { acc, item ->
if (acc && acc[-1].endsWith(':'))
item = acc.removeLast() + item
acc + item
}
assert res == ["s1", "s2", "s3:s4", "s5"]
with perhaps a few less characters.

Here is my second solution. It doesn't use closures but is arguably "functional". Note that it is not a great idea for large lists:
def list = ["s1", "s2", "s3:", "s4", "s5"]
def str = list.join(",")
// e.g. s1,s2,s3:,s4,s5
str = str.replaceAll(/\:,/, ":")
// e.g. s1,s2,s3:s4,s5
def res = str.split(",")
assert res == ["s1", "s2", "s3:s4", "s5"]

Not sure if this is the grooviest of all existing solution, but it works for your example:
list = ["s1", "s2", "s3:", "s4", "s5:"]
res = []
list.eachWithIndex
{e, i -> if (i > 0 && list[i-1].endsWith(":")) {return}
if (e.endsWith(":") && i < list.size-1) {res.add("${e}${list[i+1]}") } else {res.add(e)}}
which returns [s1, s2, s3:s4, s5:] as expected
You process each element in the list and
if the preceding element exists and is suffixed: do nothing
if the current element is suffixed and is not last: concatenate the current element with the next.

Related

I tried to apply function "cliff" from installed package named "lace", I got the following error "TypeError: 'map' object is not subscriptable"

I read about the error and try to cast map into list, but the error still appeared, I will show you the main file that contain the error.
def power(L, C, Erange):
assert len(L) == len(C), "The L and C must be corresponded to each other"
E = copy.deepcopy(Erange)
E[0] -= 1
power_table = dict()
for c in set(C): # for each type of class
first = [index for index, eachc in enumerate(C) if eachc == c]
rest = [index for index, eachc in enumerate(C) if eachc != c]
p_first = len(first) / len(L)
p_rest = len(rest) / len(L)
powerc = []
for u, v in zip(E[0:-1], E[1:]): # checking the range (u,v]
like_first = sum([1 for i in first if u < L[i] <= v]) / len(first) * p_first
like_rest = sum([1 for i in rest if u < L[i] <= v]) / len(rest) * p_rest
try:
powerc.append((like_first ** 2 / (like_first + like_rest)))
except ZeroDivisionError:
powerc.append(0)
power_table[c] = powerc
power = []
for l, c in zip(L, C):
for e_cursor in range(len(E)):
if E[e_cursor] >= l: break
power.append(round(power_table[c][e_cursor - 1], 2))
return power
def cliff_core(data, percentage, obj_as_binary, handled_obj=False):
if len(data) < 50:
logging.debug("no enough data to cliff. return the whole dataset")
return range(len(data))
classes = map(toolkit.str2num, zip(*data)[-1])
if not handled_obj:
if obj_as_binary:
classes = [1 if i > 0 else 0 for i in classes]
else:
classes = toolkit.apply_bin_range(classes)
data_power = list()
for col in zip(*data):
col = map(toolkit.str2num, col)
E = toolkit.binrange(col)
data_power.append(power(col, classes, E))
data_power = map(list, zip(*data_power)) # transposing the data power
row_sum = [sum(row) for row in data_power]
index = range(len(data))
zips = zip(data, classes, row_sum, index)
output = list()
for cls in set(classes):
matched = filter(lambda z: z[1] == cls, zips)
random.shuffle(matched)
matched = sorted(matched, key=lambda z: z[2], reverse=True)
if len(matched) < 5:
output.extend([m[3] for m in matched]) # all saved
continue
for i in range(int(len(matched) * percentage)):
output.append(matched[i][3])
return sorted(output)
def cliff(attribute_names,data_matrix,independent_attrs,objective_attr,objective_as_binary=False,
cliff_percentage=0.4):
ori_attrs, alldata = attribute_names, data_matrix # load the database
alldata_t = map(list, zip(*alldata))
valued_data_t = list()
for attr, col in zip(ori_attrs, alldata_t):
if attr in independent_attrs:
valued_data_t.append(col)
valued_data_t.append(alldata_t[attribute_names.index(objective_attr)])
alldata = map(list, zip(*valued_data_t))
alldata = map(lambda row: map(toolkit.str2num, row), alldata) # numbering the 2d table
after_cliff = cliff_core(alldata, cliff_percentage, objective_as_binary)
res = [data_matrix[i] for i in after_cliff]
return res

Switch values in two multi-nested maps in groovy

I am writing a method in order to replace values in one of two maps depending on if the keys match together. For example lets say we have two maps:
def oldmap = [emails: 1, files:[permissions: 3, config:4]]
def replacementmap = [emails: 2, permissions: 5]
// I want this old map to have updated values for keys that match after the method is called.
replacementPermissions(oldmap, replacementmap)
print oldmap
//prints [emails: 2, files:[permissions: 5, config:4]]
I have written this method shown below that works for one layered nested map, but I noticed a recursive solution would be a better option instead because my solution wouldn't work for multi-layered nested maps.
def replacePermissions(read, params){
read.each{x,y ->
temp = x
if (y instanceof Map){
y.each{x2,y2->
temp = x2
params.each{xx,yy->
if (temp == xx) y.put(x2, yy)
if (yy instanceof Map){
yy.each{aa, bb->
if (temp == aa) y.put(x2, bb)
}
}
}
}
}
else{
params.each{x1,y1->
if (temp == x1) read.put(x, y1)
}
}
}
return read
}
I am having trouble wrapping my head around how to think of a recursive solution for traversing and matching keys to swap values.
Right now I have this with
No signature of method: main.swapsearch() is applicable for argument types: (java.util.LinkedHashMap, java.lang.Integer) values: [[lol3:[lol5:4, lol6:10], lol4:4], 5]
Possible solutions: swapsearch(java.util.Map, java.util.Map)
def swapsearch(Map mapa, Map mapb){
mapa.each{x,y ->
temp = x
mapb.each{x1, y1->
if (y instanceof Map || y1 instanceof Map){
swapsearch(y, y1)
}
else if (temp == x1){
mapb.put(x, y1)
}
}
}
mapb
}
Map oldmap = [lol1: 1, lol2:[lol3: [lol5: 4, lol6: 10], lol4:4]]
Map newmap = [lol1: 5, lol5: 111]
print newmap
newmap = swapsearch(oldmap, newmap)
print newmap
SOLUTION with help of #injecteer:
I was able to do a simple recursion as so:
// Make sure repl map is flattened
def switchMaps( Map src, Map repl ){
src.each{key,value ->
if( repl.containsKey(key) ){
src.put(key, repl[key])
}
else if( value && value instanceof Map ){
replacemaps (value, repl)
}
}
src
}
Some simple recursion:
Map oldmap = [emails: 1, files:[permissions: 3, config:4, deep:[ deeper:[ verydeep:1 ] ] ] ]
Map replacementmap = [emails: 2, permissions: 5, verydeep:400]
def replace( Map src, Map repl ){
src.each{
if( repl.containsKey( it.key ) )
it.value = repl[ it.key ]
else if( it.value && Map.isAssignableFrom( it.value.getClass() ) )
replace it.value, repl
}
}
replace oldmap, replacementmap
assert oldmap.emails == replacementmap.emails
assert oldmap.files.permissions == replacementmap.permissions
assert oldmap.files.deep.deeper.verydeep == replacementmap.verydeep
If your replacementmap is also nested, you have to pre-process it before using, like so:
Map replacementmap = [emails: 2, permissions: 5, deep:[ config:300, toodeep:[ verydeep:400 ] ] ]
Map flatten( Map m, Map res = [:] ) {
m.each{ k, v ->
if( !v ) return
if( Map.isAssignableFrom( v.getClass() ) ) flatten v, res
else res[ k ] = v
}
res
}
Map flatRepl = flatten replacementmap
assert flatRepl == [emails:2, permissions:5, config:300, verydeep:400]

Nested closure resolution different between methods and properties?

When a closure's resolveStrategy is set to DELEGATE_ONLY or DELEGATE_FIRST, resolution is different in nested closures between methods and properties of the delegate. For example, in the following, x resolves to f's delegate (what I expect), but keySet() resolves to g's delegate.
​def g = {->
def f = {
{-> [x, keySet()]}()
}
f.resolveStrategy = Closure.DELEGATE_ONLY
f.delegate = [x: 1, f: 0]
f()
}
g.delegate = [x: 0, g: 0]
g()
​
Result: [1, ['x', 'g']]
Whereas without the nested closure
def g = {->
def f = {
[x, keySet()]
}
f.resolveStrategy = Closure.DELEGATE_ONLY
f.delegate = [x: 1, f: 0]
f()
}
g.delegate = [x: 0, g: 0]
g()
Result: [1, ['x', 'f']]
Is this behavior expected and documented somewhere? Is it a bug?
I believe it is a bug. If you change the map for a Expando it behaviors differently:
f = {
g = {
{ -> keySet() }()
}
g.delegate = new Expando(a: 1000, b: 900, c: 800, keySet: { 'g keyset' })
g.resolveStrategy = Closure.DELEGATE_ONLY
g()
}
f.delegate = new Expando(a: 90, x: 9, y: 1, keySet: { 'f keyset' })
assert f() == 'g keyset'
f = {
g = {
{ -> keySet() }()
}
g.delegate = [a: 1000, b: 900, c: 800]
g.resolveStrategy = Closure.DELEGATE_ONLY
g()
}
f.delegate = [a: 90, x: 9, y: 1]
assert f().toList() == ['a', 'b', 'c'] // fails :-(
Maybe filling a JIRA?
I discovered a workaround if you never want to fall through to the owner (ie, DELEGATE_ONLY): you can set both the delegate and the owner to the same value:
def g = {->
def f = {
{-> [x, keySet()]}()
}
def d = [x: 1, f: 0]
f = f.rehydrate(d, d, f.thisObject)
f()
}
g.delegate = [x: 0, g: 0]
g()
Result: [1, ["x", "f"]]
Note that f.owner = d does not work: while there is no error, it seems to be a no-op. You must use rehydrate.

Groovy: NumberFormatException: For input string: "-5.2"

I have this code:
static def parseString(String inputRow, Particle particle) {
//input row is:
//static final inputRow = "1 -5.2 3.8"
def map = inputRow.split()
particle.mass = Integer.parseInt(map[0])
particle.x = Integer.parseInt(map[1])
particle.y = Integer.parseInt(map[2])
}
This code is throwing this error:
java.lang.NumberFormatException: For input string: "-5.2"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:492)
at java.lang.Integer.valueOf(Integer.java:582)
at RepulsionForce.parseString(RepulsionForce.groovy:13)
at RepulsionForceTest.string should be parsed into particles(RepulsionForceTest.groovy:27)
How do I avoid this exception?
You can cut down redundant lines as shown below where particle is a map:
def arr = inputRow.split()
def mass, x, y
(mass, x, y) = arr*.toDouble()
def particle = [:] << [mass: mass, x: x, y: y]
Missed the part where Particle class has been referred instead.
Whoops, figured this out... 5.2 is not an integer.
static def parseString(String inputRow, Particle particle) {
def map = inputRow.split()
particle.mass = Double.parseDouble(map[0])
particle.x = Double.parseDouble(map[1])
particle.y = Double.parseDouble(map[2])
}
Here is one way to do it... Answer within a full solution:
// setup
class Particle {
def mass
def x
def y
}
def particle = new Particle()
def inputRow = "1 -5.2 3.8"
def fieldsByIndex = [0: "mass", 1: "x", 2: "y"]
// answer
inputRow.split().eachWithIndex { val, index ->
def field = fieldsByIndex.get(index)
particle."${field}" = val.toDouble()
}
// output
println "mass : " + particle.mass
println "x : " + particle.x
println "y : " + particle.y
Explanation: You Mistakenly trying to parse Double number to Integer number and it's not applicable, not sharing the same type ( integer != double) , and not gonna work - Therefore you getting java.lang.NumberFormatException: For input string exception which explicitly inform you for the type issue.
Solution: Convert it to to double using Double Class.
Double.parseDouble(map[i])

Remove overlapping ranges from a list of ranges Groovy

I need to write a code where I have a list of Ranges in Groovy. And I need to created a fresh list from that where all the ranges dont overlap.
For example if the input is: [13..15 , 14..16]
I should be able to create a list which has either [13..16] or [13..14, 14..16]
I would really appreciate any help. I have written the following code for now but its not working one bit:
def removeOverlapInRanges(ranges)
{
def cleanedRanges = []
def overLapFound = false
def rangeIsClean = true
def test = "ranges"
ranges.each
{
range->
def index = ranges.indexOf(range)
while (index < ranges.size() -1)
{
if (ranges.get(index + 1).disjoint(range) == false)
{
overLapFound = true
rangeIsClean = false
def nextRange = ranges.get(index + 1)
if (range.from > nextRange.from && range.to < nextRange.to)
cleanedRanges.add(range.from..range.to)
else if (range.from < nextRange.from && range.to < nextRange.to)
cleanedRanges.add(range.from..nextRange.to)
else if (range.from > nextRange.from && range.to > nextRange.to)
cleanedRanges.add(nextRange.from..range.to)
}
index = index + 1
}
if (rangeIsClean)
cleanedRanges.add(range)
rangeIsClean = true
test = test + cleanedRanges
}
cleanedRanges.add(0, cleanedRanges.get(cleanedRanges.size()-1))
cleanedRanges.remove(cleanedRanges.size() - 1)
if (overLapFound)
return removeOverlapInRanges(cleanedRanges)
else
return cleanedRanges
}
I passed [12..13, 17..19, 18..22,17..19, 22..23,19..20 ]
And in return I got [12..13]
Thanks in advance for any input!!
I got this:
List<Range> simplify( List<Range> ranges ) {
ranges.drop( 1 ).inject( ranges.take( 1 ) ) { r, curr ->
// Find an overlapping range
def ov = r.find { curr.from <= it.to && curr.to >= it.from }
if( ov ) {
ov.from = [ curr.from, ov.from ].min()
ov.to = [ curr.to, ov.to ].max()
simplify( r )
}
else {
r << curr
}
}
}
def ranges = [ 12..13, 17..19, 18..22, 17..19, 22..23, 19..20 ]
assert simplify( ranges ) == [ 12..13, 17..23 ]
ranges = [ -2..3, -5..-2 ]
assert simplify( ranges ) == [ -5..3 ]
ranges = [ 3..1, 1..5 ]
assert simplify( ranges ) == [ 5..1 ] // reversed as first range is reversed
ranges = [ 1..5, 3..1 ]
assert simplify( ranges ) == [ 1..5 ]
ranges = [ 1..5, 3..1, -1..-4 ]
assert simplify( ranges ) == [ 1..5, -1..-4 ]
ranges = [ 1..5, -6..-4, 3..1, -1..-4 ]
assert simplify( ranges ) == [ 1..5, -6..-1 ]
ranges = [ 1..3, 5..6, 3..5 ]
assert simplify( ranges ) == [ 1..6 ]
Though there are probably edge cases... So I'll do a bit more testing...
The following with create a simple list of your unique numbers:
def ranges = [12..13, 17..19, 18..22,17..19, 22..23,19..20 ];
def range = ranges.flatten().unique().sort()
Here is a slightly different approach that yields some nice helper methods:
def parseToRangeString(range)
{
String result = "";
range.eachWithIndex{cur,i->
def nex = range[i+1]
def start = !result || result.endsWith(",")
def cont = cur == nex?.minus(1)
if (start && cont) //starting a new section and the next item continues this seq (starting a range = 1,10)
result += "$cur-"
else if (!cont && nex) //not continuing the seq and there are more nums to process (end with more = 6,8)
result += "$cur,"
else if (!cont && !nex) //not continuing the seq but there are no more nums to process (very end = 11)
result += cur
}
return result
}
def toRange(rangeStr)
{
def ranges = rangeStr.split(",").collect{
def range = it.split("-");
new IntRange(range[0] as int, range[-1] as int)
}
}
List.metaClass.toRangeString = {
parseToRangeString(delegate)
}
List.metaClass.toRange = {
def rangeStr = parseToRangeString(delegate)
toRange(rangeStr)
}
def ranges = [12..13, 17..19, 18..22,17..19, 22..23,19..20 ];
def list = ranges.flatten().unique().sort()
assert "12-13,17-23" == list.toRangeString()
assert [12..13,17..23] == list.toRange();

Resources