开发者

Scala, make my loop more functional

开发者 https://www.devze.com 2023-02-01 03:09 出处:网络
I\'m trying to reduce the extent to which I write Scala (2.8) like Java.Here\'s a simplification of a problem I came across.Can you suggest improvements on my solutions that are \"more functional\"?

I'm trying to reduce the extent to which I write Scala (2.8) like Java. Here's a simplification of a problem I came across. Can you suggest improvements on my solutions that are "more functional"?

Transform the map

val inputMap = mutable.LinkedHashMap(1->'a',2->'a',3->'b',4->'z',5->'c')

by discarding any entries with value 'z' and indexing the characters as they are encountered

First try

var outputMap = new mutable.HashMap[Char,Int]()
var counter = 0
for(kvp <- inputMap){
  val character = kvp._2
  i开发者_StackOverflow社区f(character !='z' && !outputMap.contains(character)){
    outputMap += (character -> counter)
    counter += 1
  }
}

Second try (not much better, but uses an immutable map and a 'foreach')

var outputMap = new immutable.HashMap[Char,Int]()
var counter = 0
inputMap.foreach{
  case(number,character) => {
    if(character !='z' && !outputMap.contains(character)){
      outputMap2 += (character -> counter)
      counter += 1
    }
  }
}


Nicer solution:

inputMap.toList.filter(_._2 != 'z').map(_._2).distinct.zipWithIndex.toMap


I find this solution slightly simpler than arjan's:

inputMap.values.filter(_ != 'z').toSeq.distinct.zipWithIndex.toMap

The individual steps:

inputMap.values       // Iterable[Char]   = MapLike(a, a, b, z, c)
   .filter(_ != 'z')  // Iterable[Char]   = List(a, a, b, c)
   .toSeq.distinct    // Seq[Char]        = List(a, b, c)
   .zipWithIndex      // Seq[(Char, Int)] = List((a,0), (b,1), (c,2))
   .toMap             // Map[Char, Int]   = Map((a,0), (b,1), (c,2))

Note that your problem doesn't inherently involve a map as input, since you're just discarding the keys. If I were coding this, I'd probably write a function like

def buildIndex[T](s: Seq[T]): Map[T, Int] = s.distinct.zipWithIndex.toMap

and invoke it as

buildIndex(inputMap.values.filter(_ != 'z').toSeq)


First, if you're doing this functionally, you should use an immutable map.

Then, to get rid of something, you use the filter method:

inputMap.filter(_._2 != 'z')

and finally, to do the remapping, you can just use the values (but as a set) withzipWithIndex, which will count up from zero, and then convert back to a map:

inputMap.filter(_._2 != 'z').values.toSet.zipWithIndex.toMap

Since the order of values isn't going to be preserved anyway*, presumably it doesn't matter that the order may have been shuffled yet again with the set transformation.

Edit: There's a better solution in a similar vein; see Arjan's. Assumption (*) is wrong, since it was a LinkedHashMap. So you do need to preserve order, which Arjan's solution does.


i would create some "pipeline" like this, but this has a lot of operations and could be probably shortened. These two List.map's could be put in one, but I think you've got a general idea.

inputMap
.toList // List((5,c), (1,a), (2,a), (3,b), (4,z))
.sorted // List((1,a), (2,a), (3,b), (4,z), (5,c))
.filterNot((x) => {x._2 == 'z'}) // List((1,a), (2,a), (3,b), (5,c))
.map(_._2) // List(a, a, b, c)
.zipWithIndex // List((a,0), (a,1), (b,2), (c,3))
.map((x)=>{(x._2+1 -> x._1)}) // List((1,a), (2,a), (3,b), (4,c))
.toMap // Map((1,a), (2,a), (3,b), (4,c))

performing these operation on lists keeps ordering of elements.


EDIT: I misread the OP question - thought you wanted run length encoding. Here's my take on your actual question:

val values = inputMap.values.filterNot(_ == 'z').toSet.zipWithIndex.toMap

EDIT 2: As noted in the comments, use toSeq.distinct or similar if preserving order is important.

val values = inputMap.values.filterNot(_ == 'z').toSeq.distinct.zipWithIndex.toMap


In my experience I have found that maps and functional languages do not play nice. You'll note that all answers so far in one way or another in involve turning the map into a list, filtering the list, and then turning the list back into a map.

I think this is due to maps being mutable data structures by nature. Consider that when building a list, that the underlying structure of the list does not change when you append a new element and if a true list then an append is a constant O(1) operation. Whereas for a map the internal structure of a map can vastly change when a new element is added ie. when the load factor becomes too high and the add algorithm resizes the map. In this way a functional language cannot just create a series of a values and pop them into a map as it goes along due to the possible side effects of introducing a new key/value pair.

That said, I still think there should be better support for filtering, mapping and folding/reducing maps. Since we start with a map, we know the maximum size of the map and it should be easy to create a new one.

If you're wanting to get to grips with functional programming then I'd recommending steering clear of maps to start with. Stick with the things that functional languages were designed for -- list manipulation.

0

精彩评论

暂无评论...
验证码 换一张
取 消