16 July 2011

Subtract a list of numbers from another one

Valid since: op4j 1.1

Description
Given two lists: one of ordered books and another of sent ones, subtract the sent books from the ordered ones and obtain the not yet sent. Notice that, if more books than ordered have been sent, the subtraction will be negative and you have to set those ones to 0 (we are not worried about the books we get for free).

Scenario
Our first List<Integer> list variable contains the ordered books:
List<Integer> orderedBooks = new ArrayList<Integer>();
orderedBooks.add(Integer.valueOf(15)); // Ordered units of book A
orderedBooks.add(Integer.valueOf(50)); // Ordered units of book B
orderedBooks.add(Integer.valueOf(23)); // Ordered units of book C
orderedBooks.add(Integer.valueOf(150)); // Ordered units of book D
orderedBooks.add(Integer.valueOf(3)); // Ordered units of book E
orderedBooks.add(Integer.valueOf(98)); // Ordered units of book F
orderedBooks.add(Integer.valueOf(300)); // Ordered units of book G
orderedBooks.add(Integer.valueOf(7)); // Ordered units of book H
Our second List<Integer> list variable contains the sent books:
List<Integer> sentBooks = new ArrayList<Integer>();
sentBooks.add(Integer.valueOf(5)); // Sent units of book A
sentBooks.add(Integer.valueOf(56)); // Sent units of book B
sentBooks.add(Integer.valueOf(23)); // Sent units of book C
sentBooks.add(Integer.valueOf(134)); // Sent units of book D
sentBooks.add(Integer.valueOf(13)); // Sent units of book E
sentBooks.add(Integer.valueOf(98)); // Sent units of book F
sentBooks.add(Integer.valueOf(345)); // Sent units of book G
sentBooks.add(Integer.valueOf(87)); // Sent units of book H
We want to:
  1. Subtract sent books from ordered ones so that we get the not sent ones
  2. Replace those negative results in the previous list by 0 as we just want to know how many books we have not yet received
  3. Add up all the pending units of each book to obtain the total number of not yet sent books

Recipe
Steps:
1. Subtract sent from ordered, replace negative units by 0 and add up the result.
op4j-ognl functions involved:
  • FnOgnl.i(final String ognlExpression, final Object... optionalParameters): executes an OGNL expression returning an Integer
  • FnOgnl.bool(final String ognlExpression, final Object... optionalParameters): executes an OGNL expression returning a Boolean

op4j functions involved:
  • FnInteger.sum(): sums up all the items in the iterable target

Let's see it coded with op4j:
Integer pendingBooks = Op.on(orderedBooks).forEach().exec(FnOgnl.i("#target - #param[0].get(#index)", sentBooks))
.execIfTrue(FnOgnl.bool("#target < 0"), FnOgnl.i("#param[0]", Integer.valueOf(0))).endFor() .exec(FnInteger.sum()).get();

11 December 2010

Convert a List<String> to a List<DateMidnight>

Valid since: op4j 1.1

Description
Convert a List<String> where each string represents a date in the format MM/dd/yyyy to a List<DateMidnight>

Scenario
Our List<String> list variable contains strings in the format MM/dd/yyyy:

List<String> list = new ArrayList<String>();
list.add("12/24/2000");
list.add("02/02/2010");
list.add("04/04/2002");
list.add("11/22/2005");
list.add("02/07/2005");
list.add("03/05/2005");
list.add("09/13/2006");
list.add("12/29/2007");

We want to create a List<DateMidNight> with the strings in our list converted
to DateMidnight:

List<DateMidnight> result = new ArrayList<DateMidnight>();
result.add(new DateMidnight(2000, 12, 24));
result.add(new DateMidnight(2010, 2, 2));
result.add(new DateMidnight(2002, 4, 4));
result.add(new DateMidnight(2005, 11, 22));
result.add(new DateMidnight(2005, 2, 7));
result.add(new DateMidnight(2005, 3, 5));
result.add(new DateMidnight(2006, 9, 13));
result.add(new DateMidnight(2007, 12, 29));

Recipe
Steps:
1. Iterate the list and convert each string to DateMidnight.

op4j-jodatime functions involved:
  • FnDateMidnight.strToDateMidnight(pattern): it converts the input string to DateMidnight using the given pattern

