根据Scala中的特定条件更新变量

时间:2016-02-10 22:23:25

标签: scala apache-spark

我在C中有一系列元组,这是用户1的活动日志

scala> C.collect.foreach(println)
((1,A,1),1)
((1,B,2),1)
((1,C,4),2)
((1,D,7),3)
((1,E,15),8)
((1,F,16),1)

第一个元组中的第三个条目(1,2,4,7,15,16)是时间戳,第二个条目(1,1,2,3,8,1)是连续时间戳之间的差异。

我试图在第一次启动某个操作时或在一段时间TIMEOUT之后启动操作时创建一个会话。

我的计划是首先将ID分配给每个元组,然后将它们映射成对。 ID s将是它所属的会话中的第一个时间戳。

例如,如果TIMEOUT = 2,示例将映射到

(1, (1,"A",1))
(1, (1,"B",2))
(4, (1,"C",4)) //creation of a new session with ID 4
(7, (1,"D",7)) //creation of a new session with ID 7
(15, (1,"E",15)) //creation of a new session with ID 15
(15, (1,"F",16))

然后我将按会话处理数据。

但是,我在这种映射方面遇到了困难。

我需要保留某种全局变量来跟踪TIMEOUT中的最后一个时间戳,并在创建新会话时更新此变量,并将其设为ID随后的条目。

因为这是Spark,我使用Accumulator accum就像一个全局变量。

如果时间戳差异&gt; = 2,我不确定如何设置accum的值,然后将新值用作新会话的ID。如果时间戳差异< 2,会话的ID保持不变。

到目前为止我的尝试是

val accum = sc.accumulator(0, "My Accumulator")
C.map(x => (x._2 match {
  case _ if (x._2 > -2) => accum.setValue(x._1._3); accum.value
  case _ => accum.value
}, x._1)).collect

然后失败了。

我想这是因为accum.setValue()是一个带有副作用的语句,而不是一个值,scala中不允许这样做。此外,对象的变异在scala中不受欢迎。我也知道语法错了。但是,我想不出有任何其他方法可以做到这一点。

如何实现此映射?谢谢。

1 个答案:

答案 0 :(得分:0)

问题不在于副作用。 Scala中允许使用副作用。在功能代码中不鼓励他们。问题只是你需要将函数体放入{},如果你希望它有多个语句。同样使用匹配只有if是没有意义的。我还假设你想要条件&gt; = 2 not&gt; -2,至少这适合你的例子。

所以这应该有效:

val accum = sc.accumulator(0, "My Accumulator")
C.map(x =>
  (if (x._2 >= 2) {
    accum.setValue(x._1._3)
    accum.value
  } else accum.value,
  x._1)
).collect

唯一的问题是第一个ID,因为在您检测到第一个超时之前,您的ID将为0。但是你的例子并没有真正解释你如何处理这种边缘情况。

然而,我不会使用副作用来解决这个问题。序列上有一个scanLeft方法,允许您在访问前一个值时进行转换:

val list = List(
  ((1,"A",1),1),
  ((1,"B",2),1),
  ((1,"C",4),2),
  ((1,"D",7),3),
  ((1,"E",15),8),
  ((1,"F",16),1))
list.tail.scanLeft((list.head._1._1, list.head._1)){
  case ((id, _), ((a, b, id2), delta)) =>
    if(delta < 2) (id, (a,b,id2))
    else (id2, (a,b,id2))
}

这也解决了第一个id的问题,因为明确指定了第一个元素。这显然假设您的序列中至少有一个元素。