Map and reduce with multiple parameters

The following Python-to-Julia exercise is taken from
https://maryrosecook.com/blog/post/a-practical-introduction-to-functional-programming
Generally I am having difficulties with map and reduce when dealing with multiple parameters.

function assoc(_d, key, value)
    d = deepcopy(_d)
    d[key] = value
    return d
end
function set_canada_as_country(band)
    return assoc(band, "country", "Canada")
end
function strip_punctuation_from_name(band)
    return assoc(band, "name", replace(band["name"], "."=>""))
end
function capitalize_names(band)
    return assoc(band, "name", titlecase(band["name"]))
end
fns = [set_canada_as_country,
       strip_punctuation_from_name,
       capitalize_names]
bands = [Dict("name"=>"sunset rubdown", "country"=>"UK", "active"=>false),
         Dict("name"=>"women", "country"=>"Germany", "active"=>false),
         Dict("name"=>"a silver mt. zion", "country"=>"Spain", "active"=>true)]

# Exercise 4. Try and write the pipeline_each function. Think about the order of operations. 
# The bands in the array are passed, one band at a time, to the first transformation function. 
# The bands in the resulting array are passed, one band at a time, to the second transformation function. 
# And so forth.
# A solution:
#= 
def pipeline_each(data, fns):
    return reduce(lambda a, x: map(x, a),
                  fns,
                  data)
 =#
function pipeline_each(data, fns)
    reduce(a,x -> map(x,a), fns, data) # not operational
    #mapreduce ?
end
pipeline_each(bands, ops)

Anonymous functions with multiple arguments need brackets around their arguments, like (a, x) -> map(x, a). Hope that helps!

https://docs.julialang.org/en/v1/manual/functions/#man-anonymous-functions

I am not familiar with Python.
There are a lot of (implicit) questions here:

  1. about map/reduce an multiple arguments
  2. how to translate the Python code to Julia
  3. how to solve the example you linked in a Julian way

some food for thought on map/reduce:

a = 1:5
b = 2:6

?map()
map((i,j)->i^j,a,b)
?reduce()
reduce(+,map((i,j)->i^j,a,b))
?mapreduce()
z = zip(a,b)
mapreduce(x->x[1]^x[2],+,z)
mapreduce((i,j)->i^j,+,a,b)

#you can also use global variables in map
sum(map(i->a[i]^b[i],1:length(a)))

Regarding pipelines, see https://github.com/oxinabox/Pipe.jl

1 Like

Nice way to explore functions!
I am not sure if any of these covers my use case though.

As a simpler MWE, take the following problem with the same structure:

julia> x = [1,2,3]
3-element Vector{Int64}:
 1
 2
 3

julia> funs = [sin,cos,tan]
3-element Vector{Function}:
 sin (generic function with 13 methods)
 cos (generic function with 13 methods)
 tan (generic function with 12 methods)

julia> funs[3].(funs[2].(funs[1].(x)))
3-element Vector{Float64}:
 0.786357394978223
 0.7053391147354299
 1.523873017405459

How to express this operation for an arbitrary array funs using map or reduce?

I think you are looking for function composition: ?∘

That’s all well, but no. (But yes in the end, see your solution below)
The context from the original Python article is “functional programming”, so a stateless function which accepts arbitrary inputs is sought.
The Python article uses a combination of map and reduce. The earlier examples and exercises were an almost 1:1 translation. So I guess that’s possible here.

Maybe this

julia> x = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> funs = [sin, cos, tan]
3-element Vector{Function}:
 sin (generic function with 13 methods)
 cos (generic function with 13 methods)
 tan (generic function with 12 methods)

julia> reduce((val, fun) -> map(fun, val), funs, init=x)
3-element Vector{Float64}:
 0.786357394978223
 0.7053391147354299
 1.523873017405459

julia> funs[3].(funs[2].(funs[1].(x)))
3-element Vector{Float64}:
 0.786357394978223
 0.7053391147354299
 1.523873017405459
1 Like

Simpler:
reduce(∘, [tan, cos, sin]).([1,2,3])

2 Likes

Wow, thank you so much for the two solutions
(one more Python- the other more Julia-like)
and to everyone who helped - great community!

1 Like