Let's see it coded with op4j:

String pattern = "MM/dd/yyyy";
List<DateMidnight> datemidnights = Op.on(list).forEach()
.exec(FnDateMidnight.strToDateMidnight(pattern)).get();

05 December 2010

Create a map from a List with the valid and invalid percentages

Valid since: op4j 1.1

Description
Given a List, create a map with two keys: "VALID" with the strings valid as a percentage between 0 and 100 and "INVALID" with the not valid ones

Scenario
Our List<String> list variable contains some strings that, in some cases, represent valid percentages:

List<String> list = new ArrayList<String>();
list.add("5");
list.add("3.4");
list.add("-13");
list.add("0");
list.add("5k7");
list.add("ten");
list.add("32");
list.add("1,2");

Some of the strings in the list, do not represent valid percentages and we want to create a map with two keys: INVALID whose value will be a list with the invalid strings and VALID which will have a list with the valid ones as its value:

Map<String, List<String>> result = new LinkedHashMap<String, List<String>>();
result.put("VALID", Arrays.asList(new String[] {"5", "3.4", "0", "32", "1,2"}));
result.put("INVALID", Arrays.asList(new String[] {"-13", "5k7", "ten"}));

Recipe
Steps:
1. Create a map from the input list with two keys: VALID and INVALID. Depending on whether a string in the input list represents a valid percentage or not, put it into the list of VALID or INVALID values.

op4j funcions involved:
  • FnString.isInteger(decimalPoint): returns whether the input is valid as an integer or not based on the given decimal point. It does not check the string represents a non decimal number that fits in an Integer number but that FnString.toInteger can be executed correctly.

  • FnString.toInteger(decimalPoint): converts the input value into an integer based on the given decimal point.

  • FnNumber.between(min, max): returns wether the input number is between min and max or not

Let's see it coded with op4j:

Map<String, List<String>> validInvalidPercentages = Op.on(list).zipAndGroupKeysBy(
new IFunction<String, String>() {
public String execute(String input, ExecCtx ctx) throws Exception {
return Op.on(input).exec(FnString.isInteger(DecimalPoint.IS_POINT))
.get().booleanValue()
&& Op.on(input).exec(FnString.toInteger(DecimalPoint.IS_POINT))
.exec(FnNumber.between(0, 100)).get().booleanValue()
? "VALID" : "INVALID";
}
}).get();

Comments
We are supposing the valid decimal point is IS_POINT though, if it were COMMA, it would be the same but using DecimalPoint.COMMA instead

Create a map from a List and generate a List with the strings convertible to Integer

Valid since: op4j 1.1

Description
For a given List<String>, create a Map with two keys: "VALID" with the strings valid as integer and "INVALID" with the not valid ones. Convert the valid strings to integer.

Scenario
Our List<String> list variable contains some strings that, in some cases, represent integer numbers:

List<String> list = new ArrayList<String>();
list.add("5");
list.add("3.4");
list.add("89.7");
list.add("-13.999");
list.add("5f7");
list.add("537");
list.add("323a");
list.add("3,23");

Some of the strings in the list, do not represent valid integers and we want to create a map with two keys: INVALID whose value will be a list with the invalid strings and VALID which will have a list with the valid ones as its value:

Map<String, List<String>> result = new LinkedHashMap<String, List<String>>();
result.put("VALID", Arrays.asList(new String[] {"5", "3.4", "89.7", "-13.999", "537", "3,23"}));
result.put("INVALID", Arrays.asList(new String[] {"5f7", "323a"}));

The items in the VALID list, will be converted into integer...and that List<Integer> will be the output we want.

Recipe
Steps:
1. Create a map from the input list with two keys: VALID and INVALID. Depending on whether a string in the input list represents a valid integer or not, put it into the list of VALID or INVALID values.
2. Iterate the list of valid strings and convert them into integer numbers.

op4j funcions involved:
  • FnString.isInteger(decimalPoint): returns whether the input is valid as an integer or not based on the given decimal point. It does not check the string represents a non decimal number that fits in an Integer number but that FnString.toInteger can be executed correctly.

  • FnString.toInteger(decimalPoint): converts the input value into an integer based on the given decimal point.

Let's see it coded with op4j:

