使用自定义逻辑过滤对象的 scala 集合

Posted

技术标签:

【中文标题】使用自定义逻辑过滤对象的 scala 集合【英文标题】:Filter scala collection of objects with custom logic 【发布时间】:2021-11-26 23:33:59 【问题描述】:

我需要过滤一组对象(已经按属性 A 排序)删除属性 B 乱序的项目(下面用粗体标记): (为简单起见,一组元组)

案例一:

输入Seq((10, 10), (20, 20), (30, 36), (40, 30), (50, 40), (60, 50)) 预期输出:Seq((10, 10), (20, 20), (30, 36), (50, 40), (60, 50)) 实际输出:Seq((10,10), (20,20), (40,30), (50,40))

案例2:

输入:Seq((10, 10), (20, 20), (30, 36), (40, 40), (50, 30), (60, 50)) 预期输出:Seq((10, 10), (20, 20), (30, 36), (40, 40), (60, 50)) 实际输出:Seq((10,10), (20,20), (30,36), (50,30))

我将不胜感激。

这是我目前所拥有的:

@RunWith(classOf[SpringRunner])
class OutOfOrderTest extends AnyWordSpec with Matchers with Debuggable 

  "OutOfOrderTest" should 

    def ooo(a: Seq[(Int, Int)]): Option[(Int, Int)] = 
      val x = a.head
      val y = a.tail.head
      if (x._2 <= y._2) Some(x) else None
    

    "filter out of order simple" in 
      val bad = Seq((10, 10), (20, 20), (30, 36), (40, 30), (50, 40), (60, 50))

      val filtered = bad.sliding(2).collect  a =>
        ooo(a)
      .filter(_.isDefined).map(_.get).toSeq
      filtered shouldEqual Seq((10, 10), (20, 20), (30, 36), (50, 40), (60, 50))
    

    "filter out of order complex" in 
      val bad2 = Seq((10, 10), (20, 20), (30, 36), (40, 40), (50, 30), (60, 50))

      val filtered2 = bad2.sliding(2).collect  a =>
        ooo(a)
      .filter(_.isDefined).map(_.get).toSeq
      filtered2 shouldEqual Seq((10, 10), (20, 20), (30, 36), (40, 40), (60, 50))
    
  

更新 1

我使用序列来提高性能(可能最好使用数组,但我可以稍后处理)

为简单起见,我在这里使用了元组,但它们实际上是案例类 (Target) 的实例,其中 2 个字段由元组数据表示。

这也是一个更大的 Spring-boot 应用程序中的一个过滤器,我必须用它制作一个组件。

虽然“组件化”(是的,我们在办公室里提出了这个词)过滤器没有问题,但来自@Luis 的递归函数将难以抽象如下:

我想将这三种方法作为组件进行测试 因此,如果可以的话,我会将 3 个答案标记为有效。

case class Target(rt: Double, ri: Double)

trait OutOfOrderRemover 
  def filter(targets: Seq[Target])


@Component
class RecurtsiveOOO extends OutOfOrderRemover 
  override def filter(targets: Seq[Target]): Seq[Target] =  

【问题讨论】:

感谢所有 jwvh、Guru 和 Luis 的快速回答,每个人都有我喜欢的东西,jwvh 非常简洁,Guru 使用折叠,Luis 使用递归。 【参考方案1】:

这是我的 2 美分。

这将为已发布的两个 input 示例返回所需的结果。

input.head +: input.sliding(2).collect
      case Seq((_,b1),(a2,b2)) if b1 <= b2 => (a2,b2)
    .toSeq

警告:如果input 为空,将抛出。

【讨论】:

【参考方案2】:

恕我直言,这是尾递归的完美用例。

def removeUnsortedElements[A, B](data: List[A])(orderBy: A => B)(implicit ev: Ordering[B]): List[A] = 
  import Ordering.Implicits._

  @annotation.tailrec
  def loop(remaining: List[A], previous: B, acc: List[A]): List[A] =
    remaining match 
      case a :: tail =>
        val next = orderBy(a)
        val newAcc =
          if (next >= previous) a :: acc
          else acc
        
        loop(
          remaining = tail,
          previous = next,
          acc = newAcc
        )
      
      case Nil =>
        acc.reverse
    
  
  data match 
    case first :: tail =>
      loop(
        remaining = tail,
        previous = orderBy(first),
        acc = first :: Nil
      )
    
    case Nil =>
      Nil
  

可以这样使用:

val input1 = List((10, 10), (20, 20), (30, 36), (40, 30), (50, 40), (60, 50))
val output1 = removeUnsortedElements(input1)(_._2)
// output1: List[Int] = List((10,10), (20,20), (30,36), (50,40), (60,50))

val input2 = List((10, 10), (20, 20), (30, 36), (40, 40), (50, 30), (60, 50))
val output2 = removeUnsortedElements(input2)(_._2)
// output2: List[Int] = List((10,10), (20,20), (30,36), (40,40), (60,50))

可以看到运行here的代码

【讨论】:

【参考方案3】:

您可以将foldLeft与空集合累加器一起使用,将当前值与累加器头部1进行比较,如果满足条件则追加当前值,然后反转结果:

val bad = ...
val filtered = bad.foldLeft(List.empty[(Int, Int)])((acc, t) => acc match 
    case head :: _ if head._2 > t._2 => acc
    case _ => t :: acc
  )
    .reverse

【讨论】:

以上是关于使用自定义逻辑过滤对象的 scala 集合的主要内容,如果未能解决你的问题,请参考以下文章

Scala 自定义集合返回 null 列表作为默认值

Elasticsearch自定义过滤插件实现复杂逻辑过滤

Magento JOIN 在两个过滤的自定义集合之间

AngularJs学习之一使用自定义的过滤器

在 AADAppRoleStatelessAuthenticationFilter 中添加自定义逻辑,以便我可以决定过滤哪些请求

使用自定义(对象)适配器过滤 ListView