【问题标题】:Multiple yields in sequence comprehension?序列理解中的多重收益?
【发布时间】:2011-03-17 14:47:27
【问题描述】:

我正在尝试学习 Scala,并尝试编写一个从序列中提取一元、二元和三元的序列理解。例如,[1,2,3,4] 应该转换为(不是 Scala 语法)

[1; _,1; _,_,1; 2; 1,2; _,1,2; 3; 2,3; 1,2,3; 4; 3,4; 2,3,4]

在 Scala 2.8 中,我尝试了以下方法:

def trigrams(tokens : Seq[T]) = {
  var t1 : Option[T] = None
  var t2 : Option[T] = None
  for (t3 <- tokens) {
    yield t3
    yield (t2,t3)
    yield (t1,t2,Some(t3))
    t1 = t2
    t2 = t3
  }
}

但这不能编译为apparently,在for-comprehension 中只允许一个yield(也没有块语句)。有没有其他优雅的方式来获得相同的行为,只需要传递一次数据?

【问题讨论】:

  • 不是 scala 程序员,但看起来您正在返回 1-tuple、2-tuple 和 3-tuple,它们都没有相同的数据类型。所以它可能不是类型检查。每个元素都应该具有相同的数据类型,例如本例中的 3 元组。在 F#/OCaml 中,您可以将不同的返回类型包装在一个联合中,例如let trigram = One of 'a | Two of 'a * 'a | Three of 'a * 'a * 'a,然后您可以yield One(t3); yield Two(t2, t3); yield Three(t1, t2, t3),其中返回类型为seq&lt;trigram&lt;Option&lt;'a&gt;&gt;&gt;
  • 它确实会检查我是否将结果声明为 Seq[Any],或者我是否使用派生自 Ngram 的类 UnigramBigramTrigram,而不是元组。

标签: scala yield list-comprehension


【解决方案1】:

在 for 循环中不能有多个 yield,因为 for 循环是 map(或 flatMap)操作的语法糖:

for (i <- collection) yield( func(i) )

翻译成

collection map {i => func(i)}

完全没有收益

for (i <- collection) func(i)

翻译成

collection foreach {i => func(i)}

所以整个for循环体变成了单个闭包,yield关键字的存在决定了对集合调用的函数是map还是foreach(或者flatMap )。由于这种翻译,以下内容被禁止:

  1. yield 旁边使用命令式语句来确定将产生什么。
  2. 使用多个收益