Map<String, List<String>> validInvalidIntegers = Op.on(list).zipAndGroupKeysBy(
new IFunction<String, String>() {
public String execute(String input, ExecCtx ctx) throws Exception {
return Op.on(input).exec(FnString.isInteger(DecimalPoint.IS_POINT))
.get().booleanValue()? "VALID" : "INVALID";
}
}).get();

List<Integer> asIntegerIfPoint = Op.on(result.get("VALID")).forEach()
.exec(FnString.toInteger(DecimalPoint.IS_POINT)).get();

Comments
We are supposing the valid decimal point is POINT though, if it were COMMA, it would be the same but using DecimalPoint.IS_COMMA instead

28 June 2010

Build a map of lists of calendar from a list of strings

Valid since: op4j 1.0

Description
Convert a List<String> to a List<Calendar>, filter the result by keeping only the elements belonging to the year 2010 and, finally, create a Map<Integer,List<Calendar>> with the month as key. Finally, what we get are the dates belonging to 2010 grouped by month.

Scenario
Our List<String> asString variable contains some strings representing dates:

asString == {"20100505", "20100614", "19980407",
"20100209", "20100215", "20110101",
"20100620", "20100301"}

Some of the dates represented by these strings do not belong to the year 2010 so, once they are converted to Calendar, we'll have to filter them in order to keep only the 2010 dates. With these filtered results we'll create the Map<Integer,List<Calendar>> result variable which will be:

Map<Integer, List<Calendar>> result ==
[{1, {20100209, 20100215}},
{2, {20100301}},
{4, {20100505}},
{5, {20100614, 20100620}}]

Recipe
Steps:
1. Iterate the list and, for each element, create the Calendar it represents (we assume all of the elements are convertible to Calendar, otherwise, we should filter them).
2. Filter the List<Calendar> by removing all the dates not between January, 1st and December, 31st.
3. Create a Map<Integer, List<Calendar>> with the 2010 dates using the date month as the key.

op4j funcions involved:
  • FnString.toCalendar(pattern): converts a String into a Calendar.

  • FnObject.lessThan(object): compares two comparable elements and returns true if the target is less than the object passed.

  • FnObject.greaterOrEqTo(object): compares two comparable elements and returns true if the target is greater or equal to the object passed.

  • Call.methodForInteger(method, parameters, ...): calls a method on the target element and returns the result of that call.

Let's see it coded with op4j:

Map<Integer, List<Calendar>> result = Op.on(asString).forEach()
.exec(FnString.toCalendar("yyyyMMdd")).endFor()
.removeAllTrue(FnObject
.lessThan(new DateTime(2010, 1, 1, 0, 0, 0, 0)
.toCalendar(Locale.getDefault())))
.removeAllTrue(FnObject
.greaterOrEqTo(new DateTime(2011, 1, 1, 0, 0, 0, 0)
.toCalendar(Locale.getDefault())))
.zipAndGroupKeysBy(Call
.methodForInteger("get", Calendar.MONTH)).get();

31 May 2010

Converting the keys in a map

Valid since: op4j 1.0

Description
Convert all the keys in a map without changing its corresponding values.

Scenario
Our Map<String,String> map variable contains some numbers (as keys) along with their names in English language (as values):
// map == MAP [ {"1", "one"}, {"2", "two"}, {"3", "three"} ]
...but notice that keys are Strings, not real Integer objects, and that is precisely what we want to obtain, a Map<Integer,String> newMap variable like:
// newMap == MAP [ {1, "one"}, {2, "two"}, {3, "three"} ]

Recipe
Using op4j, we can iterate on the map entries, select the keys of these entries to operate on, and then execute a function that will convert the entries from String to Integer:

Map<Integer,String> newMap = 
    Op.on(map).forEachEntry().onKey().exec(FnString.toInteger()).get();

Comments
The onKey() action has of course a value counterpart, onValue(), which you can use just like:
Map<String,String> newMap = 
    Op.on(map).forEachEntry().onValue().exec(FnString.toUpperCase()).get();

Checking if all the members of an array meet a specific condition

Valid since: op4j 1.0

Description
Check whether all the elements in an array meet a specific condition, obtaining a TRUE if they do, or a FALSE if they don't.

