I am familiar with standard zipWith
functions which operate on corresponding elements of two sequences, but in a functional language (or a language with some functional features), what is the most succinct way to conditionally select the pairs of elements to be zipped, based on a third sequence?
This curiosity arose while scratching out a few things in Excel.
With numbers in A1:A10, B1:B10, C1:C10, D1, E1 and F1, I'm using a formula like this:
{=AVERAGE(IF((D1<=(A1:A10))*((A1:A10)<=E1),B1:B10/C1:C10))}
Each half of the multiplication in the IF statement will produce an array of Boolean values, which are then multiplied (AND'ed) together. Those Booleans control which of the ten quotients will ultimately be averaged, so it's as though ten separate IF statements were being evaluated.
If, for example, only the second and third of the 10 values in A1:A10 satisfy the conditions (both >=D1 and <=E1), then the formula ends up evaluating thusly:
AVERAGE(FALSE,B2/C2,B3/C3,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE)
The AVERAGE function happens to ignore Boolean and text values, so we just get the average of the second and third quotients.
Can this be done as succinctly with Haskell? Erlang? LINQ or F#? Python? etc..
NOTE that for this particular example, the formula given above isn't entirely correct--it was abbreviated to get the basic point across. When none of the ten elements in A1:A10 satisfies the conditions, then ten FALSE values will be passed to AVERAGE, which will incorrectly evaluate to 0.
The formula should be written this way:
{=AVERAGE(IF(NOT(OR((D1<=(A1:A10))*((A1:A10)<=E1))),NA(),
IF((D1<=(A1:A10))*((A1:A10)<=E1),B1:B10/C1:C10)))}
Where the NA()
produces an error, indicating that the average is undefined.
Update:
Thanks for the answers. I realized that my first question was pretty trivial, in terms of applying a function on pairs of elements from the second and third lists when the corresponding element from the first list meets some particular criteria. I accepted Norman Ramsey's answer for that.
However, where I went to next was wondering whether the function could be applied to a tuple representing corresponding elements from an arbitrary number of lists--hence my question to Lebertram about the limits of zipWithN
.
Apocalisp's info on applicative functors led me to info on python's unpacking of argument lists--applying a function to an arbitrary number of arguments.
For the specific example I gave above, averaging the quotients of elements of lists (where nums
is the list of lists), it looks like python can do it like this:
from operator import div
def avg(a): return sum(a,0.0)/len(a)
avg([reduce(div,t[1:]) for t in zip(*nums) if d<=t[0] and t[0]<=e])
More generally, with a function f
and a predicate p
(along with avg
) this becomes:
avg([f(t[1:]) for t in zip(*nums) if p(t[0])])