简体   繁体   English

Scala:如何将序列的子集映射到较短的序列

[英]Scala: How to map a subset of a seq to a shorter seq

I am trying to map a subset of a sequence using another (shorter) sequence while preserving the elements that are not in the subset. 我正在尝试使用另一个(较短)序列映射一个序列的子集,同时保留不在该子集中的元素。 A toy example below tries to give a flower to females only: 下面的玩具示例尝试只给雌性送花:

def giveFemalesFlowers(people: Seq[Person], flowers: Seq[Flower]): Seq[Person] = {
  require(people.count(_.isFemale) == flowers.length)
  magic(people, flowers)(_.isFemale)((p, f) => p.withFlower(f))
}

def magic(people: Seq[Person], flowers: Seq[Flower])(predicate: Person => Boolean)
         (mapping: (Person, Flower) => Person): Seq[Person] = ??? 

Is there an elegant way to implement the magic? 有没有一种实现魔术的优雅方法?

Use an iterator over flowers , consume one each time the predicate holds; flowers使用迭代器,每次predicate成立时消耗一个; the code would look like this, 代码看起来像这样,

val it = flowers.iterator
people.map ( p => if (predicate(p)) p.withFlowers(it.next) else p )

What about zip (aka zipWith) ? zip(aka zipWith)呢?

scala> val people = List("m","m","m","f","f","m","f")
people: List[String] = List(m, m, m, f, f, m, f)

scala> val flowers = List("f1","f2","f3")
flowers: List[String] = List(f1, f2, f3)

scala> def comb(xs:List[String],ys:List[String]):List[String] = (xs,ys) match {
     |  case (x :: xs, y :: ys) if x=="f" => (x+y) :: comb(xs,ys)
     |  case (x :: xs,ys) => x :: comb(xs,ys)
     |  case (Nil,Nil) => Nil
     | }

scala> comb(people, flowers)
res1: List[String] = List(m, m, m, ff1, ff2, m, ff3)

If the order is not important, you can get this elegant code: 如果顺序不重要,则可以获取以下简洁代码:

scala> val (men,women) = people.partition(_=="m")
men: List[String] = List(m, m, m, m)
women: List[String] = List(f, f, f)

scala> men ++ (women,flowers).zipped.map(_+_)
res2: List[String] = List(m, m, m, m, ff1, ff2, ff3)

I am going to presume you want to retain all the starting people (not simply filter out the females and lose the males), and in the original order, too. 我假设您想保留所有开始的人(而不是简单地过滤掉雌性并失去雄性),并且保留原始顺序。

Hmm, bit ugly, but what I came up with was: 嗯,有点丑陋,但是我想到的是:

def giveFemalesFlowers(people: Seq[Person], flowers: Seq[Flower]): Seq[Person] = {
    require(people.count(_.isFemale) == flowers.length)
    people.foldLeft((List[Person]() -> flowers)){ (acc, p) => p match {
        case pp: Person if pp.isFemale => ( (pp.withFlower(acc._2.head) :: acc._1) -> acc._2.tail)
        case pp: Person => ( (pp :: acc._1) -> acc._2)
    } }._1.reverse
}

Basically, a fold-left, initialising the 'accumulator' with a pair made up of an empty list of people and the full list of flowers, then cycling through the people passed in. 基本上是向左折,用一对由空的人列表和完整的花朵组成的对初始化“累加器”,然后在经过的人中循环。

If the current person is female, pass it the head of the current list of flowers (field 2 of the 'accumulator'), then set the updated accumulator to be the updated person prepended to the (growing) list of processed people, and the tail of the (shrinking) list of flowers. 如果当前人员是女性,则将当前鲜花列表的标题(“累加器”的字段2)传递给它,然后将已更新的累加器设置为已处理人员(正在增长)列表之前的已更新人员, (缩小)花朵列表的尾部。

If male, just prepend to the list of processed people, leaving the flowers unchanged. 如果是雄性,则只需添加到已处理人员列表中即可,而使花朵保持不变。

By the end of the fold, field 2 of the 'accumulator' (the flowers) should be an empty list, while field one holds all the people (with any females having each received their own flower), in reverse order, so finish with ._1.reverse 在首屏结束时,“累加器”(花朵)的第2栏应该是一个空列表,而第1栏则以相反的顺序容纳所有人(所有雌性都有各自的花朵),因此以._1.reverse

Edit : attempt to clarify the code (and substitute a test more akin to @elm's to replace the match , too) - hope that makes it clearer what is going on, @Felix! 编辑 :尝试澄清代码(并替换一个类似于@elm的测试来替换match )-希望可以更清楚地了解发生了什么,@ Felix! (and no, no offence taken): (没有,没有违法行为):

def giveFemalesFlowers(people: Seq[Person], flowers: Seq[Flower]): Seq[Person] = {

    require(people.count(_.isFemale) == flowers.length)

    val start: (List[Person], Seq[Flower]) = (List[Person](), flowers)

    val result: (List[Person], Seq[Flower]) = people.foldLeft(start){ (acc, p) =>
        val (pList, fList) = acc
        if (p.isFemale) {
          (p.withFlower(fList.head) :: pList, fList.tail)
        } else {
          (p :: pList, fList)
        }
    }

    result._1.reverse
}

I'm obviously missing something but isn't it just 我显然缺少了一些东西,但不仅仅是

people map {
  case p if p.isFemale => p.withFlower(f)
  case p => p
}

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

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