Scenario
Our Integer[] values variable contains some numbers:
// values == ARRAY [ 100, 23, 587 ]
...and we want to know whether all the numbers are less than 800 or not, obtaining a Boolean object representing the result.

Recipe
Just use operate on the array and use the all action, applying the conditional "lessThan" function:

Boolean result = 
    Op.on(values).all(FnInteger.lessThan(800)).get();

Comments
The all action is also available for lists and sets.

There is also an any action, which returns true if any of the elements meets the specified condition.

04 May 2010

Modifying some elements in a list (depending on a specific condition)

Valid since: op4j 1.0


Description
Given a list (or array, or set) with several elements, modify some of them by executing a function. The elements to be modified will be determined based on a condition.


Scenario
Our List<String> herbs variable contains some of the spices offered at our online shop, which are stored somewhere in our databases:
herbs == LIST [ "*parsley*", "BASIL", "*Coriander*", "Spearmint" ]
Some of the names are stored between asterisks, and that means that the product has been completely sold out. Also, case is not homogeneous, and some names are in upper case, some others in small case, etc.

We want to process that list so that the names are capitalised (first letter in upper-case) and that sold out products are marked with a text, like:
herbs == LIST [ "Parsley (sold out!)", "Basil", 
                "Coriander (sold out!)", "Spearmint" ]


Recipe
Lots of work to do here. Step by step:
  1. Iterate the list, and for each element...
    1. If the herb name matches a pattern indicating it is surrounded by asterisks:
      1. Extract the name (remove the asterisks).
      2. Add the " (sold out!)" suffix.
    2. Capitalise the herb name (will involve converting first to lower case).

op4j functions involved will be:
  • FnString.matches(regex): returns true if the target String matches the regular expression.
  • FnString.matchAndExtract(regex, group): Similar to matches(regex), but allows the extraction of one of the groups defined in the regular expression as a substring.
  • FnOgnl.evalForstring(expression): Executes an OGNL expression. This will be our chosen way to reshape the String.
  • FnFunc.chain(fn1, fn2): Chains the execution of two functions. Equivalent to exec(fn1).exec(fn2), but used here for the sake of example.
  • FnString.toLowerCase() and FnString.capitalize(): String-related functions for reshaping Strings.
And here it goes:
herbs = 
    Op.on(herbs).forEach().
        ifTrue(FnString.matches("\\*.*?\\*")).
            exec(FnString.matchAndExtract("\\*(.*?)\\*",1)).
            exec(FnOgnl.evalForString("#target + ' (sold out!)'")).
        endIf().exec(FnFunc.chain(FnString.toLowerCase(),FnString.capitalize())).get();
As usual, almost harder to explain than to really code.


Comments
We could have created an equivalent piece of code using the execIfTrue(condition, then) action:
herbs = 
    Op.on(herbs).forEach().
        execIfTrue(
            FnString.matches("\\*.*?\\*"),
            FnFunc.chain(
                FnString.matchAndExtract("\\*(.*?)\\*",1),
                FnOgnl.evalForString("#target + ' (sold out!)'"))).
        exec(FnFunc.chain(FnString.toLowerCase(),FnString.capitalize())).get();
...which would not be as pretty as the first option, but would allow us to specify an else function by using the execIfTrue(condition, then, else) variant:
herbs = 
    Op.on(herbs).forEach().
        execIfTrue(
            FnString.matches("\\*.*?\\*"),
            FnFunc.chain(
                FnString.matchAndExtract("\\*(.*?)\\*",1),
                FnOgnl.evalForString("#target + ' (sold out!)'")),
            FnOgnl.evalForString("#target + ' (on sale)'")).
        exec(FnFunc.chain(FnString.toLowerCase(),FnString.capitalize())).get();
And finally, being this a quite complex process.. why don't we create a function we can carry around and use when we please, instead of the operation expression we just defined?:
Function<List<String>,List<String>> processHerbNames = 
    Fn.onListOf(Types.STRING).forEach().
        execIfTrue(
            FnString.matches("\\*.*?\\*"),
                FnFunc.chain(
                FnString.matchAndExtract("\\*(.*?)\\*",1),
                FnOgnl.evalForString("#target + ' (sold out!)'")),
            FnOgnl.evalForString("#target + ' (on sale)'")).
        exec(FnFunc.chain(FnString.toLowerCase(),FnString.capitalize())).get();
