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);

No comments:

Post a Comment