简体   繁体   English

与foldleft scala分组

[英]group by with foldleft scala

I have the following list in input: 输入中有以下列表:

val listInput1 = 
  List(
    "itemA,CATs,2,4",
    "itemA,CATS,3,1",
    "itemB,CATQ,4,5",
    "itemB,CATQ,4,6",
    "itemC,CARC,5,10")

and I want to write a function in scala using groupBy and foldleft ( just one function) in order to sum up third and fourth colum for lines having the same title(first column here), the wanted output is : 并且我想使用groupBy和foldleft在scala中编写一个函数(仅一个函数),以便对具有相同标题的行的第三和第四列(此处为第一列)求和,所需的输出为:

val listOutput1 = 
      List(
         "itemA,CATS,5,5",
         "itemB,CATQ,8,11",
         "itemC,CARC,5,10"

       )


 def sumIndex (listIn:List[String]):List[String]={

 listIn.map(_.split(",")).groupBy(_(0)).map{ 
  case (title, label) => 
       "%s,%s,%d,%d".format(
         title,
         label.head.apply(1),
         label.map(_(2).toInt).sum,
         label.map(_(3).toInt).sum)}.toList

} }

Kind regards 亲切的问候

You can solve it with a single foldLeft, iterating the input list only once. 您可以使用一个foldLeft来解决它,只对输入列表进行一次迭代。 Use a Map to aggregate the result. 使用地图汇总结果。

listInput1.map(_.split(",")).foldLeft(Map.empty[String, Int]) {
  (acc: Map[String, Int], curr: Array[String]) =>
    val label: String = curr(0)
    val oldValue: Int = acc.getOrElse(label, 0)
    val newValue: Int = oldValue + curr(2).toInt + curr(3).toInt
    acc.updated(label, newValue)
}

result: Map(itemA -> 10, itemB -> 19, itemC -> 15) 结果:Map(itemA-> 10,itemB-> 19,itemC-> 15)

If you have a list as 如果您有一个清单

val listInput1 =
  List(
    "itemA,CATs,2,4",
    "itemA,CATS,3,1",
    "itemB,CATQ,4,5",
    "itemB,CATQ,4,6",
    "itemC,CARC,5,10")

Then you can write a general function that can be used with foldLeft and reduceLeft as 然后,您可以编写一个可以与foldLeftreduceLeft一起使用的通用函数foldLeft reduceLeft

def accumulateLeft(x: Map[String, Tuple3[String, Int, Int]], y: Map[String, Tuple3[String, Int, Int]]): Map[String, Tuple3[String, Int, Int]] ={
  val key = y.keySet.toList(0)
  if(x.keySet.contains(key)){
    val oldTuple = x(key)
    x.updated(key, (y(key)._1, oldTuple._2+y(key)._2, oldTuple._3+y(key)._3))
  }
  else{
    x.updated(key, (y(key)._1, y(key)._2, y(key)._3))
  }
}

and you can call them as 你可以称他们为

foldLeft foldLeft

listInput1
  .map(_.split(","))
  .map(array => Map(array(0) -> (array(1), array(2).toInt, array(3).toInt)))
  .foldLeft(Map.empty[String, Tuple3[String, Int, Int]])(accumulateLeft)
  .map(x => x._1+","+x._2._1+","+x._2._2+","+x._2._3)
  .toList
//res0: List[String] = List(itemA,CATS,5,5, itemB,CATQ,8,11, itemC,CARC,5,10)

reduceLeft reduceLeft

listInput1
  .map(_.split(","))
  .map(array => Map(array(0) -> (array(1), array(2).toInt, array(3).toInt)))
  .reduceLeft(accumulateLeft)
  .map(x => x._1+","+x._2._1+","+x._2._2+","+x._2._3)
  .toList
//res1: List[String] = List(itemA,CATS,5,5, itemB,CATQ,8,11, itemC,CARC,5,10)

Similarly you can just interchange the variables in the general function so that it can be used with foldRight and reduceRight as 同样,您可以在常规函数中互换变量,以便可以将foldRightreduceRight

def accumulateRight(y: Map[String, Tuple3[String, Int, Int]], x: Map[String, Tuple3[String, Int, Int]]): Map[String, Tuple3[String, Int, Int]] ={
  val key = y.keySet.toList(0)
  if(x.keySet.contains(key)){
    val oldTuple = x(key)
    x.updated(key, (y(key)._1, oldTuple._2+y(key)._2, oldTuple._3+y(key)._3))
  }
  else{
    x.updated(key, (y(key)._1, y(key)._2, y(key)._3))
  }
}

and calling the function would give you 并调用该函数会给你

foldRight foldRight

listInput1
  .map(_.split(","))
  .map(array => Map(array(0) -> (array(1), array(2).toInt, array(3).toInt)))
  .foldRight(Map.empty[String, Tuple3[String, Int, Int]])(accumulateRight)
  .map(x => x._1+","+x._2._1+","+x._2._2+","+x._2._3)
  .toList
//res2: List[String] = List(itemC,CARC,5,10, itemB,CATQ,8,11, itemA,CATs,5,5)

reduceRight reduceRight

listInput1
  .map(_.split(","))
  .map(array => Map(array(0) -> (array(1), array(2).toInt, array(3).toInt)))
  .reduceRight(accumulateRight)
  .map(x => x._1+","+x._2._1+","+x._2._2+","+x._2._3)
  .toList
//res3: List[String] = List(itemC,CARC,5,10, itemB,CATQ,8,11, itemA,CATs,5,5)

So you don't really need a groupBy and can use any of the foldLeft , foldRight , reduceLeft or reduceRight functions to get your desired output. 因此,您实际上并不需要groupBy ,可以使用foldLeftfoldRightreduceLeftreduceRight函数中的任何一个来获取所需的输出。

The logic in your code looks sound, here it is with a case class implemented as that handles edge cases more cleanly: 代码中的逻辑看起来很合理,这里是用一个case class实现的,因为它可以更清晰地处理边缘案例:

// represents a 'row' in the original list
case class Item(
                 name: String,
                 category: String,
                 amount: Int,
                 price: Int
               )

// safely converts the row of strings into case class, throws exception otherwise
def stringsToItem(strings: Array[String]): Item = {
  if (strings.length != 4) {
    throw new Exception(s"Invalid row: ${strings.foreach(print)}; must contain only 4 entries!")
  } else {
    val n = strings.headOption.getOrElse("N/A")
    val cat = strings.lift(1).getOrElse("N/A")
    val amt = strings.lift(2).filter(_.matches("^[0-9]*$")).map(_.toInt).getOrElse(0)
    val p = strings.lastOption.filter(_.matches("^[0-9]*$")).map(_.toInt).getOrElse(0)

    Item(n, cat, amt, p)
  }
}

// original code with case class and method above used
listInput1.map(_.split(","))
  .map(stringsToItem)
  .groupBy(_.name)
  .map { case (name, items) =>
    Item(
      name,
      category = items.head.category,
      amount = items.map(_.amount).sum,
      price = items.map(_.price).sum
    )
  }.toList

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM