简体   繁体   中英

Scala for loop Replace on List

Maybe this might be easy to fix but can you help me out or guide me to a solution. I have a remove function that goes through a List of tuples "List[(String,Any)]" and im trying to replace the 1 index of the value with Nil when the list is being looped over.

But when I try to replace the current v with Nil, it say the v is assigned to "val". Now I understand that scala lists are immutable. So maybe this is what is going wrong?

I tried a Tail recursion implementation as will but when I get out of the def there is a type mismatch. ie: is unit but required: Option[Any]

// remove(k) removes one value v associated with key k
// from the dictionary, if any, and returns it as Some(v).  
// It returns None if k is associated to no value.  
def remove(key:String):Option[Any] = {
    for((k,v) <- d){
        if(k == key){
            var temp:Option[Any] = Some(v)
            v = Nil
            return temp
        } 
    }; None
}

Here was the other way of trying to figure out

  def remove(key:String):Option[Any] = {
    def removeHelper(l:List[(String,Any)]):List[(String,Any)] =
      l match {
        case Nil => Nil
        case (k,v)::t => if (key == k) t else (k,v)::removeHelper(t)
      }
    d = removeHelper(d)
  }

Any Suggestions? This is a homework/Project for school thought I might add that for the people that don't like to help with homework.

Well, there are many ways of answering that question. I'll be outlining the ones I can think of here with my own implementations, but the list is by no means exhaustive (nor, probably, the implementations optimal).

First, you can try with existing combinators - the usual suspects are map , flatMap , foldLeft and foldRight :

def remove_flatMap(key: String, list: List[(String, Any)]): List[(String, Any)] =
  // The Java developer in me rebels against creating that many "useless" instances.
  list.flatMap {a => if(a._1 == key) Nil else List(a)}

def remove_foldLeft(key: String, list: List[(String, Any)]): List[(String, Any)] =
  list.foldLeft(List[(String, Any)]()) {(acc, a) =>
    if(a._1 == key) acc
    else            a :: acc
  // Note the call to reverse here.
  }.reverse

// This is more obviously correct than the foldLeft version, but is not tail-recursive.
def remove_foldRight(key: String, list: List[(String, Any)]): List[(String, Any)] =
  list.foldRight(List[(String, Any)]()) {(a, acc) =>
    if(a._1 == key) acc
    else            a :: acc
  }

The problem with these is that, as far as I'm aware, you cannot stop them once a certain condition has been reached: I don't think they solve your problem directly, since they remove all instances of key rather than the first.

You also want to note that:

  • foldLeft must reverse the list once it's done, since it appends elements in the "wrong" order.
  • foldRight doesn't have that flaw, but is not tail recursive: it will cause memory issues on large lists.
  • map cannot be used for your problem, since it only lets us modify a list's values but not its structure.

You can also use your own implementation. I've included two versions, one that is tail-recursive and one that is not. The tail-recursive one is obviously the better one, but is also more verbose (I blame the ugliness of using a List[(String, Any)] rather than Map[String, Any] :

def remove_nonTailRec(key: String, list: List[(String, Any)]): List[(String, Any)] = list match {
  case h :: t if h._1 == key => t
  // This line is the reason our function is not tail-recursive.
  case h :: t                => h :: remove_nonTailRec(key, t)
  case Nil                   => Nil
}

def remove_tailRec(key: String, list: List[(String, Any)]): List[(String, Any)] = {
  @scala.annotation.tailrec
  def run(list: List[(String, Any)], acc: List[(String, Any)]): List[(String, Any)] = list match {
    // We've been aggregating in the "wrong" order again...
    case h :: t if h._1 == key => acc.reverse ::: t
    case h :: t                => run(t, h :: acc)
    case Nil                   => acc.reverse
  }

  run(list, Nil)

}

The better solution is of course to use the right tool for the job: a Map[String, Any] .

