根据Spark中值的相似性映射键值对

时间:2015-10-18 15:59:30

标签: scala apache-spark key-value keyvaluepair

我已经学习了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中找到某个键​​,然后将该值作为列表返回

2 个答案:

答案 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对你的初始方法不合适是对的。)

TL; DR;

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(_++=_)
  1. 我们现在有一个可变集合,为其优化了连接
  2. 我们使用++=代替++,因此每次融合现有的两个系列时,我们甚至不必分配全新的系列
  3. 如果我们知道或数据集很好,我们可以预先调整缓冲区的大小,以便具有可预测的内存分配,并尽可能避免缓冲区大小调整。或者相应地切换实现。
  4. flatMap

    的不同看法

    使用可变集合的收益

    原始实现再次使用了广泛的列表操作,例如takedrop,以及带索引的zip。

    使用可变集合在这里为我们提供了更好的可读性,因为我们可以替换3个不可变列表副本(takedrop++):

    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中有两条线是没用的,它没有任何意义而不是一条线。)