...
// Just execute it whenever you want!
herbs = processHerbNames.execute(herbs);

22 April 2010

Building a map from a list by executing functions on its elements

Valid since: op4j 1.0

Description
Build a map by iterating the elements of a list and executing two functions on each of them: one for obtaining the key of the corresponding entry, and another one for obtaining the value.

Scenario
Our Country class looks like this:
public class Country {
    private final String name;
    private final Integer population;
    ...
    public String getName() {
        return this.name;
    }
    public Integer getPopulation() {
        return this.population;
    }
}
And we have a List<Country> countries variable containing data about some countries:
// countries == LIST [
//                     COUNTRY [ "Spain"; 45989016 ],
//                     COUNTRY [ "France"; 65447374 ],
//                     COUNTRY [ "Portugal"; 10707924 ],
//                     COUNTRY [ "United Kingdom"; 62041708 ],
//                     COUNTRY [ "Ireland"; 4459300 ],
//                   ]
...which we want to transform into a Map<String,Integer> containing the population of each country, indexed by the country name:
// populationByCountry == MAP [
//                              {"Spain": 45989016},
//                              {"France": 65447374},
//                              {"Portugal": 10707924},
//                              {"United Kingdom": 62041708},
//                              {"Ireland": 4459300},
//                            ]

Recipe
The toMap(keyFunction, valueFunction) action, applied on the list, will let us specify two functions: one for obtaining map keys out of the original list elements, and another one for obtaining their corresponding values. We will use getters for both:

Map<String,Integer> populationByCountry =
    Op.on(countries).
        toMap(Get.attrOfString("name"), Get.attrOfInteger("population")).get();

Comments
And what if we wanted the result map to be ordered by population, from the highest to the lowest figure? Something like:
// populationByCountry == MAP [
//                              {"France": 65447374},
//                              {"United Kingdom": 62041708},
//                              {"Spain": 45989016},
//                              {"Portugal": 10707924},
//                              {"Ireland": 4459300},
//                            ]
We could solve it in two ways:
  1. First sorting the list by population, then creating the map.
  2. First creating the map, then sorting its entries by value.
Let's try ordering the list first:
// INCORRECT: Order will be ascending!
Map<String,Integer> populationByCountry =
    Op.on(countries).
        sortBy(Get.attrOfInteger("population")).
        toMap(Get.attrOfString("name"), Get.attrOfInteger("population")).get();
There is a problem there: sortBy will use the specified function for sorting, but sorting will be made according to natural order, and this will mean that less-populated countries will come first (opposite to what we want). So we will have to reverse the list once sorted:
Map<String,Integer> populationByCountry =
    Op.on(countries).
        sortBy(Get.attrOfInteger("population")).reverse().
        toMap(Get.attrOfString("name"), Get.attrOfInteger("population")).get();
Now it is correct. Finally, let's try the create map first, then sort approach. This time we will have to sort the map by its entry values...
Map<String,Integer> populationByCountry =
    Op.on(countries).
        toMap(Get.attrOfString("name"), Get.attrOfInteger("population")).
        sortBy(Get.attrOfInteger("value")).reverse().get();

21 April 2010

Executing an op4j function directly (without an expression)

Valid since: op4j 1.0

Description
Execute an op4j predefined function (or any other org.op4j.functions.Function<T,R> object) without having to create an operation or function expression.

Scenario
Our value String contains some accented characters, escaped for HTML:
// value == "Saint-&Eacute;tienne est une ville de France"
...and we want to unescape it so that we obtain a String like:
// value == "Saint-Étienne est une ville de France"

Recipe
Of course we could create an operation expression for executing the FnString.unescapeHTML function:

value = Op.on(value).exec(FnString.unescapeHTML()).get();

But, if we prefer, we can directly execute the function on the target String:

value = FnString.unescapeHTML().execute(value);

This is because the function object returned by FnString.unescapeHTML() is Function<String,String> so, in fact, if we look at it step by step:

Function<String,String> fn = FnString.unescapeHTML();
value = fn.execute(value);


Comments
All Function objects in op4j allow direct execution by means of their execute(...) method.