Hi I have a list of maps in groovy like
def v=[[val1:'FP'],[val1:'LP'],[val1:'MP'],[val1:'MP'],[val1:'LP'],[val1:'FP']]
I wanted to sort based on the following order FP,MP,LP
I tried doing
v.sort{x,y->
x.val1 <=> y.val1
}
which prints [[val1:FP], [val1:FP], [val1:LP], [val1:LP], [val1:MP], [val1:MP]] which is sorted alphabetically, but I need it to be sorted in the following format
FP,MP,LP
An alternative: Whenever I am dealing with a fixed, ordered list of strings I immediately think of using enums instead:
enum PValue { FP, MP, LP }
Now we have an ordered set of constants that readily converts to and from string values. So sorting looks as simple as this:
v.sort { x, y -> PValue[x.val1] <=> PValue[y.val1] }
EDIT: Or even simpler:
v.sort { PValue[it.val1] }
As has been said int the comments, you need to define a preferred order, and then sort based on that... so with your list of maps:
def v=[[val1:'FP'],[val1:'LP'],[val1:'MP'],[val1:'MP'],[val1:'LP'],[val1:'FP']]
And a preferred order of results:
def preferredOrder = ['FP', 'MP', 'LP']
You can then sort based on the values index into this preferred order:
v.sort(false) { preferredOrder.indexOf(it.val1) }
Or, if you want unknown elements (ie: [val1:'ZP']) to go at the end of the sorted list, then you an do:
v.sort(false) { preferredOrder.indexOf(it.val1) + 1 ?: it.val1 }
So if they are not found (index -1) then they are compared on their String name
This question is similar to this one btw, which has more options in the answer
Related
I'm at beginning of programming so sorry if this is a stupid question.. :)
I'm trying to count the number of times a certain key is found in a list of maps or a map. (it can be either one)
In my example, I need to know the number of times key key1 will appear in a variable (keep in mind that it can be either a list of maps or a map)
def test01 = [[key1:'value1', key2:'value1', key3:'value1'], [key1:'value1', key2:'value1', key3:'value1'], [key5:'value1', key2:'value1', key4:'value1']]
def test02 = [key1:'value1', key2:'value1', key3:'value1']
def test03 = test01.findAll{it -> it.key1}.size()
println (test03)
My code works if my variable will be a list of maps (test01). But it brakes if I try it on a map (test02).. I'm not sure how to correctly iterate so it's ok for both a list of maps or a map.
Thanks for any help!
You can try this:
countKey = { e -> (e instanceof Map ? [e] : e).count { it.key1 } }
countKey(test01)
countKey(test02)
I have a Java code that looks like below code:
for(MyClass myclassObject: input.classes()) {
if(myclassObject.getName().equals("Tom")) {
outputMap.put("output", myclassObject.getAge())
}
}
How do I efficiently write this with Groovy collectmap?
I can do
input.classes().collectEntries["output":it.getAge()] But how do I include the if condition on it?
you could use findAll to keep only items according to condition
and after then apply collectEntries to transform items found
#groovy.transform.ToString
class MyClass{
int age
String name
}
def classes = [
new MyClass(age:11, name:'Tom'),
new MyClass(age:12, name:'Jerry'),
]
classes.findAll{it.getName()=='Tom'}.collectEntries{ [output:it.getAge()] }
Since your resulting map is only retaining one value anyway, you can also just do this:
input.classes().findResult { it.name == 'Tom' ? [output: it.age] : null }
where findResult will return the first item in classes() for which the closure:
{ it.name == 'Tom' ? [output: it.age] : null }
returns a non-null value.
Since you mentioned efficiency in your question: this is more efficient than going through the whole collection using collectEntries or findAll since findResult returns directly on finding the first instance of it.name == 'Tom'.
Which way to go really depends on your requirements.
collectEntries can take a closure as a parameter. You can apply your logic inside the closure and make sure you return the Map Entry when condition passes and return an empty map when condition fails. Therefore;
input.classes().collectEntries { MyClass myClassObject ->
myClassObject.name == 'Tom' ? ['output': myClassObject.getAge()] : [:]
}
However, with your approach there is a caveat. Since you are using the key as output and Map does not allow duplicate keys, you will always end up with the last entry in the map. You have to come up with a better plan if that is not your intention.
"When you've found the treasure, stop digging!"
I'm wanting to use more functional programming in Groovy, and thought rewriting the following method would be good training. It's harder than it looks because Groovy doesn't appear to build short-circuiting into its more functional features.
Here's an imperative function to do the job:
fullyQualifiedNames = ['a/b/c/d/e', 'f/g/h/i/j', 'f/g/h/d/e']
String shortestUniqueName(String nameToShorten) {
def currentLevel = 1
String shortName = ''
def separator = '/'
while (fullyQualifiedNames.findAll { fqName ->
shortName = nameToShorten.tokenize(separator)[-currentLevel..-1].join(separator)
fqName.endsWith(shortName)
}.size() > 1) {
++currentLevel
}
return shortName
}
println shortestUniqueName('a/b/c/d/e')
Result: c/d/e
It scans a list of fully-qualified filenames and returns the shortest unique form. There are potentially hundreds of fully-qualified names.
As soon as the method finds a short name with only one match, that short name is the right answer, and the iteration can stop. There's no need to scan the rest of the name or do any more expensive list searches.
But turning to a more functional flow in Groovy, neither return nor break can drop you out of the iteration:
return simply returns from the present iteration, not from the whole .each so it doesn't short-circuit.
break isn't allowed outside of a loop, and .each {} and .eachWithIndex {} are not considered loop constructs.
I can't use .find() instead of .findAll() because my program logic requires that I scan all elements of the list, nut just stop at the first.
There are plenty of reasons not to use try..catch blocks, but the best I've read is from here:
Exceptions are basically non-local goto statements with all the
consequences of the latter. Using exceptions for flow control
violates the principle of least astonishment, make programs hard to read
(remember that programs are written for programmers first).
Some of the usual ways around this problem are detailed here including a solution based on a new flavour of .each. This is the closest to a solution I've found so far, but I need to use .eachWithIndex() for my use case (in progress.)
Here's my own poor attempt at a short-circuiting functional solution:
fullyQualifiedNames = ['a/b/c/d/e', 'f/g/h/i/j', 'f/g/h/d/e']
def shortestUniqueName(String nameToShorten) {
def found = ''
def final separator = '/'
def nameComponents = nameToShorten.tokenize(separator).reverse()
nameComponents.eachWithIndex { String _, int i ->
if (!found) {
def candidate = nameComponents[0..i].reverse().join(separator)
def matches = fullyQualifiedNames.findAll { String fqName ->
fqName.endsWith candidate
}
if (matches.size() == 1) {
found = candidate
}
}
}
return found
}
println shortestUniqueName('a/b/c/d/e')
Result: c/d/e
Please shoot me down if there is a more idiomatic way to short-circuit in Groovy that I haven't thought of. Thank you!
There's probably a cleaner looking (and easier to read) solution, but you can do this sort of thing:
String shortestUniqueName(String nameToShorten) {
// Split the name to shorten, and make a list of all sequential combinations of elements
nameToShorten.split('/').reverse().inject([]) { agg, l ->
if(agg) agg + [agg[-1] + l] else agg << [l]
}
// Starting with the smallest element
.find { elements ->
fullyQualifiedNames.findAll { name ->
name.endsWith(elements.reverse().join('/'))
}.size() == 1
}
?.reverse()
?.join('/')
?: ''
}
I see in the API docs there is a sort() method on List, but I'm not clear what it needs for a parameter. The current need is for a very simple straight up alpha comparison.
1. A Quick Solution
Thanks for the question! You can sort a list of Strings like this:
main() {
final List<String> fruits = <String>['bananas', 'apples', 'oranges'];
fruits.sort();
print(fruits);
}
The above code prints:
[apples, bananas, oranges]
2. Slightly more advanced usage
Notice that sort() does not return a value. It sorts the list without creating a new list. If you want to sort and print in the same line, you can use method cascades:
print(fruits..sort());
For more control, you can define your own comparison logic. Here is an example of sorting the fruits based on price.
main() {
final List<String> fruits = <String>['bananas', 'apples', 'oranges'];
fruits.sort((a, b) => getPrice(a).compareTo(getPrice(b)));
print(fruits);
}
Let's see what's going on here.
A List has a sort method, which has one optional parameter: a Comparator. A Comparator is a typedef or function alias. In this case, it's an alias for a function that looks like:
int Comparator(T a, T b)
From the docs:
A Comparator function represents such a total ordering by returning a negative integer if a is smaller than b, zero if a is equal to b, and a positive integer if a is greater than b.
3. How to do it with a list of custom objects
Additionally, if you create a list composed of custom objects, you could add the Comparable<T> as a mixin or as inheritance (extends) and then override the compareTo method, in order to recreate the standard behavior of sort() for your list of custom objects. For more info, do check out this other, related StackOverflow answer.
Here is the one line code to achieve it.
fruits.sort((String a, String b)=>a.compareTo(b)); //fruits is of type List<String>
For Sorting Simple List of Integers or Strings:
var list = [5 , -5 ,1];
list.sort(); //-5 , 1 , 5
For Reversing the list order:
list.reversed;
For Sorting List of Objects or Map by field of it:
List<Map<String, dynamic>> list= [
{"name": "Shoes", "price": 100},
{"name": "Pants", "price": 50},
];
// from low to high according to price
list.sort((a, b) => a["price"].compareTo(b["price"]));
// from high to low according to price
list.sort((a, b) => b["price"].compareTo(a["price"]));
To add just one point to Seth's detailed answer, in general, in
(a, b) => foo(a, b)
passed into sort, the function foo should answer an integer result as follows:
if a < b, result should be < 0,
if a = b, result should be = 0, and
if a > b, result should be > 0.
For the above law of trichotomy to hold, both a and b must be Comparables.
use compareAsciiUpperCase instead of compareTo, as it supports strings and automatically ignores case sensitive:
import "package:collection/collection.dart";
data.sort((a, b) {
return compareAsciiUpperCase(a.name, b.name);
});
After today, you should just be able to do list.sort() .
The sort method's argument is now optional, and it defaults to a function that calls compareTo on the elements themselves. Since String is Comparable, it should Just Work now.
How I have solved this problem.
List<Product> _dataSavingListProducts = [];
List<Product> _dataSavingListFavoritesProducts = [];
void _orderDataSavingLists() {
_dataSavingListProducts.toList().reversed;
_dataSavingListFavoritesProducts.toList().reversed;
}
In groovy are there any methods that can find the near by numbers? For example :
def list = [22,33,37,56]
def number = 25
//any method to find $number is near to 22 rather than 33.
Is there any method for the above mentioned purpose, or i have to construct my own method or closure for this purpose.
Thanks in advance.
The following combination of Groovy's collection methods will give you the closest number in the list:
list.groupBy { (it - number).abs() }.min { it.key }.value.first()
The list.groupBy { (it - number).abs() } will transform the list into a map, where each map entry consists of the distance to the number as key and the original list entry as the value:
[3:[22], 8:[33], 12:[37], 31:[56]]
The values are now each a list on their own, as theoretically the original list could contain two entries with equal distance. On the map you then select the entry with the smallest key, take its value and return the first entry of the value's list.
Edit:
Here's a simpler version that sorts the original list based on the distance and return the first value of the sorted list:
list.sort { (it - number).abs() }.first()
If it's a sorted List, Collections.binarySearch() does nearly the same job. So does Arrays.binarySearch().