Groovy custom sort a map by value - sorting

I have a map such as
m=[
"james":"silly boy",
"janny":"Crazy girl",
"jimmy":"funny man",
"georges":"massive fella"
];
I want to sort this map by its value but ignoring the case (this is really why the custom sort is needed). Hence I figured I had to implement a custom sort using a closure. But I'm brand new at Groovy and been struggling to get this very simple task done!
The desired results would be:
["janny":"Crazy girl", "jimmy":"funny man", "georges":"massive fella", "james":"silly boy"]
Thanks !

To sort with case insensitive, use
m.sort { it.value.toLowerCase() }

Assuming you mean you want to sort on value, you can just do:
Map m =[ james :"silly boy",
janny :"Crazy girl",
jimmy :"funny man",
georges:"massive fella" ]
Map sorted = m.sort { a, b -> a.value <=> b.value }

If somebody is looking for how to make it work in the Jenkins pipeline script, you will have to create a separate method with #NonCPS annotation for that:
#NonCPS
def getSorted(def mapSizeMap){
mapSizeMap.sort(){ a, b -> b.value <=> a.value }
}
And then call it from the pipeline script.
def sortedMapZoneMap = getSorted(mapZonesMap)
You can of course apply "case sensitive" logic on top.

BTW, here is code which is showing different sorting with and without toLowerCase():
Map m =[ james :"silly boy",
janny :"crazy girl",
jimmy :"Funny man",
georges:"massive fella" ]
Map sorted = m.sort { a, b -> a.value <=> b.value }
println sorted
sorted = m.sort { a, b -> a.value.toLowerCase() <=> b.value.toLowerCase() }
println sorted
And wll print:
[jimmy:Funny man, janny:crazy girl, georges:massive fella, james:silly boy]
[janny:crazy girl, jimmy:Funny man, georges:massive fella, james:silly boy]

Related

Sorting ArrayList of Maps based on a map value

Let's say I have this ArrayList of Maps
def map1 = [key1:value1, key2:value2, key3:value3]
def map2 = [key1:value1, key2:value2, key3:value3]
def map3 = [key1:value1, key2:value2, key3:value3]
def maps = [map1, map2, map3]
I want to sort this list based on value3 of the map.
How can I do that assuming that I'm working in Groovy?
You can create your own custom sort comparator like this in Groovy:
maps.sort { a, b -> a.key3 <=> b.key3 }
This will sort the maps based on the value of each one's key3.
You can deal with the ArrayList of maps like with any other ArrayList. Hence calling the Collections.sort(List) would take care of your issue, sorting it using the natural order of hashMaps - probably this will use identity. In case you want to provide a Comparator to provide more appropriate logic to what you have in mind you can use Collections.sort(List, Comparator).
In groovy you can call directly the .sort() on a given array, this is possible as the DefaultGroovyMethods contains several helper methods, among with several sorting related methods.
On your code:
def map1 = [key1:"1",key2:"2",key3:"3"]
def map2 = [key1:"2",key2:"3",key3:"1"]
def map3 = [key1:"3",key2:"1",key3:"2"]
def maps = [map1,map2,map3 ]
maps.sort();
maps.sort({m1, m2 -> m1.key3 <=> m2.key3})
println maps
Output:
[[key1:2, key2:3, key3:1], [key1:3, key2:1, key3:2], [key1:1, key2:2, key3:3]]
This response are based on capabilities of groovy 2.4.4.
I put some values, to make easier to understand...
You can create a comparison block (very analogous to a Comparator) and pass it to the sort() method
def map1 = [key1:11,key2:12,key3:13]
def map2 = [key1:21,key2:22,key3:23]
def map3 = [key1:31,key2:32,key3:33]
def maps = [map2,map1,map3] //map1 in second position
maps.sort({mapa, mapb -> mapa.key3.compareTo(mapb.key3)})​
Would yield
[[key1:11, key2:12, key3:13], [key1:21, key2:22, key3:23], [key1:31, key2:32, key3:33]] //now they are sorted

How to sort and filter backbone collection

