LondonMassive
LondonMassive

Reputation: 447

How do I remove an element from a list by value?

I am currently working on a function that takes in a Map[String, List[String]] and a String as arguments. The map contains a user Id and the IDs of films that they liked. What I need to do is, to return a List[List[String]] which contains the other movies that where liked by the user who liked the movie that was passed into the function.

The function declaration looks as follows:

def movies(m: Map[String, List[String]], mov: String) : List[List[String]]= {

}

So lets imagine the following:

val m1 : [Map[Int, List[String]]] = Map(1 ‐> List("b", "a"), 2 ‐> List("y", "x"), 3 ‐> List("c", "a"))
val movieID = "a"
movies(m1, movieId)

This should return:

List(List("b"), List("c"))

I have tried using

m1.filter(x => x._2.contains(movieID))

So that only Lists containing movieID are kept in the map, but my problem is that I need to remove movieID from every list it occurs in, and then return the result as a List[List[String]].

Upvotes: 1

Views: 195

Answers (3)

Raman Mishra
Raman Mishra

Reputation: 2686

This should work for you:

  object DemoAbc extends App {
  val m1 = Map(1 -> List("b", "a"), 2 -> List("y", "x"), 3 -> List("c", "a"))
  val movieID = "a"

  def movies(m: Map[Int, List[String]], mov: String): List[List[String]] = {
    val ans = m.foldLeft(List.empty[List[String]])((a: List[List[String]], b: (Int, List[String])) => {
      if (b._2.contains(mov))
        b._2.filter(_ != mov) :: a
      else a
    })

    ans
  }

  print(movies(m1, movieID))
}

Upvotes: 1

jwvh
jwvh

Reputation: 51271

The solution from Krzysztof is a good one. Here's an alternate way to traverse every List just once.

def movies(m: Map[String, List[String]], mov: String) =
  m.values.toList.flatMap{ss =>
    val tpl = ss.foldLeft((false, List.empty[String])){
      case ((_,res), `mov`)  => (true, res)
      case ((keep,res), str) => (keep, str::res)
    }
    if (tpl._1) Some(tpl._2) else None
  }

Upvotes: 1

Krzysztof Atłasik
Krzysztof Atłasik

Reputation: 22595

You could use collect:

val m = Map("1" -> List("b", "a"), "2" -> List("y", "x"), "3" -> List("c", "a"))

def movies(m: Map[String, List[String]], mov: String) = m.collect {
  case (_, l) if l.contains(mov) => l.filterNot(_ == mov)
}

movies(m, "a") //List(List(b), List(c))

Problem with this approach is, that it would iterate over every movie list twice, the first time with contains and the second time with filterNot. We could optimize it tail-recursive function, which would look for element and if found just return list without it:

import scala.annotation.tailrec

def movies(m: Map[String, List[String]], mov: String) = {

   @tailrec
   def withoutElement[T](l: List[T], mov: T, acc: List[T] = Nil): Option[List[T]] = {
      l match {
        case x :: xs if x == mov => Some(acc.reverse ++ xs)
        case x :: xs => withoutElement(xs, mov, x :: acc)
        case Nil => None
      }
   }

   m.values.flatMap(withoutElement(_, mov))
}

Upvotes: 2

Related Questions