Julia count() struct array from function

Viewed 316

How can I get the following Julia code to work (counting adults in a house) using count() instead of the for loop?

mutable struct Person
    age
end
mutable struct House
    people::Array{Person}
end
function Adults(h::House)
    numAdults = 0
    for n in 1:length(h.people)
        if h.people[n].age > 18; numAdults = numAdults + 1; end
    end
    numAdults
# count(h.people.age > 18, h.people) is there some variant of this that works?    
end
p1 = Person(10)
p2 = Person(40)
h1 = House([p1, p2])
Adults(h1)
2 Answers

There's nothing wrong with a for loop in Julia! It's often just as fast (if not faster) than the equivalent "vectorized" version. That said, it can be nice to use higher order functions at times to make your code more concise. In this case, you want to pass an anonymous function to count that computes the comparison you want for a single element.

julia> f = (x->x.age > 18)
#7 (generic function with 1 method)

julia> f(p1)
false

julia> f(p2)
true

You can pass this to any of Julia's higher order functions and it'll apply it to each element as it does its operations:

julia> count(x->x.age > 18, h1.people)
1

julia> map(x->x.age > 18, h1.people)
2-element Array{Bool,1}:
 0
 1

julia> filter(x->x.age > 18, h1.people)
1-element Array{Person,1}:
 Person(40)

(As an aside, you may want to ensure your struct fields are concretely typed for the best performance; that'll similarly affect performance for both the for loop and count.)

It's only syntactic sugar for an anonymous function, but you can use a do block:

function adults(h::House)
    return count(h.people) do person
        person.age > 18
    end  
end

Closer to what you wrote in the comment is

adults(h::House) = count(getproperty.(h.people, :age) .> 18)

But this is somewhat less readable (there's no sugar for property broadcasting), and construct an unnecessary intermediate array.

There's a somewhat intermediate form using a generator, which doesn't add excessive memory:

adults(h::House) = count(person.age > 18 for person in h.people)

This is probably what I'd go for.

Finally, let it be said that of all versions, the one you wrote is not really less idiomatic, and will most likely be the fastest of all in a micro-benchmark, although I'd write it like this:

function adults(h::House)
    count = 0
    for i in eachindex(h.people)
        count += Int(h.people[i].age > 18)
    end
    return count   
end

Finally finally: this function is a natural map-reduce task, opening more possibilities if you go for purely functional approaches (like using Transducers or @distributed for).

Related