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

根据Scala中的特定条件更新变量,scala,apache-spark,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启动操作时,我都会尝试创建一个会话 我的计划是首先

我在
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
s,然后将它们成对地映射
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
s

因为这是
Spark
,所以我像使用全局变量一样使用
累加器
acum

如果时间戳差异>=2,我不知道如何设置
acum
的值,然后将新值用作新会话的
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
中,对象的突变是不受欢迎的。我也知道语法是错误的。然而,我想不出任何其他方法来做到这一点


如何实现此映射?谢谢。

问题不在于副作用。在Scala中,只要你愿意,副作用都是允许的。功能代码中不鼓励它们。问题只是,如果希望函数体中包含多个语句,就需要将函数体放入{}。同样,仅使用匹配来获得if也是毫无意义的。我还假设您希望条件>=2而不是>-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))
}
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.scanlight((list.head.\u 1.\u 1,list.head.\u 1)){
大小写((id,41;),((a,b,id2),delta))=>
if(δ<2)(id,(a,b,id2))
其他(id2,(a,b,id2))
}

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

问题不在于副作用。在Scala中,只要你愿意,副作用都是允许的。功能代码中不鼓励它们。问题只是,如果希望函数体中包含多个语句,就需要将函数体放入{}。同样,仅使用匹配来获得if也是毫无意义的。我还假设您希望条件>=2而不是>-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))
}
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.scanlight((list.head.\u 1.\u 1,list.head.\u 1)){
大小写((id,41;),((a,b,id2),delta))=>
if(δ<2)(id,(a,b,id2))
其他(id2,(a,b,id2))
}

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

请不要使用元组对这样的数据进行分组。用例类。更容易阅读,更安全,更容易使用。你有多少记录?有多少用户?平均会话长度是多少?您希望每个会话有多少个条目?您是否需要准确的结果,或者是否可以接受一些误报?什么是总时间线(最小时间戳-最大时间戳)?嗨,我会发布更多的细节后,我把他们弄清楚。谢谢。请不要使用元组对这样的数据进行分组。用例类。更容易阅读,更安全,更容易使用。你有多少记录?有多少用户?平均会话长度是多少?您希望每个会话有多少个条目?您是否需要准确的结果,或者是否可以接受一些误报?什么是总时间线(最小时间戳-最大时间戳)?嗨,我会发布更多的细节后,我把他们弄清楚。谢谢,这样不行。它看起来像是
C
RDD
而不是
Iterable
。此外,您无法访问转换内部的累加器值。我不知道spark。查看来自RDD的文档,第一个示例将在语法上工作,因为RDD有一个映射方法。但是,由于RDD实现了一些分布式魔法,因此副作用将不起作用。然而,这与Scala本身无关。对于第二个示例:如果您首先收集数据,这将起作用。RDD没有扫描。可以尝试将其转换为折叠,但我认为这种本质上的顺序操作在分布式数据结构(如RDD)上没有多大意义