简体   繁体   English

Scala,让我的循环功能更强大

[英]Scala, make my loop more functional

I'm trying to reduce the extent to which I write Scala (2.8) like Java. 我正在努力减少像Java一样编写Scala(2.8)的程度。 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 通过丢弃值为'z'的任何条目并在遇到它们时索引字符

First try 第一次尝试

var outputMap = new mutable.HashMap[Char,Int]()
var counter = 0
for(kvp <- inputMap){
  val character = kvp._2
  if(character !='z' && !outputMap.contains(character)){
    outputMap += (character -> counter)
    counter += 1
  }
}

Second try (not much better, but uses an immutable map and a 'foreach') 第二次尝试(不是更好,但使用不可变的地图和'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
    }
  }
}

更好的解决方案:

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

I find this solution slightly simpler than arjan's : 我发现这个解决方案比arjan更简单:

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: 然后,为了摆脱某些东西,你使用filter方法:

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

and finally, to do the remapping, you can just use the values (but as a set) with zipWithIndex , which will count up from zero, and then convert back to a map: 最后,要进行重新映射,您可以使用zipWithIndex的值(但作为一组), zipWithIndex计数,然后转换回地图:

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. 看到Arjan的。 Assumption (*) is wrong, since it was a LinkedHashMap. 假设(*)是错误的,因为它是LinkedHashMap。 So you do need to preserve order, which Arjan's solution does. 所以你需要保留秩序,这是Arjan的解决方案所做的。

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. 这两个List.map可以放在一个,但我想你有一个大概。

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. 编辑:我误读了OP问题 - 认为你想要运行长度编码。 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. 编辑2:如评论中所述,如果保留顺序很重要,请使用toSeq.distinct或类似内容。

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. 考虑在构建列表时,列表的基础结构在附加新元素时不会更改,如果是真实列表,则追加是一个常量O(1)操作。 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. 坚持使用函数式语言设计的东西 - 列表操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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