(更不用说您建议的版本将返回 List[Any],因为元组和 1-gram 都是不同的类型。您可能希望获得 List[List[Int]]

改用以下方法(将 n-gram 按它们出现的顺序排列):

val basis = List(1,2,3,4)
val slidingIterators = 1 to 4 map (basis sliding _)

for {onegram <- basis
     ngram <- slidingIterators if ngram.hasNext}
     yield (ngram.next)

val basis = List(1,2,3,4)
val slidingIterators = 1 to 4 map (basis sliding _)
val first=slidingIterators head
val buf=new ListBuffer[List[Int]]

while (first.hasNext)
   for (i <- slidingIterators)
      if (i.hasNext)
         buf += i.next

如果您希望 n-gram 按长度顺序排列,请尝试:

val basis = List(1,2,3,4)
1 to 4 flatMap { basis sliding _ toList }

【讨论】:

    【解决方案2】:
    scala> val basis = List(1, 2, 3, 4)
    basis: List[Int] = List(1, 2, 3, 4)
    
    scala> val nGrams = (basis sliding 1).toList ::: (basis sliding 2).toList ::: (basis sliding 3).toList
    nGrams: List[List[Int]] = ...
    
    scala> nGrams foreach (println _)
    List(1)
    List(2)
    List(3)
    List(4)
    List(1, 2)
    List(2, 3)
    List(3, 4)
    List(1, 2, 3)
    List(2, 3, 4)
    

    【讨论】:

    • 由于::: 运算符,这会有点慢。
    • 串联不一定要发生。我只是想说明sliding n 完成了提取n-grams 的基本工作。而它是否真的“慢”取决于其他参数。
    【解决方案3】:

    我想我应该多考虑一下。

    def trigrams(tokens : Seq[T]) : Seq[(Option[T],Option[T],T)] = {
      var t1 : Option[T] = None
      var t2 : Option[T] = None
      for (t3 <- tokens)
        yield {
          val tri = (t1,t2,t3)
          t1 = t2
          t2 = Some(t3)
          tri
        }
    }
    

    然后从三元组中提取一元组和二元组。但是任何人都可以向我解释为什么不允许“多收益”,以及是否有任何其他方法可以达到其效果?

    【讨论】:

    • 要达到它们的效果,请尝试添加另一个嵌套级别的迭代,就像我第一个针对您的问题提出的解决方案一样。
    • 多个收益(如果您将它们视为它们不是的退出点)会将简单的闭包变成协程,这是它们不容易处理的原因之一。然而,延续插件有一些解决方案。
    【解决方案4】:
    val basis = List(1, 2, 3, 4)
    val nGrams = basis.map(x => (x)) ::: (for (a <- basis; b <- basis) yield (a, b)) ::: (for (a <- basis; b <- basis; c <- basis) yield (a, b, c))
    nGrams: List[Any] = ...
    nGrams foreach (println(_))
    1
    2
    3
    4
    (1,1)
    (1,2)
    (1,3)
    (1,4)
    (2,1)
    (2,2)
    (2,3)
    (2,4)
    (3,1)
    (3,2)
    (3,3)
    (3,4)
    (4,1)
    (4,2)
    (4,3)
    (4,4)
    (1,1,1)
    (1,1,2)
    (1,1,3)
    (1,1,4)
    (1,2,1)
    (1,2,2)
    (1,2,3)
    (1,2,4)
    (1,3,1)
    (1,3,2)
    (1,3,3)
    (1,3,4)
    (1,4,1)
    (1,4,2)
    (1,4,3)
    (1,4,4)
    (2,1,1)
    (2,1,2)
    (2,1,3)
    (2,1,4)
    (2,2,1)
    (2,2,2)
    (2,2,3)
    (2,2,4)
    (2,3,1)
    (2,3,2)
    (2,3,3)
    (2,3,4)
    (2,4,1)
    (2,4,2)
    (2,4,3)
    (2,4,4)
    (3,1,1)
    (3,1,2)
    (3,1,3)
    (3,1,4)
    (3,2,1)
    (3,2,2)
    (3,2,3)
    (3,2,4)
    (3,3,1)
    (3,3,2)
    (3,3,3)
    (3,3,4)
    (3,4,1)
    (3,4,2)
    (3,4,3)
    (3,4,4)
    (4,1,1)
    (4,1,2)
    (4,1,3)
    (4,1,4)
    (4,2,1)
    (4,2,2)
    (4,2,3)
    (4,2,4)
    (4,3,1)
    (4,3,2)
    (4,3,3)
    (4,3,4)
    (4,4,1)
    (4,4,2)
    (4,4,3)
    (4,4,4)
    

    【讨论】:

    • 不,这给出了(我认为被称为)多集组合的列表。我正在寻找 n-gram,子字符串。它还经过basis 3 次。顺便问一下,::: 是什么?
    • basis 被迭代六次。 :::List 连接。
    • 性能不是问题的一部分,是吗? ::: O(n) 其中 n 是其 RHS 参数的长度。
    • ::: 是 O(n),其中 n 是其 LHS 参数的长度。
    【解决方案5】:

    您可以尝试没有分配的功能版本:

    def trigrams[T](tokens : Seq[T]) = {
      val s1 = tokens.map { Some(_) }
      val s2 = None +: s1
      val s3 = None +: s2
      s1 zip s2 zip s3 map {
        case ((t1, t2), t3) => (List(t1), List(t1, t2), List(t1, t2, t3))
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-07-20
      • 2014-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-03
      • 2010-12-21
      • 2020-12-16
      相关资源
      最近更新 更多