How do I convert a List[Option[(A, List[B])]] to the Option[(A,List[B])]? (Basically retrieve Option[X] from List[Option[X]])

huangapple go评论46阅读模式
英文:

How do I convert a List[Option[(A, List[B])]] to the Option[(A,List[B])]? (Basically retrieve Option[X] from List[Option[X]])

问题

我有一个名为“rules”的元组列表,如下所示:

val rules = List[(A, List[B])],其中A和B是两个单独的case类

根据我的用例,我需要将其转换为一个`Option[(A, List[B])]`。A案例类包含一个类型为`Option[String]`的属性`id`,根据它返回元组。

我编写了一个名为`def findRule(entryId: Option[String])`的函数,我打算从中返回满足条件的元组`(A, List[B])`,即`A.id = entryId`作为一个Option。到目前为止,我编写了以下代码片段:

def findRule(entryId: Option[String]) = {
	for {
		ruleId <- rules.flatMap(_._1.id) // ruleId是一个String
		id <- entryId	// entryId是一个Option[String]
	} yield {
		rules.find(_ => ruleId.equalsIgnoreCase(id))	// 返回List[Option[(A, List[B])]]
	}
}

这段代码返回一个`List[Option[(A, List[B])]`,但我不知道如何从中提取出Option。使用`.head()`不是一个选择,因为`rules`列表可能为空。请帮忙,我是Scala的新手。

示例(真实示例太大无法在此处发布,请考虑此代表性示例):

val rules = [(A = {id=1, ….}, B = [{B1}, {B2}, {B3}, …]), (A={id=2, ….}, B = [{B10}, {B11}, {B12}, …]), …. ](这里B并不重要,我需要根据case类A的id元素来找到元组)

现在,假设`entryId = Some(1)`

经过`findRule()`函数后,它将如下所示:

[Some((A = {id=1, …}, B = [{B1}, {B2}, {B3}, …]))]

我想返回:

Some((A = {id=1, …}, B = [{B1}, {B2}, {B3}, …])),即从`findRule()`返回的列表中获取Option(当前)。
英文:

I have a List of “rules” tuples as follows:

val rules = List[(A, List[B])], where A and B are two separate case-classes

For the purposes of my use-case, I need to convert this to an Option[(A, List[B])]. The A case class contains a property id which is of type Option[String], based on which the tuple is returned.

I have written a function def findRule(entryId: Option[String]), from which I intend to return the tuple (A, List[B]) whose A.id = entryId as an Option. So far, I have written the following snippet of code:

def findRule(entryId: Option[String]) = {
	for {
		ruleId <- rules.flatMap(_._1.id) // ruleId is a String
		id <- entryId	// entryId is an Option[String]
	} yield {
		rules.find(_ => ruleId.equalsIgnoreCase(id))	// returns List[Option[(A, List[B])]
	}
}

This snippet returns a List[Option[(A, List[B])] but I can’t figure out how to retrieve just the Option from it. Using .head() isn’t an option, since the rules list may be empty. Please help as I am new to Scala.

Example (the real examples are too large to post here, so please consider this representative example):

val rules = [(A = {id=1, ….}, B = [{B1}, {B2}, {B3}, …]), (A={id=2, ….}, B = [{B10}, {B11}, {B12}, …]), …. ] (B is not really important here, I need to find the tuple based on the id element of case-class A)

Now, suppose entryId = Some(1)

After the findRule() function, this would currently look like:

[Some((A = {id=1, …}, B = [{B1}, {B2}, {B3}, …]))]

I want to return:


Some((A = {id=1, …}, B = [{B1}, {B2}, {B3}, …])) , ie, the Option within the List returned (currently) from findRule()

答案1

得分: 2

根据你的问题,看起来你想基于某种条件从列表中选择一个单一的项目,这意味着你可能希望从 rules.find 开始。然后问题变成了如何表达传递给 find 的谓词函数。

根据我对你的问题的理解,条件是:

元组的 A 部分的 id 需要与其他地方传递的 entryId 匹配

def findRule(entryId: Option[String]) = 
  rules.find { case (a, listOfB) => entryId.contains(a.id) }

case 表达式只是处理元组的一种方便的语法。我也可以这样做:

rules.find { tup => entryId.contains(tup._1.id) }

Option 上的 contains 方法大致是"如果我是 Some,检查我的值是否等于参数;如果我是 None,只返回 false 并忽略参数"。它用于比较你对 entryIdOption[String]Aid 的普通 String

你的第一个尝试没有成功,因为rules.flatMap 得到了一个 List,在 for-理解中使用 <- 后继续使用它意味着另一个 flatMap,这将保持它作为一个列表。

英文:

From your question, it sounds like you're trying to pick a single item from your list based on some conditional, which means you'll probably want to start with rules.find. The problem then becomes how to express the predicate function that you pass to find.

From my read of your question, the conditional is

> The id on the A part of the tuple needs to match the entryId that was passed in elsewhere

def findRule(entryId: Option[String]) = 
  rules.find { case (a, listOfB) =&gt; entryId.contains(a.id) }

The case expression is just nice syntax for dealing with the tuple. I could have also done

rules.find { tup =&gt; entryId.contains(tup._1.id) }

The contains method on Option roughly does "if I'm a Some, see if my value equals the argument; if I'm a None, just return false and ignore the argument". It works as a comparison between the Option[String] you have for entryId and the plain String you have for A's id.

Your first attempt didn't work because rules.flatMap got you a List, and using that after the &lt;- in the for-comprehension means another flatMap, which keeps things as a List.

答案2

得分: 1

我个人更喜欢的一个变种是

```scala
def findRule(entryId: Option[String]): Option[(A, List[B])] = {
  entryId.flatMap { id =>
    rules.find { case (a, _) => a.id == id }
  }
}

entryIdNone的情况下,它立即返回None。如果你从rules.find开始,即使entryIdNone,它也会遍历所有规则并检查每一个。如果规则列表很小,可能不会有明显的性能差异,但我也觉得这种方式更直观易懂。


<details>
<summary>英文:</summary>

A variant that I personally prefer is

```scala
def findRule(entryId: Option[String]): Option[(A, List[B])] = {
  entryId.flatMap { id =&gt;
    rules.find { case (a, _) =&gt; a.id == id }
  }
}

In the case where entryId is None, it just immediately returns None. If you start with rules.find, then it will iterate through all of the rules and check each one even when entryId is None. It's unlikely to make any observable performance difference if the list of rules is small, but I also find it more intuitive to understand.

huangapple
  • 本文由 发表于 2023年2月16日 18:43:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/75471110.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定