سؤال

لنفترض أن لدي

val foo : Seq[Double] = ...
val bar : Seq[Double] = ...

وأود أن أصلع SEQ حيث BAZ (I) = FOO (I) BAR (I). طريقة واحدة أستطيع أن أفكر في القيام بذلك

val baz : Seq[Double] = (foo.toList zip bar.toList) map ((f: Double, b : Double) => f+b)

ومع ذلك، فإن هذا يشعر بالقايرة وغير الفعالة - لا بد لي من تحويل كلا SEQs إلى القوائم (التي تنفجر مع قوائم كسول)، وإنشاء هذه القائمة المؤقتة من TUPLES، فقط على الخريطة واتركها. ربما تدفقات حل المشكلة الكسولة، ولكن في أي حال، هذا يبدو وكأنه قبيح غير ضروري. في LISP، فإن وظيفة الخريطة سوف خريطة على تسلسل متعددة. أود أن أكتب

(mapcar (lambda (f b) (+ f b)) foo bar)

ولم يتم إنشاء قوائم مؤقتة في أي مكان. هل هناك وظيفة قائمة على عدة قوائم متعددة في Scala، أو هل يتم دمج الرمز البريدي بمدى التدمير في الحقيقة "اليمين" للقيام بذلك؟

هل كانت مفيدة؟

المحلول

وتسمى الوظيفة التي تريدها zipWith, ، لكنه ليس جزءا من المكتبة القياسية. سيكون في 2.8 (تحديث: لا يبدو، انظر التعليقات).

foo zipWith((f: Double, b : Double) => f+b) bar

يرى تذكرة TRAC هذه.

نصائح أخرى

في SCALA 2.8:

val baz = (foo, bar).zipped map (_ + _)

ويعمل لأكثر من اثنين من المعاملين بنفس الطريقة. أي يمكنك بعد ذلك اتباع هذا مع:

(foo, bar, baz).zipped map (_ * _ * _)

حسنا، هذا، عدم وجود الرمز البريدي، هو نقص في SCALA 2.7 SEQ. يحتوي SCALA 2.8 على تصميم جمع الأفكار جيدا، لاستبدال الطريقة المخصصة، جاءت المجموعات الموجودة في 2.7 (لاحظ أنهم لم يتم إنشاؤهم جميعا في وقت واحد، مع تصميم موحد).

الآن، عندما تريد تجنب إنشاء مجموعة مؤقتة، يجب عليك استخدام "الإسقاط" على Scala 2.7، أو "عرض" على Scala 2.8. سيعطيك هذا نوع جمع تعليمات معينة، خاصة الخريطة واللطيفة والتصفية، غير صارمة. في SCALA 2.7، فإن الإسقاط القائمة هو مجرى. في SCALA 2.8، هناك تسلسل سلسلة من التسلسل، ولكن هناك zipwith الحق في التسلسل، فلن تحتاج حتى ذلك.

بعد القول أنه على النحو المذكور، تم تحسين JVM لمعالجة مخصصات الكائنات المؤقتة، وعند التشغيل في وضع الخادم، يمكن أن يؤدي تحسين وقت التشغيل إلى عجائب. لذلك، لا تتحسن قبل الأوان. قم باختبار التعليمات البرمجية في الشروط التي سيتم تشغيلها - وإذا لم تكن مخططا لتشغيله في وضع الخادم، فقم بإعادة التفكير في أنه إذا كان من المتوقع أن يكون التعليمات البرمجية قيد التشغيل طويلا وحتى / عند الضروري.

تعديل

ما سيكون متاحا في الواقع في SCALA 2.8 هذا:

(foo,bar).zipped.map(_+_)

قائمة كسول ليست نسخة من القائمة - إنها أشبه كائن واحد. في حالة تطبيق ZIP كسول، في كل مرة يطلب فيها العنصر التالي، فإنه يمسك عنصرا من كل من قوائم المدخلتين وإنشاء Tuple منه، ثم كسر Tuple بعيدا عن مطابقة النمط Lambda الخاص بك.

