How to find a matching element in a list and map it in as an Scala API method?
Asked Answered
B

4

22

Is there a method to do the following without doing both methods: find and map?

val l = 0 to 3
l.find(_ * 33 % 2 == 0).map(_ * 33) // returns Some(66)
Beano answered 29/3, 2011 at 21:34 Comment(2)
Why do you want a single method to do this? Is it curiosity or is there a deeper reason?Amortize
Mostly curiosity, but it is an operation that I have done a few times so I was going to make a function to do it. I assumed it was part of the library, but couldn't figure out where.Beano
L
32

How about using collect?

// Returns List(66)
List(1, 2, 3) collect { case i if (i * 33 % 2 == 0) => i * 33 }

However that will return all matches and not just the first one.

The better answer would have been, based on Scala 2.9:

// Returns Some(66)
List(1, 2, 3) collectFirst { case i if (i * 33 % 2 == 0) => i * 33 }

The solution suggested in the comments to append a head to get a Scala 2.8 version of that is not very efficient, I'm afraid. Perhaps in that case I would stick to your own code. In any case, in order to make sure it returns an option, you should not call head, but headOption.

// Returns Some(66)
List(1, 2, 3) collect { case i if (i * 33 % 2 == 0) => i * 33 } headOption
Lavinalavine answered 29/3, 2011 at 21:53 Comment(5)
collect will return multiple matches, while find returns just one.Leggat
Scala 2.9 adds a collectFirst method. Otherwise, as Daniel's comment is alluding to, you will need to use headOption in addition to collect to get the same type and behaviour as the original example.Pisci
collect will traverse the entire list. if you're on 2.8 and can't use collectFirst, you can do myList.view.collect(...).headOption. by using a view, you avoid traversing the whole list.Heredes
The problem with it is that the operation must be executed twice because there is no way to reuse intermediate result from guardRosemarie
you're calculating i*33 twice in each case. Is that possible to do once?Archine
V
22

If you don't want to do your map() operation multiple times (for instance if it's an expensive DB lookup) you can do this:

l.view.map(_ * 33).find(_ % 2 == 0)

The view makes the collection lazy, so the number of map() operations is minimized.

Virgel answered 25/11, 2014 at 15:52 Comment(2)
I think this is the most correct answer to this question. The others are useless in most cases because the operation must be executed twice.Rosemarie
THANKS! exactly what I wanted never heard of this views before (but used somethi,ng like that with Java/Guava in the past)!Interjacent
I
6

Hey look, it's my little buddy findMap again!

/**
 * Finds the first element in the list that satisfies the partial function, then 
 * maps it through the function.
 */
def findMap[A,B](in: Traversable[A])(f: PartialFunction[A,B]): Option[B] = {
  in.find(f.isDefinedAt(_)).map(f(_))
}

Note that, unlike in the accepted answer, but like the collectFirst method mentioned in one of its comments, this guy stops as soon as he finds a matching element.

Imbrue answered 30/3, 2011 at 0:6 Comment(1)
Traversable already defines the method collectFirst which does exactly what your findMap does.Electrobiology
P
-1

This can do it, but it would be easier if you tell what you're really trying to achieve:

l.flatMap(n => if (n * 33 % 2 == 0) Some(n * 33) else None).headOption
Precocity answered 29/3, 2011 at 22:18 Comment(1)
this traverses the entire listHeredes

© 2022 - 2024 — McMap. All rights reserved.