All right, I’m finally going to write that piece I’ve been promising everybody for the last couple of months. What I’m covering in this post applies mainly to ShoppingAdvisor accounts that have their inventory in MarketplaceAdvisor Premium and that have matrix (aka “parent/child”) inventory. Remember, this one is going to be kind of technical, unlike my usual posts.

Also, it’s REALLY long.

How ShoppingAdvisor Handles Parent-Child
By now, most people know that if a ShoppingAdvisor feed is based off a label that includes a parent sku and one or more of its child skus, then only the parent sku will appear in the feed. However, if the label contains child skus without their parent, those child skus will be in the output; in effect, they are treated as if they were not in a parent/child relationship.

parent with children

For example, this figure shows parent sku 98765 (”Mens Pants”) and its child skus, which are size-based variations; they all have an attribute called “Size” which contains their size (duh). If they were all on a label used to create a ShoppingAdvisor feed, only the parent sku would be in the output.

Generally, this really is the desired behavior. Most of the CSEs only want parent skus. However, there may be cases where you need to send information about the individual variations, rolled up somehow into a column (or several columns) in the feed. For example, ShopAmex has several pairs of “configuration” columns in their feed. The first column of the pair is the name of an attribute, and the second is a pipe-delimited string of the variation values.

parent with children

In the case of our “Mens Pants” sku, the name of the attribute would be “Size,” and the string of values would be “29|30|31|32|33|34.” The question is: how do we create that string of the values of the child skus’ attribute?

New Field Syntax
The good news is, even though we’re only putting the parent item into the output, the ShoppingAdvisor system actually does have the child item data available. What the system does internally is collect all the child items into a “children” collection that is contained by the parent item. This collection is actually accessible by the system, and IS used by certain templates (mainly the Overstock template’s “option” section). However, up until now there was no way in the UI to explicitly access the child item data.

With the January Release, you can now put the following in a business rule:

$CHILDREN.ATTRNAME

Where “ATTRNAME” is of course some attribute that your items have. In our example, we would have:

$CHILDREN.SIZE

Of course, it’s never that simple. The problem is that $CHILDREN.SIZE gives you a an array of the size values of all the children. Now, an array is fancy programmer lingo for a list, but I don’t want to use “list” because it’s really nothing at all like the Lookup Lists we have in ShoppingAdvisor. Anyway, if you use this in a template, you’ll actually only get the first value in the array, and the order is defined by some wacky number that’s internal to our system. That’s not all that useful, so why bother?

Yeah, Why Bother?
Because what we really want is to take that array of attribute values from the child items and combine them, somehow, into a single column of the outgoing feed. In the ShopAmex example, we want to create a pipe-delimited string. Let me throw out another example, and see if that helps clarify things. What if you had a array of the child items’ prices, and you wanted to add them all up? Probably not a realistic scenario, but let’s go through the motions to see how this works.

Step Zero
First, we’ve got a array of prices. Also, we’ve got a “running total,” which starts out at zero:

Running Total = $0.0

Array:
$1.23
$4.99
$20.01

The process is to remove a price from the array and add it to the running total. The result is the new running total.

Step One
So let’s do that:

Running Total = $0.0 + $1.23 = $1.23

Array:
$4.99
$20.01

Step Two
And again:

Running Total = $1.23 + $4.99 = $6.22

Array:
$20.01

Step Three
And finally:

Running Total = $6.22 + $20.01 = $26.23

Array:

The array is now empty, so we’re done, and the running total is the answer we were looking for.

The Algorithm
Here’s one way to describe what we did:

  • Set the running total to zero
  • For each item in the array:
    • Remove it from the array
    • Compute a new result by adding the running total and the array value
    • Set the running total to the new result
  • When the array is empty, stop
  • The running total is the value you wanted.

As a programmer, I like to think of ways to generalize algorithms. Notice that I’ve italicized running total. Let’s generalize by renaming that to intermediate result:

  • Set the intermediate result to zero
  • For each item in the array:
    • Remove it from the array
    • Compute a new result by adding the intermediate result and the array value
    • Set the intermediate result to the new result
  • When the array is empty, stop
  • The intermediate result is the value you wanted.

Now, replace zero with seed value:

  • Set the intermediate result to seed value
  • For each item in the array:
    • Remove it from the array
    • Compute a new result by adding the intermediate result and the array value
    • Set the intermediate result to the new result
  • When the array is empty, stop
  • The intermediate result is the value you wanted.

The final change is a little trickier. Note the part that I’ve bolded. Here’s where we think like programmers. We can think of adding two numbers together as calling a function that takes two parameters. In math terms, we might write this function as f(x,y). For x we use the value of the intermediate result, and for y we use the array value. So here’s our final change:

  • Set the intermediate result to seed value
  • For each item in the array:
    • Remove it from the array
    • Compute a new result by calling f(x,y), where x is the intermediate result and y is the array value
    • Set the intermediate result to the new result
  • When the array is empty, stop
  • The intermediate result is the value you wanted.

So now our algorithm is completely general, and we see that it has three “unknown” values that must be filled in before we can actually follow the steps:

  • a seed value
  • an array
  • a function, f(x,y)

This algorithm is well known in computer science, and is sometimes called fold. We’ve called it REDUCE, and it is now a function usable by ShoppingAdvisor business rules. The syntax looks like this:

REDUCE(f(x,y), array, seed value)

Wait. f(x,y)?
How do you define a function inside a business rule? Well, it’s actually quite simple. Here’s how:

FUNCTION(VARS(list of parameters), the body of the function)

Yes, we have a new function called FUNCTION that lets you define your own functions. Notice also that inside the FUNCTION function, you have to use another function called VARS to hold your function’s list of parameters. The parameters can be called whatever you want, but you have to put an “at sign” in front of each one. The body of the function is written just like a business rule, except you get to use your own parameters instead of, say, an inventory field. You can use inventory fields, if you want to, but usually in these situations you’re only doing something to the parameters of the function.

For our adding example, you would just write this:

FUNCTION(VARS(@X,@Y),@X+@Y)

Just as with the new field syntax, there’s some bad news: for now, you can only use this inside a REDUCE.

Putting It All Together
Let’s go back to the ShopAmex example again. How would we write a business rule to do that? Let’s look at our algorithm and figure out the pieces (the seed value, the array, and the function). The array is easy, of course; it’s just the child items’ sizes. To figure out the seed value and the function, it might help to write it out as we did for the adding example. We’ll just hand-wave over the parts we don’t know yet.

Step Zero

Intermediate Result = ? (it's the seed value, but we don't know it yet)

Array:
29
30
31
32
33
34

We’ve got the array, but we don’t know the seed value or the function. What if we skip ahead a few steps; we know how we want the intermediate result to look, so maybe we can “back out” the seed and the function. Let’s fast-forward a couple of steps.

Steps Two and Three
Suppose we’ve already done our algorithm using the first two values from the array, 29 and 30. At that point, we’d want things to look like this:

Intermediate Result = 29|30

Array:
31
32
33
34

Then we’d pull the next value from the array (31), and run through the algorithm:

Intermediate Result = f(29|30,31) = 29|30|31

Array:
32
33
34

A possible f(x,y) to give us this intermediate result would be: CONCATENATE(@X,”|”,@Y). Let @X be 29|30 and @Y be 31, and that gives us the intermediate result we want. If we take this and “rewind,” then after the first step (when we pull the 29 from the array and put it into f(x,y)) the intermediate result must have been 29. Before that, it must have been a blank, so our seed value should be a blank.

Wait. That can’t be right. If we’d started with a blank, the intermediate result after the first step would have been:

Intermediate Result = f(,29) = |29

Well, it turns out that there’s really no way around that; there’s no way for us to use REDUCE to create this string without it leaving an extra | either on the beginning or the end. Think you can guess what we’ll do about it?

Finally
If we decide to use a blank value for our seed, then our final business rule would look like this:

REDUCE(FUNCTION(VARS(@X,@Y),CONCATENATE(@X,"|",@Y)),$CHILDREN.SIZE,"")

If we used that in a template, the result would be: |29|30|31|32|33|34. That’s pretty close, except for the first | character. However, that’s easily cleaned up with a REGEXREPLACE:

REGEXREPLACE(REDUCE(FUNCTION(VARS(@X,@Y),CONCATENATE(@X,"|",@Y)),$CHILDREN.SIZE,""),"^\|","")

The regex matches a | character at the beginning, and that gets replaced with blank, so we’re done!

The End
Let’s summarize what this was all about. There are three new things you can use in business rules:

  1. Field syntax to get an array of a single attribute’s values from child items
  2. A way to define your own functions
  3. A function called REDUCE to apply a function to an array of values

Of course, you have to use all three of these things together to get something done, so I guess it’s really just one great big new thing. Give it a whirl and let me know how it goes.

UPDATE: I edited this post slightly, because of some confusion about whether or not the arrays (which I had called “lists”) passed to REDUCE were the same as a ShoppingAdvisor Lookup List. They are not, thus the edit.