Skip to content

A version of findIndex for the last matching element. - #174

Merged
Bodigrim merged 2 commits into
haskell:masterfrom
leftaroundabout:master
Aug 15, 2020
Merged

Bodigrim merged 2 commits into
haskell:masterfrom
leftaroundabout:master

Conversation

@leftaroundabout

Copy link
Copy Markdown
Contributor

Discussed in

#172

Comment thread Data/Vector/Generic.hs
-- | /O(n)/ Yield 'Just' the index of the /last/ element matching the predicate
-- or 'Nothing' if no such element exists.
findIndexR :: Vector v a => (a -> Bool) -> v a -> Maybe Int
{-# INLINE findIndexR #-}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually not sure if it makes sense to inline this. dolio remarked that this function won't be able to use any stream fusion. I know too little about vector's optimisations to judge this.

@cartazio

Copy link
Copy Markdown
Contributor
  1. could you also add a benchmark comparing this vs explicitly looping over the vector too? (we've established mostly that this isn't going to fuse with the current design, not how to make it most performant)

  2. should the name be findLastIndex rather than findIndexR ?
    It seems to me that because streams and vector indices are "oriented", 'findLastIndex' might be better :)

@leftaroundabout

Copy link
Copy Markdown
Contributor Author
  1. Yeah, I can add such a benchmark. Do you mean, I should add two new function checks to benchmarks/Main.hs' defaultMain list?

  2. I don't have much of an opinion on the naming. findLastIndex is certainly nice descriptive. But findIndexR should IMO be clear enough too. And it's consistent with Data.Sequence.findIndexR as well streamR on which it is based.

leftaroundabout added a commit to leftaroundabout/vector that referenced this pull request Jun 11, 2017
The results confirm that the `streamR` based implementation is
significantly faster than a manual recursion loop (at least
without any non-obvious optimisations) and much faster than a naïve
implementation with `foldl`.

```
benchmarking findIndexR
time                 592.0 μs   (589.8 μs .. 594.7 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 591.3 μs   (589.8 μs .. 593.0 μs)
std dev              5.563 μs   (4.182 μs .. 7.344 μs)

benchmarking findIndexR_naïve
time                 15.87 ms   (15.66 ms .. 16.08 ms)
                     0.999 R²   (0.998 R² .. 1.000 R²)
mean                 16.01 ms   (15.92 ms .. 16.17 ms)
std dev              316.9 μs   (207.2 μs .. 490.3 μs)

benchmarking findIndexR_manual
time                 977.0 μs   (968.0 μs .. 987.1 μs)
                     0.999 R²   (0.999 R² .. 1.000 R²)
mean                 978.0 μs   (973.1 μs .. 986.6 μs)
std dev              21.65 μs   (13.65 μs .. 34.29 μs)
variance introduced by outliers: 12% (moderately inflated)
```

The benchmark was requested by cartazio
haskell#174 (comment)
@leftaroundabout

Copy link
Copy Markdown
Contributor Author

The benchmark suggests that this function is indeed worthwhile, in that it's 40% faster than a manual loop (it's possible that I've missed some optimisation that should be used in the loop, though. But then, that could also happen to other people, so...)

benchmarking findIndexR
time                 592.0 μs   (589.8 μs .. 594.7 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 591.3 μs   (589.8 μs .. 593.0 μs)
std dev              5.563 μs   (4.182 μs .. 7.344 μs)

benchmarking findIndexR_naïve
time                 15.87 ms   (15.66 ms .. 16.08 ms)
                     0.999 R²   (0.998 R² .. 1.000 R²)
mean                 16.01 ms   (15.92 ms .. 16.17 ms)
std dev              316.9 μs   (207.2 μs .. 490.3 μs)

benchmarking findIndexR_manual
time                 977.0 μs   (968.0 μs .. 987.1 μs)
                     0.999 R²   (0.999 R² .. 1.000 R²)
mean                 978.0 μs   (973.1 μs .. 986.6 μs)
std dev              21.65 μs   (13.65 μs .. 34.29 μs)

leftaroundabout added a commit to leftaroundabout/vector that referenced this pull request Sep 26, 2017
The results confirm that the `streamR` based implementation is
significantly faster than a manual recursion loop (at least
without any non-obvious optimisations) and much faster than a naïve
implementation with `foldl`.

```
benchmarking findIndexR
time                 592.0 μs   (589.8 μs .. 594.7 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 591.3 μs   (589.8 μs .. 593.0 μs)
std dev              5.563 μs   (4.182 μs .. 7.344 μs)

benchmarking findIndexR_naïve
time                 15.87 ms   (15.66 ms .. 16.08 ms)
                     0.999 R²   (0.998 R² .. 1.000 R²)
mean                 16.01 ms   (15.92 ms .. 16.17 ms)
std dev              316.9 μs   (207.2 μs .. 490.3 μs)

benchmarking findIndexR_manual
time                 977.0 μs   (968.0 μs .. 987.1 μs)
                     0.999 R²   (0.999 R² .. 1.000 R²)
mean                 978.0 μs   (973.1 μs .. 986.6 μs)
std dev              21.65 μs   (13.65 μs .. 34.29 μs)
variance introduced by outliers: 12% (moderately inflated)
```

The benchmark was requested by cartazio
haskell#174 (comment)

@Bodigrim Bodigrim left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leftaroundabout could you possibly rebase?

Comment thread benchmarks/Algo/FindIndexR.hs Outdated
{-# NOINLINE findIndexR #-}
findIndexR = uncurry V.findIndexR

findIndexR_naïve :: (Double -> Bool, Vector Double) -> Maybe Int

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's avoid Unicode in function names.

The results confirm that the `streamR` based implementation is
significantly faster than a manual recursion loop (at least
without any non-obvious optimisations) and much faster than a naïve
implementation with `foldl`.

```
benchmarking findIndexR
time                 592.0 μs   (589.8 μs .. 594.7 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 591.3 μs   (589.8 μs .. 593.0 μs)
std dev              5.563 μs   (4.182 μs .. 7.344 μs)

benchmarking findIndexR_naïve
time                 15.87 ms   (15.66 ms .. 16.08 ms)
                     0.999 R²   (0.998 R² .. 1.000 R²)
mean                 16.01 ms   (15.92 ms .. 16.17 ms)
std dev              316.9 μs   (207.2 μs .. 490.3 μs)

benchmarking findIndexR_manual
time                 977.0 μs   (968.0 μs .. 987.1 μs)
                     0.999 R²   (0.999 R² .. 1.000 R²)
mean                 978.0 μs   (973.1 μs .. 986.6 μs)
std dev              21.65 μs   (13.65 μs .. 34.29 μs)
variance introduced by outliers: 12% (moderately inflated)
```

The benchmark was requested by cartazio
haskell#174 (comment)
@leftaroundabout

Copy link
Copy Markdown
Contributor Author

@Bodigrim done

@lehins

lehins commented Aug 15, 2020

Copy link
Copy Markdown
Contributor

This looks good to me. @Bodigrim do you have any more suggestions? The ones you've pointed out have already been taken care of. I'll merge the PR in a few days if I don't hear form you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants