Does a `filterfirst` or `filtersingle` function exist? Similar to c# Single()?

I’m wondering if the following functions already exist in Julia with another name? I’m often trying to grab a single element, so I find myself writing filter(...) -> first a lot, but this is not safe because if the filter call returns an empty array, then I need to handle that case. c# has a linq function call Single() which returns the single object which satisfies the criteria, if more than one element match an exception is returned, otherwise nothing is returned.

function filterfirst(f,items)
    for item in items
        if f(item)
            return item
        end
    end

    return nothing
end

function filtersingle(f,items::Vector{T}) where T
    
    found = T[]
    for item in items
        if f(item)
            push!(found, item)
        end
    end

    n = length(found)

    if n == 0
        return nothing
    elseif n == 1
        return found[1]
    elseif n > 1
        error("Found $n items, expected 1")
    end

end

Anyone know where these might already exist?

1 Like

Is this (part of) what you are looking for?

https://docs.julialang.org/en/v1/base/arrays/#Base.findfirst-Tuple{Function,%20Any}

Almost, except I’d like the object returned, rather than the index

using Base.Iterators: take, filter

a = 1:10
div_5(x) = iszero(x % 5)
only(take(filter(div_5, a), 2)) # ArgumentError
a = 1:9
only(take(filter(div_5, a), 2)) # 5

function my_single(a, f)
    res = Iterators.take(Iterators.filter(f, a), 2)
    isempty(res) && return nothing
    only(res)
end

I would approach in a similar way

julia> a = [1, 4, 2, 2]
4-element Vector{Int64}:
 1
 4
 2
 2

julia> first(Iterators.filter(iseven, a))
4

1 Like

filterfirst can be written as

filter(f, a) |> fila -> get(fila, firstindex(fila), nothing)

but your version is more efficient since it returns immediately on finding a match.

For filtersingle, instead of collecting all items and then checking at the end, you can collect just the one item you might want to return, and error immediately if there’s more than one.

julia> function filtersingle(f,items::Vector{T}) where T
           
           local found::T
           for item in items
               if f(item)
                   @isdefined(found) && error("Found more than one matching item")
                   found = item
               end
           end
           return (@isdefined(found) ? found : nothing)

       end

I’ve noticed this before — Base is missing a function like

robustfirst(x) = isempty(x) ? nothing : first(x)

which returns nothing instead of raising an exception. Does anyone know why we don’t have that?

1 Like

I’m guessing I can answer this myself: Without the exception it would be impossible to discern if a collection is empty or if it simply has Nothing as its first element.

So it seems if we want the functionality of filterfirst or filtersingle, one must write their own function or combined expression. Is this worth a PR to include in the Julia language?

I don’t think it’s worth opening a PR—as I noted above, (unlike for findfirst) there is no obvious behavior for filterfirst when the collection has no matching element. So it is unlikely that folks will come to an agreement about the implementation

My thinking is findfirst returns nothing when no match exists, therefore fitlerfirst would do the same thing.

The find.. functions are for returning indexes, the filter.. functions are for returning the elements. Just seems to me like a missing feature. Other languages implement such a thing.

A collection cannot have an index nothing — so there is a clear interpretation for that return value.

However, a collection can have an element nothing:

julia> a = [1, nothing, 2, 3]
4-element Vector{Union{Nothing, Int64}}:
 1
  nothing
 2
 3

julia> first(Iterators.filter(isnothing, a)) |> println
nothing

So now it’s unclear: Did I find a matching element which happens to have the value nothing? Or was there no matching element at all? And, in the first place, why should the return value “no matching element” be nothing rather than missing?

All of these questions are quite unclear…

OK, makes sense. I guess the best option is to put the code in a separate package. So that’s what I did :slight_smile:

bradcarman/FilterHelpers.jl: A place to add some missing filter functions: filterfirst, filtersingle, filterlast, etc. (github.com)

Another implementation of FilterHelper functions, using almost one-liners:

struct NotUniqueError <: Exception end
filterfirst(f, a) = 
  isnothing(begin pos = findfirst(f,a) ; end) ? nothing : @inbounds a[pos]                                                       
filterlast(f, a) = 
  filterfirst(f, reverse(a))                         
filteronly(f, a) = 
  (r = findfirst(f,a)) == findlast(f,a) ?
  ifelse(isnothing(r),r,@inbounds a[r]) :
  ( @error "More than 1 items" ; throw(NotUniqueError()) )

Both filterfirst and filterlast avoid all item pass. And even filteronly in case of too many matching items.

1 Like

Some related discussion here:

1 Like

I think the accepted way to handle this in Base is to return a Union{Some{T}, Nothing}. However, I’m not sure if any functions in the public API for Base/stdlib Julia have actually adopted this approach…

filtersingle could be called only(f, xs).