قم بتحويل قائمة الخيارات إلى خيار القائمة باستخدام Scalaz

StackOverflow https://stackoverflow.com/questions/2569014

  •  24-09-2019
  •  | 
  •  

سؤال

اريد تحويل أ List[Option[T]] الى Option[List[T]]. نوع توقيع الوظيفة هو

def lo2ol[T](lo: List[Option[T]]): Option[List[T]]

السلوك المتوقع هو تعيين قائمة تحتوي فقط SomeS في 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#∘∘) لالتفاف الفرد IntS في ListS من عنصر واحد.

(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])))

استندت هذه الإجابة على الطلب الأصلي بأن قائمة فارغة ، أو قائمة تحتوي فقط على NoneS ، يجب أن تعيد أ 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))
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top