【问题标题】:Find person and immediate neighbours in Seq[Person]在 Seq[Person] 中查找人和直接邻居
【发布时间】:2019-04-25 06:14:36
【问题描述】:

给定一个 Seq[Person],其中包含 1-nPersons(以及最少 1 个人蜂鸣“汤姆”),找到带有名称的 Person 的最简单方法是什么“汤姆”以及汤姆之前的人和汤姆之后的人?

更详细的解释:

case class Person(name:String)

人员列表可以任意长,但至少有一个条目,必须是“Tom”。所以这些列表可能是一个有效的案例:

val caseOne =   Seq(Person("Tom"), Person("Mike"), Person("Dude"),Person("Frank"))
val caseTwo =   Seq(Person("Mike"), Person("Tom"), Person("Dude"),Person("Frank"))
val caseThree = Seq(Person("Tom"))
val caseFour =  Seq(Person("Mike"), Person("Tom"))

你明白了。由于我已经有了“汤姆”,因此任务是获取他的左邻居(如果存在)和右邻居(如果存在)。

在 scala 中实现这一目标的最有效方法是什么?


我目前的做法:

var result:Tuple2[Option[Person], Option[Person]] = (None,None)

for (i <- persons.indices)
{
  persons(i).name match
  {
    case "Tom" if i > 0 && i < persons.size-1 => result = (Some(persons(i-1)), Some(persons(i+1))) // (...), left, `Tom`, right, (...)
    case "Tom" if i > 0                       => result = (Some(persons(i-1)), None)               // (...), left, `Tom`
    case "Tom" if i < persons.size-1          => result = (Some(persons(i-1)), None)               // `Tom`, right, (...)
    case "Tom"                                => result = (None, None)                             // `Tom`
  }
}

只是不觉得我在做 scala 方式


Mukesh prajapati 的解决方案:

val arrayPersons = persons.toArray
val index = arrayPersons.indexOf(Person("Tom"))

if (index >= 0)
   result = (arrayPersons.lift(index-1), arrayPersons.lift(index+1))

很短,似乎涵盖了所有情况。


anuj saxena 的解决方案

result = persons.sliding(3).foldLeft((Option.empty[Person], Option.empty[Person]))
{
    case ((Some(prev), Some(next)), _)            => (Some(prev), Some(next))
    case (_, prev :: Person(`name`) :: next :: _) => (Some(prev), Some(next))
    case (_, _ :: prev :: Person(`name`) :: _)    => (Some(prev), None)
    case (_, Person(`name`) :: next :: _)         => (None, Some(next))
    case (neighbours, _) => neighbours
}

【问题讨论】:

  • Person 是案例类吗?
  • 是的。更新了问题以澄清。
  • Mukesh 的解决方案很好,但如果“Tom”实际上不在集合中,它会给出错误/误导性的结果。
  • 在这种情况下,应该检查index == -1以避免问题,所以它不是问题。

标签: scala collections filtering sliding-window


【解决方案1】:

可以使用滑动功能:

persons: Seq[Person] = initializePersons()
persons.sliding(size = 3).find { itr =>
  if (itr(1).name = "Tom") {
    val before = itr(0)
    val middle = itr(1)
    val after = itr(2)
  }
}

