Cory Klein
Cory Klein

Reputation: 55870

Filter a list by item index?

val data = List("foo", "bar", "bash")
val selection = List(0, 2)
val selectedData = data.filter(datum => selection.contains(datum.MYINDEX))
//                                                  INVALID CODE HERE ^
// selectedData: List("foo", "bash")

Say I want to filter a List given a list of selected indices. If, in the filter method, I could reference the index of a list item then I could solve this as above, but datum.MYINDEX isn't valid in the above case.

How could I do this instead?

Upvotes: 6

Views: 6019

Answers (7)

dodo
dodo

Reputation: 109

There is actually an easier way to filter by index using the map method. Here is an example

val indices = List(0, 2)
val data = List("a", "b", "c")

println(indices.map(data)) // will print List("a", "c")

Upvotes: 0

Michael Zajac
Michael Zajac

Reputation: 55569

How about using zipWithIndex to keep a reference to the item's index, filtering as such, then mapping the index away?

data.zipWithIndex
    .filter{ case (datum, index) => selection.contains(index) }
    .map(_._1)

Upvotes: 8

samthebest
samthebest

Reputation: 31553

The following is the probably most scalable way to do it in terms of efficiency, and unlike many answers on SO, actually follows the official scala style guide exactly.

import scala.collection.immutable.HashSet

val selectionSet = new HashSet() ++ selection

data.zipWithIndex.collect { 
  case (datum, index) if selectionSet.contains(index) => datum
}

If the resulting collection is to be passed to additional map, flatMap, etc, suggest turning data into a lazy sequence. In fact perhaps you should do this anyway in order to avoid 2-passes, one for the zipWithIndex one for the collect, but I doubt when benchmarked one would gain much.

Upvotes: 0

Richard Close
Richard Close

Reputation: 1905

Since you have a list of indices already, the most efficient way is to pick those indices directly:

val data = List("foo", "bar", "bash")
val selection = List(0, 2)
val selectedData = selection.map(index => data(index))

or even:

val selectedData = selection.map(data)

or if you need to preserve the order of the items in data:

val selectedData = selection.sorted.map(data)

UPDATED

In the spirit of finding all the possible algorithms, here's the version using collect:

val selectedData = data
  .zipWithIndex
  .collect { 
    case (item, index) if selection.contains(index) => item 
   }

Upvotes: 0

flashball
flashball

Reputation: 9

This Works :

val data = List("foo", "bar", "bash")
val selection = List(0, 2)
val selectedData = data.filter(datum => selection.contains(data.indexOf(datum)))
println (selectedData)

output : List(foo, bash)

Upvotes: 0

The Archetypal Paul
The Archetypal Paul

Reputation: 41779

It's neater to do it the other way about (although potentially slow with Lists as indexing is slow (O(n)). Vectors would be better. On the other hand, the contains of the other solution for every item in data isn't exactly fast)

val data = List("foo", "bar", "bash")
         //> data  : List[String] = List(foo, bar, bash)
val selection = List(0, 2)  
         //> selection  : List[Int] = List(0, 2)
selection.map(index=>data(index))
         //> res0: List[String] = List(foo, bash)

Upvotes: 1

Szymon Kownacki
Szymon Kownacki

Reputation: 1

First solution that came to my mind was to create a list of pairs (element, index), filter every element by checking if selection contains that index, then map resulting list in order to keep only raw elementd (omit index). Code is self explanatory:

data.zipWithIndex.filter(pair => selection.contains(pair._2)).map(_._1)

or more readable:

val elemsWithIndices = data.zipWithIndex
val filteredPairs = elemsWithIndices.filter(pair => selection.contains(pair._2))
val selectedElements = filteredPairs.map(_._1)

Upvotes: 0

Related Questions