一个人滑动?

时间:2011-08-07 18:45:14

标签: scala collections functional-programming

不通过索引处理集合的一个优点是避免逐个错误。这肯定不是唯一的优势,但它就是其中之一。

现在,我经常在Scala的某些算法中使用sliding,但我觉得它通常会产生与逐个错误非常相似的内容,因为sliding {{1}大小为m的集合中的元素具有大小n个元素。或者,更为简单,n - m + 1是一个比list sliding 2短的元素。

我得到的感觉是,这种模式中缺少抽象,部分是list,更像是slidingfoldLeft。但是,我想不出那可能是什么。谁能帮助我在这里找到启示?

更新

由于人们不清楚我在说什么,让我们考虑一下这个案例。我想大写一个字符串。基本上,每个没有字母的字母都应该是大写字母,所有其他字母应该是小写字母。使用reduceLeft,我需要特殊情况下的第一个或最后一个字母。例如:

sliding

10 个答案:

答案 0 :(得分:6)

我正在把欧文的answer作为灵感。

如果要将简单diff()应用于列表,可以将其视为等效于以下矩阵乘法。

a = (0 1 4 3).T

M = ( 1 -1  0  0)
    ( 0  1 -1  0)
    ( 0  0  1 -1)

diff(a) = M * a = (1 3 1).T

如果我们替换加法和乘法(如果我们在矩阵M中推广数字),我们现在可以使用相同的方案进行一般列表操作。

因此,使用plus作为列表追加操作(之后使用flatten - 或只是collect操作),并且乘法等效为Some(_)None,窗口大小为2的幻灯片变为:

M = (Some(_) Some(_) None None)
    (None Some(_) Some(_) None)
    (None None Some(_) Some(_))

slide(a) = M “*” a = ((0 1) (1 4) (4 3)).T

不确定,如果这是你正在寻找的那种抽象,但它会改变一类改变项目数量的操作。

对于长度 n 的输入,

diffslide次序 m 的操作将需要使用大小为n-m + 1的矩阵×n。


修改:解决方案可以是将List[A]转换为List[Some[A]],然后将这些转换为slideLeftslideRight None。这样你就可以处理map方法中的所有魔法。

list.slideLeft(2) {
  case Seq(Some(c1), Some(c2)) if c2.isLetter => if (c1.isLetter) c2.toLower else c2.toUpper
  case Seq(_, Some(x)) => x
}

答案 1 :(得分:2)

我一直在python / R / Matlab中遇到这个问题,你在diff()中使用diff()向量,然后不能将其与原始行相符!这非常令人沮丧。

我认为真正缺少的是向量只保存依赖变量,并假设程序员正在跟踪独立的变量,即集合范围的维度。

我认为解决这个问题的方法是让语言在某种程度上跟踪自变量;也许通过类型静态地,或者通过将它们与向量一起存储来动态地。然后它可以检查独立的轴,确保它们排成一行,或者,我不知道这是否可行,随意改变它们 make 它们排成一行。

这是我迄今为止所想到的最好的。

修改

另一种思考方式是,为什么你的收藏有订单?为什么它不仅仅是一套?订单意味着什么,但是集合没有跟踪它 - 它基本上使用顺序位置(这与数字索引一样具有信息性)来代表真实含义。

修改

另一个结果是像sliding这样的转换实际上代表了两个转换,一个用于因变量,另一个用于它们的轴。

答案 2 :(得分:2)

在您的示例中,我认为代码变得更加复杂,因为您基本上想要执行map但是使用sliding,这会以一种不能很好地工作的方式引入边缘条件。我认为在概念上可以更容易地记住一个记住相关状态的累加器的折叠:

def capitalize2(s: String) = (("", true) /: s){ case ((res, notLetter), c) => 
  (res + (if (notLetter) c.toUpper else c.toLower), !c.isLetter)
}._1

我认为这可以推广,以便notLetter可以记住n个元素,其中n是滑动窗口的大小。

答案 3 :(得分:2)

您要求的转换本身会减少数据的大小。对不起 - 没有别的办法可以看了。 tail也会给你一个错误。

现在,您可能会说 - 嗯,很好,但我想要一种方便的方法来保持原始尺寸。

在这种情况下,您可能需要List上的这些方法:

initializedSliding(init: List[A]) = (init ::: this).sliding(1 + init.length)
finalizedSliding(tail: List[A]) = (this ::: tail).sliding(1 + tail.length)

将保持您的列表长度。 (你可以设想如何推广到非列表,我敢肯定。)

这是向左/向右折叠的模拟,因为您提供缺少的数据,以便对列表的每个元素执行成对(或更多)操作。

答案 4 :(得分:1)

逐个错误表明你试图将原始列表与滑动列表一一对应,但是由于滑动列表中的元素较少,所以会发生一些奇怪的事情。

您的示例的问题陈述可以粗略地表述为:“如果(a)是第一个字符,则大写每个字符,或者(b)跟随字母字符”。正如Owen所指出的,第一个字符是一个特例,任何抽象都应该尊重这一点。这是一种可能性,

def slidingPairMap[A, B](s: List[A], f1: A => B, f2: (A, A) => B): List[B] = s match {
  case Nil => Nil
  case x :: _ => f1(x) +: s.sliding(2).toList.map { case List(x, y) => f2(x, y) } 
}

(不是最好的实现,但你明白了)。这推广到滑动三元组,具有两个错误,等等。 slidingPairMap的类型清楚地表明正在进行特殊的套管。

等效签名可以是

def slidingPairMap[A, B](s: List[A], f: Either[A, (A, A)] => B): List[B]

然后f可以使用模式匹配来确定它是使用第一个元素还是后续元素。


或者,正如Owen在评论中所说,为什么不制作一个修改过的sliding方法来提供有关该元素是否为第一个的信息,

def slidingPairs[A](s: List[A]): List[Either[A, (A, A)]]

我猜这最后一个想法与Debilski在评论中建议的同构:使用None填充列表的开头,用Some包装所有现有元素,然后调用sliding

答案 5 :(得分:1)

您描述的一个问题让我想起了数字信号处理中的边界条件问题。由于数据(列表)是有限的,因此会出现问题。无限数据(流)不会发生这种情况。在数字信号处理中,通过将有限信号扩展到无限信号来解决问题。这可以通过各种方式完成,例如重复数据或重复数据并在每次重复时反转它(就像对离散余弦变换所做的那样)。

借用这些接近滑动的方法会导致抽象,但不会出现一个问题:

(1::2::3::Nil).sliding(2)

会产生

(1,2), (2,3), (3,1)

用于圆形边界条件和

(1,2), (2,3), (3,2)

用于具有反转的圆形边界条件。

答案 6 :(得分:1)

我意识到这是一个老问题,但我只是遇到了类似的问题,我想解决它而不必附加或前置任何东西,以及它将以无缝方式处理序列的最后元素。我想出的方法是slidingFoldLeft。你必须处理第一个元素作为特殊情况(像其他一些提到的那样,大写,这是一个特例),但是对于序列的结尾你可以像其他情况一样处理它。这是实现和一些愚蠢的例子:

def slidingFoldLeft[A, B] (seq: Seq[A], window: Int)(acc: B)(
    f: (B, Seq[A]) => B): B = {
  if (window > 0) {
    val iter = seq.sliding(window)
    iter.foldLeft(acc){
      // Operate normally
      case (acc, next) if iter.hasNext => f(acc, next)
      // It's at the last <window> elements of the seq, handle current case and 
      // call recursively with smaller window
      case (acc, next) =>
        slidingFoldLeft(next.tail, window - 1)(f(acc, next))(f)
    }
  } else acc
}

def capitalizeAndQuestionIncredulously(s: String) =
  slidingFoldLeft(s.toSeq, 2)("" + s(0).toUpper) {
    // Normal iteration
    case (acc, Seq(c1, c2)) if c1.isLetter && c2.isLetter => acc + c2.toLower
    case (acc, Seq(_, c2))  if c2.isLetter                => acc + c2.toUpper
    case (acc, Seq(_, c2))                                => acc + c2
    // Last element of string
    case (acc, Seq(c)) => acc + "?!"
  }

def capitalizeAndInterruptAndQuestionIncredulously(s: String) =
  slidingFoldLeft(s.toSeq, 3)("" + s(0).toUpper) {
    // Normal iteration
    case (acc, Seq(c1, c2, _)) if c1.isLetter && c2.isLetter => acc + c2.toLower
    case (acc, Seq(_, c2, _))  if c2.isLetter                => acc + c2.toUpper
    case (acc, Seq(_, c2, _))                                => acc + c2
    // Last two elements of string
    case (acc, Seq(c1, c2)) => acc + " (commercial break) " + c2
    // Last element of string
    case (acc, Seq(c)) => acc + "?!"
  }

println(capitalizeAndQuestionIncredulously("hello my name is mAtthew"))
println(capitalizeAndInterruptAndQuestionIncredulously("hello my name is mAtthew"))

输出:

Hello My Name Is Matthew?!
Hello My Name Is Matthe (commercial break) w?!

答案 7 :(得分:0)

在使用Some(_)元素进行映射后,我会先添加None;请注意,显而易见的方法(在默认情况下匹配两个Some,如在Debilski的编辑中所做的那样)是错误的,因为我们必须能够修改第一个字母。这样,抽象就意味着有时候根本没有前任。使用getOrElse(false)可确保将缺少的前任视为未通过测试。

((None +: "foo1bar".toSeq.map(Some(_))) sliding 2).map {
   case Seq(c1Opt, Some(c2)) if c2.isLetter => if (c1Opt.map(_.isLetter).getOrElse(false)) c2.toLower else c2.toUpper
   case Seq(_, Some(x)) => x
}.mkString
res13: String = "Foo1Bar"

致谢:通过Debilski的帖子,我想到了用Some(_)映射元素的想法。

答案 8 :(得分:0)

我不确定这是否能解决您的具体问题,但我们可以轻松想象一对方法,例如: slidingFromLeft(z: A, size: Int)slidingToRight(z: A, size: Int)(其中A是集合的元素类型),当被调用时,例如

List(1, 2, 3, 4, 5)

参数,例如(0,3),应分别生成

List(0, 0, 1), List(0, 1, 2), List(1, 2, 3), List(2, 3, 4), List(3, 4, 5)

List(1, 2, 3), List(2, 3, 4), List(3, 4, 5), List(4, 5, 0), List(5, 0, 0)

答案 9 :(得分:0)

这是一种很适合像J这样的面向数组的函数式语言的问题。基本上,我们生成一个布尔值,其中一个布尔值对应于每个单词的第一个字母。为此,我们从一个标记字符串中的空格的布尔值开始。例如(缩进三个空格的行是输入;结果与左边距齐平;“NB.”开始评论:

   str=. 'now  is  the    time'    NB. Example w/extra spaces for interest
   ]whspc=. ' '=str                NB. Mark where spaces are=1
0 0 0 1 1 0 0 1 1 0 0 0 1 1 1 1 0 0 0 0

验证(*.-.)(“而不是”)仅为“1 0”返回一个:

   ]tt=. #:i.4                     NB. Truth table
0 0
0 1
1 0
1 1
   (*.-.)/"1 tt                    NB. Apply to 1-D sub-arrays (rows)
0 0 1 0                            NB. As hoped.

在布尔值对中滑动我们的默认函数:

   2(*.-.)/\whspc                  NB. Apply to 2-ples
0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0

但是这不处理首字母的边缘条件,所以强制一个进入第一个位置。这实际上有所帮助,因为2-ples的减少让我们一短。这里我们比较原始布尔值和目标布尔值的长度:

   #whspc
20
   #1,2(*.-.)/\whspc
20
   1,2(*.-.)/\whspc
1 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0

我们通过使用小写向量中的索引从大写向量中选择(在定义这两个向量之后)得到大写:

   'lc uc'=. 'abcdefghijklmnopqrstuvwxyz';'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
   (uc,' '){~lc i. str
NOW  IS  THE    TIME

检查布尔值的插入是否给出了正确的结果:

       (1,2(*.-.)/\whspc) } str,:(uc,' '){~lc i. str
Now  Is  The    Time

现在是时候把所有这些结合成一个陈述:

   (1,2(*.-.)/\' '=str) } str,:(uc,' '){~lc i. str
Now  Is  The    Time