【讨论】:

  • 不幸的是,当Seq("Tom", "Mike", "Joe") 时,此操作失败(预期结果为:left-neighbour: None, right-neighbour: Mike。与列表中少于 3 人的失败方式相同。所以我认为不是可以做一个滑动窗口变体。
  • @MahmoudHanafy,您可以尝试使用Try(...).toOption 包装索引访问。或者使用模式匹配来检查所有情况。
【解决方案2】:

如果您知道 Seq 中只有一个“Tom”实例,请使用 indexOf 而不是手动循环:

tomIndex = persons.indexOf("Tom")
doSomethingWith(persons(tomIndex-1), persons(tomIndex+1))

【讨论】:

  • 您需要处理极端情况。它会给出 ArrayOutOfBoundException。
【解决方案3】:

首先找出“Tom”所在的索引,然后使用“lift”。 "lift" 将部分函数转换为返回 Option 结果的普通函数:

index = persons.indexOf("Tom")
doSomethingWith(persons.lift(index-1), persons.lift(index+1))

【讨论】:

  • 这似乎是迄今为止最短和最好的答案。
  • 您正在像这样遍历 Seq 3 次。 (它不是 IndexedSeq)
  • 是的...我们可以从 list(persons.toArray) 创建数组,然后在特定索引处找出元素需要固定的时间。
【解决方案4】:

经验法则:我们永远不应该使用索引访问列表/序列的内容,因为它容易出错(例如IndexNotFoundException)。

如果我们想使用索引,我们最好使用Array,因为它为我们提供了随机访问。

所以对于当前的解决方案,这是我在SeqList 中查找某个数据的上一个和下一个元素的代码:

  def findNeighbours(name: String, persons: Seq[Person]): Option[(Person, Person)] = {
    persons.sliding(3).flatMap{
      case prev :: person :: next :: Nil if person.name == name => Some(prev, next)
      case _ => None
    }.toList.headOption
  }

这里的返回类型在Option,因为我们可能在这里找不到它(如果列表中只有一个人或所需的人不在列表中)。

此代码将在参数中提供的person 第一次出现时选择该对。

如果您有可能提供的人员可能出现多次,请删除函数 findNeighbours 的最后一行中的 headOption。然后它将返回一个元组列表。

更新

如果Person 是一个案例类,那么我们可以像这样使用深度匹配:

  def findNeighbours(name: String, persons: Seq[Person]): Option[(Person, Person)] = {
    persons.sliding(3).flatMap{
      case prev :: Person(`name`) :: next :: Nil => Some(prev, next)
      case _ => None
    }.toList.headOption
  }

对于您的解决方案,需要添加更多案例(在单个答案的情况下将其更改为使用 foldleft):

  def findNeighboursV2(name: String, persons: Seq[Person]): (Option[Person], Option[Person]) = {
persons.sliding(3).foldLeft((Option.empty[Person], Option.empty[Person])){
    case ((Some(prev), Some(next)), _) => (Some(prev), Some(next))
    case (_, prev :: Person(`name`) :: next :: _) => (Some(prev), Some(next))
    case (_, _ :: prev :: Person(`name`) :: _) => (Some(prev), None)
    case (_, Person(`name`) :: next :: _) => (None, Some(next))
    case (neighbours, _) => neighbours
}

}

【讨论】:

  • 这种方法不适用于val persons = List(Person("Tom"), Person("Mike"), Person("Dude"),Person("Frank"),Person("Joe"))。在这种情况下,它应该为左邻居返回None,为右邻居返回Some("Mike")。我尝试添加这些案例,但似乎我必须放置很多案例才能涵盖所有内容。
  • 编辑了我的问题以进一步澄清。
  • findNeighboursV2 必须为您的第一条评论解决解决方案
【解决方案5】:
// Start writing your ScalaFiddle code here
case class Person(name: String)

val persons1 = Seq(Person("Martin"),Person("John"),Person("Tom"),Person("Jack"),Person("Mary"))
val persons2 = Seq(Person("Martin"),Person("John"),Person("Tom"))
val persons3 = Seq(Person("Tom"),Person("Jack"),Person("Mary"))
val persons4 = Seq(Person("Tom"))

def f(persons:Seq[Person]) = 
  persons
    .sliding(3)
    .filter(_.contains(Person("Tom")))
    .maxBy {
      case _ :: Person("Tom") :: _ => 1
      case _                       => 0
    }
    .toList
    .take(persons.indexOf(Person("Tom")) + 2) // In the case where "Tom" is first, drop the last person
    .drop(persons.indexOf(Person("Tom")) - 1) // In the case where "Tom" is last, drop the first person

println(f(persons1)) // List(Person(John), Person(Tom), Person(Jack))
println(f(persons2)) // List(Person(John), Person(Tom))
println(f(persons3)) // List(Person(Tom), Person(Jack))
println(f(persons4)) // List(Person(Tom))

Scalafiddle

【讨论】:

    猜你喜欢
    • 2020-01-29
    • 1970-01-01
    • 2020-05-19
    • 1970-01-01
    • 2019-01-07
    • 1970-01-01
    • 1970-01-01
    • 2012-11-10
    • 2022-08-23
    相关资源
    最近更新 更多