relatively new to backbone. I'm trying to filter and sort a collection before passing off to the template. I can easily sort OR filter, but I can't figure out how to do both. I tried chaining them, creating a variable to hold the filter then sort on that, but can't get anything to work. Any advice?? Thanks!
# Can filter like this:
monday = #collection.where({ target: '#day-mon' })
# Can sort like this:
mondaySorted = #collection.sortBy (t) -> t.get('order')
# Doesn't work:
mondayFilteredSorted = #collection.where({ target: '#day-mon' }).sortBy (t) -> t.get('order')
# Doesn't work:
mondaySorted = monday.sortBy (t) -> t.get('order')
Your problem is that where and sortBy return arrays — not collections — and arrays don't have where or sortBy methods. As soon as you where or sortBy, you lose access to the Backbone collection and Underscore utilities.
You can of course use Underscore directly on the array that where gives you:
_(#collection.where(target: '#day-mon')).sortBy (t) -> t.get('order')
or you could use chain and filter:
#collection.chain().filter((m) -> m.get('target') == '#day-mon').sortBy((m) -> m.get('order')).value()
where is a Backbone collection method so you have to drop down to Underscore's filter if you want to chain.
You could also use the standard Array::sort:
by_order = (a, b) ->
[ a, b ] = [ a.get('order'), b.get('order') ]
return 1 if(a > b)
return -1 if(a < b)
return 0
#collection.where(target: '#day-mon').sort(by_order)
I find this version a little easier on the eyes if you don't use an anonymous function with sort.
Demo: http://jsfiddle.net/ambiguous/N6AT7/1/

Search an two dimensional array by another array

I have two arrays. One Mapper and one with my ID's.
My Array with the external ID's:
genres_array = [12,28,16]
The Mapper Array (Internal-ID, External-ID)
mapper = [
[1,12],
[2,18],
[3,19],
[4,28],
[5,16],
[6,90],
]
As Result i would like to have now a new array, with only the internal values of the genres_array (the genres_array had the external values first). In this case the result would be [1,4,5]
I tried a lot of ways but i really have no idea how to solve this simple problem in a clean way. Im pretty sure it will be something like
genres_array.map { |genre_id| get_internal_id_from_mapper }
PS: It could also happen that a ID won't be found in the mapper. In that i case i just want to remove it from the array. Any idea?
You're looking for rassoc:
genres_array.map { |genre_id| mapper.rassoc(genre_id)[0] }
Which results in
[1, 4, 5]
EDIT: Just read the PS - try something like this:
genres_array.map { |genre_id|
subarr = mapper.rassoc genre_id
subarr[0] if subarr
}.compact
Then for an input of
genres_array = [12,28,100,16]
You would still get the output
[1, 4, 5]
Another way that won't throw an exception if the external id is not found:
genres_array = [12,28,16]
mapper = [
[1,12],
[2,18],
[3,19],
[4,28],
[5,16],
[6,90],
]
internal_ids = genres_array.map do |genre_id|
element = mapper.detect { |m| m[1] == genre_id }
element ? element[0] : nil
end.compact
Another solution involving a hash:
Hash[mapper].invert.values_at(*genres_array)
Asking for values that does not exist will return nil, if you do not want the nil just add .compact at the end.

Comparing more than one object properties in separate arrays

I am trying to figure out a way to iterate through and remove duplicate records from four different sources.
first_source = [#<Customer:0x007f911e307ad0 #id="123", #name="Whitehall">,# <Customer:0x007f911e307ad0 #id="124", #name="Whitehall">#<Customer:0x007f911e307ad0 #id="125", #name="Whitehall">]
second_source = [#<Customer:0x007f911e307ad0 #id="5000", #name="Whitehall">,#<Customer:0x007f911e307ad0 #id="5500", #name="Whitehall">#<Customer:0x007f911e307ad0 #id="123", #name="Whitehall">]
third_source = [#<Customer:0x007f911e307ad0 #id="800", #name="Whitehall">,#<Customer:0x007f911e307ad0 #id="5000", #name="Whitehall">#<Customer:0x007f911e307ad0 #id="124", #name="Whitehall">]
fourth_source = [#<Customer:0x007f911e307ad0 #id="4300", #name="Whitehall">,#<Customer:0x007f911e307ad0 #id="800", #name="Whitehall">#<Customer:0x007f911e307ad0 #id="125", #name="Whitehall">]
I tried
customers = []
dup_customers = first_source + second_source + third_source + fourth_source
dup_customers.combination(2).each do |cs1, cs2|
customers << cs1 unless cs1.id != cs2.id
end
But this really did not work.
Can someone help me suggest a way/strategy for traversing through these four collections and finding the Customer id's that are equal and then doing something with it?
How about Array#uniq?
customers = (first_source + second_source + third_source + fourth_source).uniq
uniq discards duplicates by element-wise comparison using Object#eql?, so for this method to work, you would need to implement Customer#eql?.
class Customer
def eql?(other)
id == other.id #or however you define equality
end
end
dup_customers =
[first_source, second_source, third_source, fourth_source]
.combination(2).flat_map{|s1, s2| s1 & s2}
Overriding eql as #pje does is not necessary. uniq takes a block (last example):
customers = [first_source ,second_source, third_source, fourth_source ].flatten
p customers.uniq{|c| c.id}
You can use Array#| (union operator):
customers = first_source | second_source | third_source | fourth_source
It returns the result of the merging of the two arrays while removing duplicates:
["a", "b", "c" ] | [ "c", "d", "a" ]
#=> [ "a", "b", "c", "d" ]

Multi string-element sort on Ruby hash, first key descending, second key ascending

Given the following array of hashes:
values = [
{"one"=>"BA", "two"=>"AAB"},
{"one"=>"AA", "two"=>"BBA"},
{"one"=>"AA", "two"=>"BCB"},
{"one"=>"AA", "two"=>"ABA"},
{"one"=>"BC", "two"=>"AAC"},
{"one"=>"AC", "two"=>"AAB"},
{"one"=>"AC", "two"=>"AAA"},
{"one"=>"AB", "two"=>"BCC"}
]
How do I get the following output:
{"one"=>"BC", "two"=>"AAC"}
{"one"=>"BA", "two"=>"AAB"}
{"one"=>"AC", "two"=>"AAA"}
{"one"=>"AC", "two"=>"AAB"}
{"one"=>"AB", "two"=>"BCC"}
{"one"=>"AA", "two"=>"ABA"}
{"one"=>"AA", "two"=>"BBA"}
{"one"=>"AA", "two"=>"BCB"}
I can do this easily enough if both key values point to integers:
multi_sort = values.sort_by { |x| [-x["one"], x["two"] ] }
What is the syntax to do this with string values?
You might need to write a full sort method:
values.sort { |a,b| a["one"] == b["one"] ? a["two"] <=> b["two"] : b["one"] <=> a["one"] }
Note that the order of the comparison is b vs a for "one" and a vs b for "two".
This could be a lot more concise if you'd used symbol keys instead of strings.

Resources