قم بتحويل قائمة الخيارات إلى خيار القائمة باستخدام Scalaz
سؤال
اريد تحويل أ List[Option[T]]
الى Option[List[T]]
. نوع توقيع الوظيفة هو
def lo2ol[T](lo: List[Option[T]]): Option[List[T]]
السلوك المتوقع هو تعيين قائمة تحتوي فقط Some
S في Some
تحتوي على قائمة بالعناصر داخل العناصر Some
'س. من ناحية أخرى ، إذا كانت قائمة الإدخال واحدة على الأقل None
, ، السلوك المتوقع هو العودة فقط None
. علي سبيل المثال:
scala> lo2ol(Some(1) :: Some(2) :: Nil)
res10: Option[List[Int]] = Some(List(1, 2))
scala> lo2ol(Some(1) :: None :: Some(2) :: Nil)
res11: Option[List[Int]] = None
scala> lo2ol(Nil : List[Option[Int]])
res12: Option[List[Int]] = Some(List())
مثال على التنفيذ ، بدون Scalaz ، سيكون:
def lo2ol[T](lo: List[Option[T]]): Option[List[T]] = {
lo.foldRight[Option[List[T]]](Some(Nil)){(o, ol) => (o, ol) match {
case (Some(x), Some(xs)) => Some(x :: xs);
case _ => None : Option[List[T]];
}}}
أتذكر أنني رأيت في مكان ما مثالًا مشابهًا ، ولكن باستخدام Scalaz لتبسيط الكود. كيف ستبدو؟
نسخة أكثر إيجازا قليلا ، باستخدام scala2.8 PartialFunction.condOpt
, ، ولكن لا يزال بدون سكالاز:
import PartialFunction._
def lo2ol[T](lo: List[Option[T]]): Option[List[T]] = {
lo.foldRight[Option[List[T]]](Some(Nil)){(o, ol) => condOpt(o, ol) {
case (Some(x), Some(xs)) => x :: xs
}
}}
المحلول
هناك وظيفة تتحول List[Option[A]]
إلى Option[List[A]]
في سكالاز. إنه sequence
. لتأخذ، لتمتلك None
في حالة وجود أي من العناصر None
و Some[List[A]]
في حالة وجود جميع العناصر Some
, ، يمكنك فقط القيام بذلك:
import scalaz.syntax.traverse._
import scalaz.std.list._
import scalaz.std.option._
lo.sequence
هذه الطريقة تتحول بالفعل F[G[A]
داخل G[F[A]]
بالنظر إلى وجود تطبيق Traverse[F]
, ، وبناءا على Applicative[G]
(Option
و List
تحدث لإرضاء كليهما ويتم توفيرها من قبل تلك الواردات).
دلالات Applicative[Option]
هكذا إذا كان أي من عناصر أ List
من Option
ق None
, ، ثم sequence
سوف يكون None
أيضًا. إذا كنت تريد الحصول على قائمة بكل ما Some
القيم بغض النظر عما إذا كانت أي قيم أخرى None
, ، يمكنك ان تفعلها:
lo flatMap (_.toList)
يمكنك تعميم ذلك لأي Monad
التي تشكل أيضا أ Monoid
(List
يحدث ليكون واحد من هؤلاء):
import scalaz.syntax.monad._
def somes[F[_],A](x: F[Option[A]])
(implicit m: Monad[F], z: Monoid[F[A]]) =
x flatMap (o => o.fold(_.pure[F])(z.zero))
نصائح أخرى
لسبب ما لا تعجبك
if (lo.exists(_ isEmpty)) None else Some(lo.map(_.get))
؟ ربما يكون هذا هو الأقصر في سكالا بدون سكالاز.
بينما ال Applicative[Option]
في Scalaz لديه السلوك الخاطئ للاستخدام مباشرة MA#sequence
, ، يمكنك أيضا اشتقاق Applicative
من Monoid
. هذا مريح مع MA#foldMapDefault
أو MA#collapse
.
في هذه الحالة ، نستخدم أ Monoid[Option[List[Int]]
. نقوم أولاً بإجراء خريطة داخلية (MA#∘∘
) لالتفاف الفرد Int
S في List
S من عنصر واحد.
(List(some(1), none[Int], some(2)) ∘∘ {(i: Int) => List(i)}).collapse assert_≟ some(List(1, 2))
(List(none[Int]) ∘∘ {(i: Int) => List(i)}).collapse assert_≟ none[List[Int]]
(List[Option[Int]]() ∘∘ {(i: Int) => List(i)}).collapse assert_≟ none[List[Int]]
تجريد من List
إلى أي حاوية مع مثيلات ل Traverse
, Pointed
و Monoid
:
def co2oc[C[_], A](cs: C[Option[A]])
(implicit ct: Traverse[C], cp: Pointed[C], cam: Monoid[C[A]]): Option[C[A]] =
(cs ∘∘ {(_: A).pure[C]}).collapse
co2oc(List(some(1), none[Int], some(2))) assert_≟ some(List(1, 2))
co2oc(Stream(some(1), none[Int], some(2))) assert_≟ some(Stream(1, 2))
co2oc(List(none[Int])) assert_≟ none[List[Int]]
co2oc(List[Option[Int]]()) assert_≟ none[List[Int]]
للأسف ، محاولة تجميع هذا الرمز حاليًا إما المشغلات #2741 أو يرسل المترجم إلى حلقة لا حصر لها.
تحديثلتجنب اجتياز القائمة مرتين ، يجب أن أستخدم foldMapDefault
:
(List(some(1), none[Int], some(2)) foldMapDefault (_ ∘ ((_: Int).pure[List])))
استندت هذه الإجابة على الطلب الأصلي بأن قائمة فارغة ، أو قائمة تحتوي فقط على None
S ، يجب أن تعيد أ None
. بالمناسبة ، من الأفضل أن يتم تصميم هذا من خلال النوع Option[scalaz.NonEmptyList]
-- NonEmptyList
يضمن عنصر واحد على الأقل.
إذا كنت تريد فقط أ List[Int]
, ، هناك العديد من الطرق الأسهل ، الواردة في إجابات أخرى. طريقتان مباشرتان لم يتم ذكرهما:
list collect { case Some(x) => x }
list flatten
هذا عمل بالنسبة لي. آمل أن يكون هذا حلًا صحيحًا.
لا يعيد أي شيء إذا كان أحد الخيارات في القائمة لا شيء ، وإلا فإنه يعيد خيار القائمة [A
def sequence[A](a: List[Option[A]]): Option[List[A]] = {
a.foldLeft(Option(List[A]())) {
(prev, cur) => {
for {
p <- prev if prev != None
x <- cur
} yield x :: p
}
}
}
بدء Scala 2.13
, وإضافة Option::unless
باني ل مكتبة قياسية, متغير ل إجابة ريكس كير سيكون:
Option.unless(list contains None)(list.flatten)
// val list = List(Some(1), Some(2)) => Some(List(1, 2))
// val list = List(Some(1), None, Some(2)) => None
أو ، إذا كان الأداء على المحك (من أجل تجنب flatten
تحويل ضمني من Option
ل List
):
Option.unless(list contains None)(list.map(_.get))