【问题标题】:Grouping list items by comparing them with their neighbors通过将列表项与相邻项进行比较来对列表项进行分组
【发布时间】:2023-04-08 08:20:02
【问题描述】:

根据相邻值将值列表分组的最优雅的方法是什么?

我所拥有的更广泛的背景是有一个需要分组为段落的行列表。我想说的是,如果两行之间的垂直差异低于阈值,它们就在同一个段落中。

我最终以不同的方式解决了这个问题,但我想知道这里的正确解决方案。

case class Box(y: Int)
val list = List(Box(y=1), Box(y=2), Box(y=5))

def group(list: List[Box], threshold: Int): List[List[Box]] = ???

val grouped = group(list, 2)
> List(List(Box(y=1), Box(y=2)), List(Box(y=5)))

我查看了groupBy(),但它一次只能使用一个元素。我还尝试了一种方法,该方法涉及使用 sliding() 预先计算差异,但是从原始集合中检索元素变得很尴尬。

【问题讨论】:

  • 您能否举一个f(xs) == ys 的示例,其中f 是所需的函数签名,xs 是示例输入,ys 是所需的输出?
  • group函数的代码块中,它接受一个框列表,一个阈值并生成一个框列表,其中ys小于阈值的框被分组到一个列表中
  • 你能把它翻译成代码吗?我更容易通过文字理解代码(函数签名、输入和所需输出)
  • 也许partition 能满足你的需要? val (underThreshold, overThreshold) = boxes.partition(_.y < threshold)。如果不是,您的示例可能过于具体,无法理解您想要实现的目标。
  • 这个问题经常出现,答案是自己写slide和group by some predicate。我最近找不到特定的 ML 线程作为示例答案。我想知道它是否被放在某个库中。

标签: scala


【解决方案1】:

这是一个单行。泛化类型留给读者作为练习。

使用整数和绝对差而不是行和间距来避免混乱。

val zs = List(1,2,4,8,9,10,15,16)  
def closeEnough(a:Int, b:Int) = (Math.abs(b -a) <= 2)

zs.drop(1).foldLeft(List(List(zs.head)))
                      ((acc, e)=> if (closeEnough(e, acc.head.head)) 
                                      (e::acc.head)::acc.tail
                                  else
                                       List(e)::acc)
       .map(_.reverse)
       .reverse

// List(List(1, 2, 4), List(8, 9, 10), List(15, 16))

或两个班轮以获得轻微的效率提升

val ys = zs.reverse
ys.drop(1).foldLeft(List(List(ys.head)))
                ((acc, e)=> if (closeEnough(e, acc.head.head)) 
                   (e::acc.head)::acc.tail
                else
                   List(e)::acc)
// List(List(1, 2, 4), List(8, 9, 10), List(15, 16))

【讨论】:

  • headtail 不安全(它们在空列表中抛出异常)并且不鼓励在函数上下文中使用它们。请改用headOptiondrop(1)
  • 是的,我很清楚这一点。但是,在foldLeft 中使用head 是完全安全的,并且在设置foldLeft 时“修复”这些问题最好先对zs.isEmpty 进行一次测试。
  • 虽然zs.drop(1) 确实让这里的事情变得安全。我会用那个。更新后,它现在也适用于空列表(并且 head 的所有用法都从未用于空列表)
  • 我同意 head 在这里是安全的,但使用 :: 提取器解构列表仍然会更优雅(和可读),而不是使用 headtail 明确。另外,reverse + foldLeft === foldRight
  • 实际上,我认为它不会更优雅(在这种精确的情况下,我通常同意你的观点)。试试看,你就会明白我的意思——尽管如果你能让它更优雅,我会很高兴被证明是错误的。尽管reverse + foldLeft 确实等同于foldRight,但我需要得到相反的head(原始last),这将是对列表的额外遍历。
猜你喜欢
  • 2015-08-04
  • 2018-07-28
  • 2020-05-26
  • 2015-09-09
  • 1970-01-01
  • 2016-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多