لذلك لا توجد حاجة أبدا لإنشاء نسخة كاملة من قائمة (قائمات) الإدخال بأكملها قبل البدء في التشغيل عليها. إنه يتلخص بنمط تخصيص مشابه للغاية لأي تطبيق يعمل على JVM - الكثير من المخصصات الصغيرة للغاية ولكنها صغيرة الحجم، والتي تم تحسين jvm للتعامل معها.

تحديث: أن تكون واضحة، تحتاج إلى استخدام تدفقات (قوائم كسول) لا قوائم. تيارات Scala لديها محرك مضغوط يعمل بطريقة كسول، وبالتالي لا ينبغي أن تتحول الأمور إلى قوائم.

من الناحية المثالية، يجب أن تكون خوارزمية قادرة على العمل على اثنين لانهائي تيارات دون تفجير (على افتراض أنها لا تفعل أي folding, بالطبع، ولكن فقط يقرأ ويقوم بإنشاء تدفقات).

تحديث: تمت الإشارة إليه (في التعليقات) أن هذه "الإجابة" لا تتناول في الواقع السؤال الذي طرحه. هذه الإجابة سوف خريطة أكثر من كل مزيج من foo و bar, ، إنتاج ن x م عناصر، بدلا من دقيقة (م، ن) كما طلب. اذن هذا هو خاطئ - ظلم - يظلم, ، ولكن غادر للأجيال القادمة لأنها معلومات جيدة.


أفضل طريقة للقيام بذلك هي مع flatMap مدموج مع map. وبعد رمز يتحدث بصوت أعلى من الكلمات:

foo flatMap { f => bar map { b => f + b } }

هذا سوف ينتج واحد Seq[Double], ، بالضبط كما تتوقع. هذا النمط شائع جدا أن Scala يشمل فعلا بعض السحر النحوي الذي ينفذه:

for {
  f <- foo
  b <- bar
} yield f + b

أو بدلا من ذلك:

for (f <- foo; b <- bar) yield f + b

ال for { ... } بناء الجملة هو حقا الطريقة الأكثر اصطلاحا للقيام بذلك. يمكنك الاستمرار في إضافة بنود المولدات (على سبيل المثال b <- bar) عند الضرورة. وبالتالي، إذا أصبح فجأة ثلاثة Seqبحيث يجب عليك تعيين الخريطة، يمكنك بسهولة توسيع نطاق بناء الجملة الخاص بك مع الاحتياجات الخاصة بك (لتعيين عبارة).

عندما تواجه مهمة مماثلة، أضفت القواد التالي إلى Iterableس:

implicit class IterableOfIterablePimps[T](collOfColls: Iterable[Iterable[T]]) {
  def mapZipped[V](f: Iterable[T] => V): Iterable[V] = new Iterable[V] {
    override def iterator: Iterator[V] = new Iterator[V] {
      override def next(): V = {
        val v = f(itemsLeft.map(_.head))
        itemsLeft = itemsLeft.map(_.tail)
        v
      }

      override def hasNext: Boolean = itemsLeft.exists(_.nonEmpty)

      private var itemsLeft = collOfColls
    }
  }
}

وجود هذا، يمكن للمرء أن يفعل شيئا مثل:

val collOfColls = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
collOfColls.mapZipped { group =>
  group // List(1, 4, 7), then List(2, 5, 8), then List(3, 6, 9)
}

لاحظ أنه يجب عليك النظر بعناية نوع التجميع مرت كما متداخل Iterable, ، حيث tail و head سيتم استدعاء ذلك بشكل متكرر. لذلك، من الناحية المثالية يجب أن تمر Iterable[List] أو آخر جمع مع السريعة tail و head.

أيضا، يتوقع هذا الرمز مجموعات متداخلة من نفس الحجم. كان ذلك حالة استخدامي، لكنني أظن أنه يمكن تحسين ذلك، إذا لزم الأمر.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top