简体   繁体   English

Scala-如何以功能方式在列表周围循环

[英]Scala - how to loop around list in functional way

I need to iterate over a list and compare the previous element in the list to current. 我需要遍历一个列表,并将列表中的上一个元素与当前元素进行比较。 Being from traditional programming background, I used for loop with a variable to hold the previous value but with Scala I believe there should be a better way to do it. 从传统的编程背景开始,我使用带变量的循环来保存先前的值,但是对于Scala,我认为应该有一种更好的方法。

for (item <- dataList)
{
   // Code to compare previous list element
   prevElement = item
}

The code above works and give correct results but I wanted to explore the functional way of writing the same code. 上面的代码可以正常工作并给出正确的结果,但是我想探索编写相同代码的功能方法。

There are a number of different possible solutions to this. 有许多不同的解决方案。 Perhaps the most generic is to use a helper function: 也许最通用的是使用辅助函数:

// This example finds the minimum value (the hard way) in a list of integers. It's
// not looking at the previous element, as such, but you can use the same approach.
// I wanted to make the example something meaningful.
def findLowest(xs: List[Int]): Option[Int] = {

  @tailrec // Guarantee tail-recursive implementation for safety & performance.
  def helper(lowest: Int, rem: List[Int]): Int = {

    // If there are no elements left, return lowest found.
    if(rem.isEmpty) lowest

    // Otherwise, determine new lowest value for next iteration. (If you need the
    // previous value, you could put that here.)
    else {
      val newLow = Math.min(lowest, rem.head)
      helper(newLow, rem.tail)
    }
  }

  // Start off looking at the first member, guarding against an empty list.
  xs match {
    case x :: rem => Some(helper(x, rem))
    case _ => None
  }
}

In this particular case, this can be replaced by a fold . 在这种特殊情况下,可以用fold代替。

def findLowest(xs: List[Int]): Option[Int] = xs match {
  case h :: _ => Some(xs.fold(h)((b, m) => Math.min(b, m)))
  case _ => None
}

which can be simplified further to just: 可以进一步简化为:

def findLowest(xs: List[Int]): Option[Int] = xs match {
  case h :: _ => Some(xs.foldLeft(h)(Math.min))
  case _ => None
}

In this case, we're using the zero argument of the fold operation to store the minimum value. 在这种情况下,我们使用fold操作的zero参数存储最小值。

If this seems too abstract, can you post more details about what you would like to do in your loop, and I can address that in my answer? 如果这看起来太抽象了,您可以在循环中发布更多有关您想做的事情的详细信息,我可以在回答中解决这个问题吗?

Update : OK, so having seen your comment about the dates, here's a more specific version. 更新 :好的,因此,看到您对日期的评论后,这里是一个更具体的版本。 I've used DateRange (which you will need to replace with your actual type) in this example. 在此示例中,我使用了DateRange (您需要将其替换为实际类型)。 You will also have to determine what overlap and merge need to do. 您还必须确定需要进行哪些overlapmerge But here's your solution: 但是,这是您的解决方案:

// Determine whether two date ranges overlap. Return true if so; false otherwise.
def overlap(a: DateRange, b: DateRange): Boolean = { ... }

// Merge overlapping a & b date ranges, return new range.
def merge(a: DateRange, b: DateRange): DateRange = { ... }

// Convert list of date ranges into another list, in which all consecutive,
// overlapping ranges are merged into a single range.
def mergeDateRanges(dr: List[DateRange]): List[DateRange] = {

  // Helper function. Builds a list of merged date ranges.
  def helper(prev: DateRange, ret: List[DateRange], rem: List[DateRange]):
  List[DateRange] = {

    // If there are no more date ranges to process, prepend the previous value
    // to the list that we'll return.
    if(rem.isEmpty) prev :: ret

    // Otherwise, determine if the previous value overlaps with the current
    // head value. If it does, create a new previous value that is the merger
    // of the two and leave the returned list alone; if not, prepend the
    // previous value to the returned list and make the previous value the
    // head value.
    else {
      val (newPrev, newRet) = if(overlap(prev, rem.head)) {
        (merge(prev, rem.head), ret)
      }
      else (rem.head, prev :: ret)

      // Next iteration of the helper (I missed this off, originally, sorry!)
      helper(newPrev, newRet, rem.tail)
    }
  }

  // Start things off, trapping empty list case. Because the list is generated by pre-pending elements, we have to reverse it to preserve the order.
  dr match {
    case h :: rem => helper(h, Nil, rem).reverse // <- Fixed bug here too...
    case _ => Nil
  }
}

If you're looking to just compare the two sequential elements and get the boolean result: 如果您只想比较两个连续的元素并获得布尔结果:

val list = Seq("one", "two", "three", "one", "one")
val result = list.sliding(2).collect { case Seq(a,b) => a.equals(b)}.toList

The previous will give you this 前一个会给你这个

List(false, false, false, true) 列表(false,false,false,true)

An alternative to the previous one is that you might want to get the value: 替代上一个方法是,您可能想要获取该值:

val result = list.sliding(2).collect { 
  case Seq(a,b) => if (a.equals(b)) Some(a) else None
  case _ => None
}.toList.filter(_.isDefined).map(_.get)

This will give the following: 这将给出以下内容:

List(one) 列表(之一)

But if you're just looking to compare items in list, an option would be to do something like this: 但是,如果您只是想比较列表中的项目,则可以选择执行以下操作:

val list = Seq("one", "two", "three", "one")

for {
  item1 <- list
  item2 <- list
  _ <- Option(println(item1 + ","+ item2))
} yield ()

This results in: 结果是:

one,one
one,two
one,three
one,one
two,one
two,two
two,three
two,one
three,one
three,two
three,three
three,one
one,one
one,two
one,three
one,one

If you are not mutating the list, and are only trying to compare two elements side-by-side. 如果您不更改列表,而只是尝试并排比较两个元素。 This can help you. 这可以为您提供帮助。 It's functional. 它是功能性的。

def cmpPrevElement[T](l: List[T]): List[(T,T)] = {
  (l.indices).sliding(2).toList.map(e => (l(e.head), l(e.tail.head)))
}

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

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