Note that I do not think I answer your question fully: my examples remove key , while you want to set it to Nil . Since this is your homework, I'll let you figure out how to change my code to match your requirements.

List is the wrong collection to use if any key should only exist once. You should be using Map[String,Any] . With a list,

  1. You have to do extra work to prevent duplicate entries.
  2. Retrieval of a key will be slower, the further down the list it appears. Attempting to retrieve a non-existent key will be slow in proportion to the size of the list.

I guess point 2 is maybe why you are trying to replace it with Nil rather than just removing the key from the list. Nil is not the right thing to use here, really. You are going to get different things back if you try and retrieve a non-existent key compared to one that has been removed . Is that really what you want? How much sense does it make to return Some(Nil) , ever?

Here's a couple of approaches which work with mutable or immutable lists, but which don't assume that you successfully stopped duplicates creeping in...

val l1: List[(String, Any)] = List(("apple", 1), ("pear", "violin"), ("banana", Unit))
val l2: List[(Int, Any)] = List((3, 1), (4, "violin"), (7, Unit))

def remove[A,B](key: A, xs: List[(A,B)]) = (
  xs collect { case x if x._1 == key => x._2 }, 
    xs map { case x if x._1 != key => x; case _ => (key, Nil) }
)

scala>  remove("apple", l1)
res0: (List[(String, Any)], List[(String, Any)]) = (List((1)),List((apple, List()),(pear,violin), (banana,object scala.Unit)))

scala> remove(4, l2)
res1: (List[(Int, Any)], List[(Int, Any)]) = (List((violin)),List((3,1), (4, List()), (7,object scala.Unit)))

scala> remove("snark", l1)
res2: (List[Any], List[(String, Any)]) = (List(),List((apple,1), (pear,violin), (banana,object scala.Unit)))

That returns a list of matching values (so an empty list rather than None if no match) and the remaining list, in a tuple. If you want a version that just completely removes the unwanted key, do this...

def remove[A,B](key: A, xs: List[(A,B)]) = (
  xs collect { case x if x._1 == key => x._2 }, 
  xs filter { _._1 != key }
)

But also look at this:

scala> l1 groupBy {
         case (k, _) if k == "apple" => "removed",
         case _ => "kept"
       }
res3: scala.collection.immutable.Map[String,List[(String, Any)]] = Map(removed -> List((apple,1)), kept -> List((pear,violin), (banana,object scala.Unit)))

That is something you could develop a bit. All you need to do is add ("apple", Nil) to the "kept" list and extract the value(s) from the "removed" list.

Note that I am using the List combinator functions rather than writing my own recursive code; this usually makes for clearer code and is often as fast or faster than a hand-rolled recursive function.

Note also that I don't change the original list. This means my function works with both mutable and immutable lists. If you have a mutable list, feel free to assign my returned list as the new value for your mutable var . Win, win.

But please use a map for this. Look how simple things become:

val m1: Map[String, Any] = Map(("apple", 1), ("pear", "violin"), ("banana", Unit))
val m2: Map[Int, Any] = Map((3, 1), (4, "violin"), (7, Unit))

def remove[A,B](key: A, m: Map[A,B]) = (m.get(key), m - key)

scala> remove("apple", m1)
res0: (Option[Any], scala.collection.immutable.Map[String,Any]) = (Some(1),Map(pear -> violin, banana -> object scala.Unit))

scala> remove(4, m2)
res1: (Option[Any], scala.collection.immutable.Map[Int,Any]) = (Some(violin),Map(3 -> 1, 7 -> object scala.Unit))

scala> remove("snark", m1)
res2: res26: (Option[Any], scala.collection.immutable.Map[String,Any]) = (None,Map(apple -> 1, pear -> violin, banana -> object scala.Unit))

The combinator functions make things easier, but when you use the right collection, it becomes so easy that it is hardly worth writing a special function. Unless, of course, you are trying to hide the data structure - in which case you should really be hiding it inside an object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM