我已经学习了Spark几周,目前我正在尝试根据他们在Scala中使用Spark和Hadoop的连接来分组几个项目或人员。例如,我想看看足球运动员如何根据他们的俱乐部历史进行联系。我的“玩家”rdd将是:
(John, FC Sion)
(Mike, FC Sion)
(Bobby, PSV Eindhoven)
(Hans, FC Sion)
我想要这样的rdd:
(John, <Mike, Hans>)
(Mike, <John, Hans>)
(Bobby, <>)
(Hans, <Mike, John>)
我打算用map来完成这个。
val splitClubs = players.map(player=> (player._1, parseTeammates(player._2, players)))
parseTeammates是一个能够找到同一个俱乐部玩家的球员(球员.2)
// RDD is not a type, how can I insert rdd into a function?
def parseTeammates(club: String, rdd: RDD) : List[String] = {
// will generate a list of players that contains same "club" value
val playerList = rdd.filter(_._1 == club)
return playerList.values;
}
我得到编译错误,类型不匹配,因为函数应该返回List [String],而是playerList.values返回org.apache.spark.rdd.RDD [List [String]]。任何人都可以帮助我以简单的形式获取RDD的值(在我的例子中,List [String])?
另外,我认为有一种更优雅的方法来解决这个问题,而不是创建一个单独的RDD,然后在新的RDD中找到某个键,然后将该值作为列表返回
答案 0 :(得分:2)
我认为你的parseTeammates
方法在RDD世界中有点偏差。当涉及到处理RDD以及可能真正的,非常大量的数据时,您不希望进行这种嵌套循环。请尝试重新整理您的数据。
以下代码可为您提供所需内容
players.map{case(player, club) => (club, List(player))}
.reduceByKey(_++_)
.flatMap{case(_, list) =>list.zipWithIndex.map{case(player, index) => (player, list.take(index) ++ list.drop(index+1))}}
请注意,我首先根据他们所参加的俱乐部组织数据,然后将玩家组合起来以您正在寻找的格式产生结果。
我希望这会有所帮助。
答案 1 :(得分:0)
对@ Glennie的解决方案采取了不同的看法(IMO对你的初始方法不合适是对的。)
players.map { case (player, team) => (team, mutable.HashSet[String](player)) }
.reduceByKey(_++=_)
.flatMap {
case (team, players) => {
for (player <- players)
yield (player, players - player)
}
}
基本思路是一样的(构建一个由团队键入的团队成员列表,并flatMap
此结果)。但我建议使用其他构建块来获得相同的结果。这是否取胜取决于品味和数据集的性能特征。
reduceByKey
按键减少,这里涉及将一组(玩家)与一个或多个玩家联系起来。 如果我们采用原始代码:
players.map{case(player, club) => (club, List(player))}
.reduceByKey(_++_)
在内部,我们最终会调用类似(如scala 1.4):
def add: (List[String], List[String]) => List[String] = _++_;
players.map { case (player, team) => (team, List(player)) }
.combineByKey(
// The first time we see a new team on each partition
(list: List[String]) => list,
// invoked each time we fusion a player in its team's list
// (e.g. map side combine)
add,
// invoked each time we fusion each team's partial lists
// (e.g. reduce side combine)
add)
这里要说的是add
操作(_++_
)被多次调用。所以最好进行优化
在这方面,我们知道List
表现不佳,因为每个突变都需要将现有列表完全复制到另一个列表中。请注意:&#34;很差&#34;可能实际上是无关紧要的。如果你拥有数百万支球队,每支球队只有20名球员,那么++
的表现可能与其他涉及减少的火花计算相形见绌。
(在我的头脑中,更糟糕的是:如果List
变得非常大,看到其序列化中涉及的一些操作是递归实现的,我们可能会遇到堆栈溢出。我和#39;必须检查一下。)
因此我们可能会从切换到可变集合中受益,如下所示:
players.map { case (player, team) => (team, mutable.ArrayBuffer[String](player)) }
.reduceByKey(_++=_)
++=
代替++
,因此每次融合现有的两个系列时,我们甚至不必分配全新的系列flatMap
原始实现再次使用了广泛的列表操作,例如take
和drop
,以及带索引的zip。
使用可变集合在这里为我们提供了更好的可读性,因为我们可以替换3个不可变列表副本(take
,drop
,++
):
list.take(index) ++ list.drop(index+1)
只有一个(-
执行克隆)
list - list(index)
我们还可以提供完全不同的实现,避免使用索引进行压缩以利用理解:
.flatMap {
case (team, players) => {
for (player <- players)
yield (player, players - player)
}
}
请注意,players - player
步骤涉及查找列表中的播放器。使用ArrayBuffer
,这是一个O(n)操作。因此,根据数据集,我们可能会再次使用mutable.HashSet
作为可变集合而不是数组缓冲区,如果我们沿着这条路走下去。
(我打算添加,前提是我们在玩家名称中没有重复,但这没关系,因为如果你有两个&#34; John&#34; s在一个团队中,那么在两个约翰的RDD中有两条线是没用的,它没有任何意义而不是一条线。)