एक गुना में जल्दी से गर्भपात


88

एक गुना जल्दी समाप्त करने का सबसे अच्छा तरीका क्या है? एक सरल उदाहरण के रूप में, कल्पना करें कि मैं संख्याओं को एक में समेटना चाहता हूं Iterable, लेकिन अगर मुझे कोई ऐसी चीज़ मिलती है, जिसकी मुझे उम्मीद नहीं है (एक विषम संख्या कहें) तो मैं समाप्त करना चाहता हूँ। यह एक पहला सन्निकटन है

def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
  nums.foldLeft (Some(0): Option[Int]) {
    case (Some(s), n) if n % 2 == 0 => Some(s + n)
    case _ => None
  }
}

हालांकि, यह समाधान बहुत बदसूरत है (जैसा कि, अगर मैंने एक .foreach और एक रिटर्न किया - यह बहुत क्लीनर और स्पष्ट होगा) और सभी का सबसे बुरा, यह पूरी तरह से चलने योग्य का पता लगाता है, भले ही यह एक गैर-समान संख्या का सामना करता हो ।

तो इस तरह एक गुना लिखने का सबसे अच्छा तरीका क्या होगा, जो जल्दी समाप्त हो जाए? क्या मुझे अभी जाकर इसे पुनरावर्ती रूप से लिखना चाहिए, या क्या अधिक स्वीकार्य तरीका है?


क्या आप मध्यवर्ती उत्तर को समाप्त और रिकॉर्ड करना चाहते हैं?
ब्रायन एग्न्यू

इस मामले में, नहीं। लेकिन थोड़े और सामान्य मामले में मैं या तो एक वापसी करना चाहता हूं जिसमें एक त्रुटि या कुछ और है
हेप्टिक

यह प्रश्न है: stackoverflow.com/questions/1595427/…
ziggystar

छोरों को तोड़ने के बारे में यह उत्तर भी उपयोगी हो सकता है: stackoverflow.com/a/2742941/1307721
ejoubaud

जवाबों:


64

मेरी पहली पसंद आम तौर पर पुनरावृत्ति का उपयोग करना होगा। यह केवल मध्यम रूप से कम कॉम्पैक्ट है, संभवतः तेज है (निश्चित रूप से धीमी नहीं), और प्रारंभिक समाप्ति में तर्क को अधिक स्पष्ट कर सकता है। इस मामले में आपको नेस्टेड डिस्टेंस की जरूरत है जो थोड़ा अजीब है:

def sumEvenNumbers(nums: Iterable[Int]) = {
  def sumEven(it: Iterator[Int], n: Int): Option[Int] = {
    if (it.hasNext) {
      val x = it.next
      if ((x % 2) == 0) sumEven(it, n+x) else None
    }
    else Some(n)
  }
  sumEven(nums.iterator, 0)
}

मेरी दूसरी पसंद का उपयोग करना होगा return, क्योंकि यह सब कुछ और बरकरार रखता है और आपको केवल तह को लपेटने की आवश्यकता होती है defताकि आपके पास वापस लौटने के लिए कुछ हो - इस मामले में, आपके पास पहले से ही एक विधि है, इसलिए:

def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
  Some(nums.foldLeft(0){ (n,x) =>
    if ((n % 2) != 0) return None
    n+x
  })
}

जो इस मामले में पुनरावृत्ति की तुलना में बहुत अधिक कॉम्पैक्ट है (हालांकि हम पुनरावृत्ति के साथ विशेष रूप से अशुभ हो गए क्योंकि हमें एक पुनरावृत्ति / पुनरावृत्ति परिवर्तन करना पड़ा)। उछल नियंत्रण प्रवाह से बचने के लिए कुछ है जब बाकी सब समान है, लेकिन यहां यह नहीं है। उन मामलों में इसका उपयोग करने में कोई नुकसान नहीं है जहां यह मूल्यवान है।

अगर मैं अक्सर ऐसा कर रहा था और इसे कहीं एक विधि के बीच में चाहता था (तो मैं सिर्फ रिटर्न का उपयोग नहीं कर सकता था), मैं शायद गैर-स्थानीय नियंत्रण प्रवाह उत्पन्न करने के लिए अपवाद-हैंडलिंग का उपयोग करूंगा। यही है, आखिरकार, यह क्या अच्छा है, और त्रुटि से निपटने का एकमात्र समय उपयोगी नहीं है। एकमात्र ट्रिक स्टैक ट्रेस उत्पन्न करने से बचने के लिए है (जो वास्तव में धीमा है), और यह आसान है क्योंकि विशेषता NoStackTraceऔर उसके बच्चे के लक्षण ControlThrowableआपके लिए पहले से ही ऐसा करते हैं। स्काला पहले से ही आंतरिक रूप से इसका उपयोग करता है (वास्तव में, यही कारण है कि यह गुना के अंदर से वापसी को लागू करता है!)। चलो अपना बनाते हैं (नेस्टेड नहीं किया जा सकता है, हालांकि कोई इसे ठीक कर सकता है):

import scala.util.control.ControlThrowable
case class Returned[A](value: A) extends ControlThrowable {}
def shortcut[A](a: => A) = try { a } catch { case Returned(v) => v }

def sumEvenNumbers(nums: Iterable[Int]) = shortcut{
  Option(nums.foldLeft(0){ (n,x) =>
    if ((x % 2) != 0) throw Returned(None)
    n+x
  })
}

यहाँ बेशक उपयोग returnकरना बेहतर है, लेकिन ध्यान दें कि आप shortcutकहीं भी डाल सकते हैं , न कि पूरी विधि को लपेटकर।

मेरे लिए अगली पंक्ति में गुना को फिर से लागू करना होगा (या तो खुद को या ऐसा पुस्तकालय खोजने के लिए जो ऐसा करता है) ताकि यह जल्दी समाप्ति का संकेत दे सके। ऐसा करने के दो प्राकृतिक तरीके मूल्य को प्रचारित नहीं करना है, लेकिन एक Optionमूल्य है, जहां Noneसमाप्ति को दर्शाता है; या एक दूसरे संकेतक फ़ंक्शन का उपयोग करने के लिए जो सिग्नल को पूरा करता है। किम स्टेबेल द्वारा दिखाया गया स्लैज़ आलसी फोल्ड पहले से ही पहले मामले को कवर करता है, इसलिए मैं दूसरा (एक परस्पर कार्यान्वयन के साथ) दिखाऊंगा:

def foldOrFail[A,B](it: Iterable[A])(zero: B)(fail: A => Boolean)(f: (B,A) => B): Option[B] = {
  val ii = it.iterator
  var b = zero
  while (ii.hasNext) {
    val x = ii.next
    if (fail(x)) return None
    b = f(b,x)
  }
  Some(b)
}

def sumEvenNumbers(nums: Iterable[Int]) = foldOrFail(nums)(0)(_ % 2 != 0)(_ + _)

(चाहे आप पुनरावृत्ति, वापसी, आलस्य, आदि द्वारा समाप्ति को लागू करते हैं, आप पर निर्भर है।)

मुझे लगता है कि मुख्य उचित वेरिएंट को कवर करता है; कुछ अन्य विकल्प भी हैं, लेकिन मुझे यकीन नहीं है कि कोई इस मामले में उनका उपयोग क्यों करेगा। ( Iteratorखुद अच्छा काम करेगा अगर यह एक था findOrPrevious, लेकिन यह नहीं है, और अतिरिक्त काम यह करने के लिए हाथ से करता है कि यह यहाँ उपयोग करने के लिए एक मूर्खतापूर्ण विकल्प बनाता है।)


foldOrFailवास्तव में क्या मैं यह सवाल पूछे जाने के बारे में सोच के साथ आ गया था। कार्यान्वयन IMO में एक उत्परिवर्तनीय पुनरावृत्ति और थोड़ी देर के लूप का उपयोग न करने का कोई कारण नहीं है, जब सभी अच्छी तरह से समझाया जाता है। iteratorपुनरावृत्ति के साथ प्रयोग करने का कोई मतलब नहीं है।
__

@Rex Kerr, आपके उत्तर के लिए धन्यवाद, मैंने अपने उपयोग के लिए एक संस्करण दिया जो या तो उपयोग करता है ... (मैं इसे उत्तर के रूप में पोस्ट करने जा रहा हूं)
कोर

संभवत: रिटर्न- आधारित समाधान की विपक्ष में से एक , यह है कि यह महसूस करने में थोड़ा समय लगता है कि यह किस कार्य पर लागू होता है: sumEvenNumbersया गुनाop
इवान बालाशोव

1
@ इवानबालाशोव - ठीक है, यह जानने के लिए एक बार कुछ समय लगता है कि स्काला के नियम क्या हैं return(यानी, यह आपके द्वारा खोजे गए सबसे स्पष्ट तरीके से वापस लौटता है), लेकिन उसके बाद यह बहुत लंबा नहीं होना चाहिए। नियम बहुत स्पष्ट है, और यह defबताता है कि संलग्न विधि कहां है।
रेक्स केर

मैं अपने foldOrFail की तरह लेकिन व्यक्तिगत रूप से मैं वापसी प्रकार बना सकता था Bनहीं Option[B]क्योंकि तब यह गुना की तरह बर्ताव करता है, जहां वापसी प्रकार शून्य संचायक के प्रकार के रूप में एक ही है। बस बी के साथ सभी विकल्प रिटर्न को बदलें। और शून्य के रूप में कोई भी नहीं। सभी प्रश्न के बाद एक ऐसा गुना चाहते थे जो असफल होने के बजाय जल्दी समाप्त कर सके।
कार्ल

26

आपके द्वारा वर्णित परिदृश्य (कुछ अवांछित स्थिति पर बाहर निकलने) takeWhileविधि के लिए एक अच्छे उपयोग के मामले की तरह लगता है । यह अनिवार्य रूप से है filter, लेकिन एक तत्व से सामना करना चाहिए जो शर्त को पूरा नहीं करता है।

उदाहरण के लिए:

val list = List(2,4,6,8,6,4,2,5,3,2)
list.takeWhile(_ % 2 == 0) //result is List(2,4,6,8,6,4,2)

यह केवल Iterators / Iterables के लिए भी ठीक काम करेगा । समाधान मैं आपके "सम संख्याओं का योग, लेकिन विषम पर विराम" के लिए सुझाव देता हूं:

list.iterator.takeWhile(_ % 2 == 0).foldLeft(...)

और सिर्फ यह साबित करने के लिए कि एक बार यह एक अजीब संख्या हिट होने पर अपना समय बर्बाद नहीं कर रहा है ...

scala> val list = List(2,4,5,6,8)
list: List[Int] = List(2, 4, 5, 6, 8)

scala> def condition(i: Int) = {
     |   println("processing " + i)
     |   i % 2 == 0
     | }
condition: (i: Int)Boolean

scala> list.iterator.takeWhile(condition _).sum
processing 2
processing 4
processing 5
res4: Int = 6

यह बिल्कुल उसी तरह की सादगी थी जिसकी मुझे तलाश थी - धन्यवाद!
टान्नर

14

आप स्कैल्प में फोल्डाइट के आलसी संस्करण का उपयोग करके एक कार्यात्मक शैली में जो चाहें कर सकते हैं। अधिक गहराई से स्पष्टीकरण के लिए, इस ब्लॉग पोस्ट को देखें । जबकि यह समाधान एक का उपयोग करता है Stream, आप एक Iterableको Streamकुशलतापूर्वक में बदल सकते हैं iterable.toStream

import scalaz._
import Scalaz._

val str = Stream(2,1,2,2,2,2,2,2,2)
var i = 0 //only here for testing
val r = str.foldr(Some(0):Option[Int])((n,s) => {
  println(i)
  i+=1
  if (n % 2 == 0) s.map(n+) else None
})

यह केवल प्रिंट करता है

0
1

जो स्पष्ट रूप से दिखाता है कि अनाम फ़ंक्शन को केवल दो बार कहा जाता है (जब तक कि यह विषम संख्या का सामना नहीं करता है)। यह तह की परिभाषा के कारण है, जिसका हस्ताक्षर (मामले में Stream) है def foldr[B](b: B)(f: (Int, => B) => B)(implicit r: scalaz.Foldable[Stream]): B। ध्यान दें कि अनाम फ़ंक्शन अपने दूसरे तर्क के रूप में नाम पैरामीटर लेता है, इसलिए इसका मूल्यांकन करने की आवश्यकता नहीं है।

Btw, आप अभी भी इसे ओपी के पैटर्न मिलान समाधान के साथ लिख सकते हैं, लेकिन मुझे लगता है कि / और फिर और अधिक सुरुचिपूर्ण नक्शा।


यदि आप डाल क्या होता है printlnसे पहले if- elseअभिव्यक्ति?
लापता

@ मिस्सिंगफैक्टर: तब यह 0 और 1 प्रिंट करता है, लेकिन अधिक नहीं
किम स्टेबेल

@missingfaktor: चूंकि मेरी बात इस तरह से आसान है, इसलिए मैंने इसे उत्तर में बदल दिया
किम स्टेबेल

1
ध्यान दें कि आप किसी भी पुनरावृत्ति को एक स्ट्रीम में बदल सकते हैं toStream, इसलिए यह उत्तर पहले प्रकट होने की तुलना में अधिक सामान्य-उद्देश्य है।
रेक्स केर

2
चूंकि स्कैल्प का उपयोग कर उर why 0.some why का उपयोग क्यों नहीं करता है?
पीडोफुरला

7

खैर, स्काला गैर स्थानीय रिटर्न की अनुमति देता है। इस पर अलग-अलग राय है कि यह एक अच्छी शैली है या नहीं।

scala> def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
     |   nums.foldLeft (Some(0): Option[Int]) {
     |     case (None, _) => return None
     |     case (Some(s), n) if n % 2 == 0 => Some(s + n)
     |     case (Some(_), _) => None
     |   }
     | }
sumEvenNumbers: (nums: Iterable[Int])Option[Int]

scala> sumEvenNumbers(2 to 10)
res8: Option[Int] = None

scala> sumEvenNumbers(2 to 10 by 2)
res9: Option[Int] = Some(30)

संपादित करें:

इस विशेष मामले में, जैसा कि @ अर्जन ने सुझाव दिया, आप भी कर सकते हैं:

def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
  nums.foldLeft (Some(0): Option[Int]) {
    case (Some(s), n) if n % 2 == 0 => Some(s + n)
    case _ => return None
  }
}

2
इसके बजाय Some(0): Option[Int]आप बस लिख सकते हैं Option(0)
लुइगी प्लिंज

1
@ लुइगीप्लिंग, हां। मैंने ओपी के कोड को कॉपी-पेस्ट किया और केवल एक बिंदु बनाने के लिए आवश्यक पर्याप्त संशोधन किया।
लापता

5

बिल्लियों के लिए एक विधि कहा जाता है foldM जो शॉर्ट-सर्किट करता है (के लिए Vector, List, Stream, ...)।

यह निम्नानुसार काम करता है:

def sumEvenNumbers(nums: Stream[Int]): Option[Long] = {
  import cats.implicits._
  nums.foldM(0L) {
    case (acc, c) if c % 2 == 0 => Some(acc + c)
    case _ => None
  }
}

जैसे ही संग्रह पर तत्वों में से एक भी नहीं है, वह लौटता है।


4

आप foldMबिल्लियों के लिबास से उपयोग कर सकते हैं (जैसा कि @Didac द्वारा सुझाया गया है) लेकिन मैं Eitherइसके बजाय Optionयदि आप वास्तविक राशि प्राप्त करना चाहते हैं तो उपयोग करने का सुझाव देते हैं।

bifoldMapसे परिणाम निकालने के लिए उपयोग किया जाता है Either

import cats.implicits._

def sumEven(nums: Stream[Int]): Either[Int, Int] = {
    nums.foldM(0) {
      case (acc, n) if n % 2 == 0 => Either.right(acc + n)
      case (acc, n) => {
        println(s"Stopping on number: $n")
        Either.left(acc)
      }
    }
  }

उदाहरण:

println("Result: " + sumEven(Stream(2, 2, 3, 11)).bifoldMap(identity, identity))
> Stopping on number: 3
> Result: 4

println("Result: " + sumEven(Stream(2, 7, 2, 3)).bifoldMap(identity, identity))
> Stopping on number: 7
> Result: 2

एक समान जवाब पोस्ट करने के लिए यहां आया, क्योंकि यह मेरी राय में करने के लिए सबसे सुविधाजनक अभी तक एफपी तरीका है। मैंने आश्चर्य किया कि कोई भी इसके लिए वोट नहीं करता है। तो, मेरे +1 को पकड़ो। (मैं (acc + n).asRightइसके बजाय Either.right(acc + n)किसी भी तरह से पसंद करता हूं )
पेट में

1

@Rex Kerr आपके उत्तर ने मेरी मदद की, लेकिन मुझे इसका उपयोग करने के लिए इसे ट्विस्ट करने की आवश्यकता थी

  
  def foldOrFail [A, B, C, D] (मानचित्र: B => Either [D, C]) (मर्ज: (A, C) => A) (प्रारंभिक: A) (यह: Iterable [B]:) या तो [डी, ए] = {
    वैल ii = it.iterator
    var b = आरंभिक
    जबकि (ii.hasNext) {
      वैल एक्स = ii.next
      नक्शा (x) मैच {
        मामला वाम (त्रुटि) => वापसी वाम (त्रुटि)
        केस राइट (d) => b = मर्ज (b, d)
      }
    }
    राइट (ख)
  }

1

आप एक अस्थायी संस्करण का उपयोग करके और takeWhile का उपयोग करने की कोशिश कर सकते हैं। यहाँ एक संस्करण है।

  var continue = true

  // sample stream of 2's and then a stream of 3's.

  val evenSum = (Stream.fill(10)(2) ++ Stream.fill(10)(3)).takeWhile(_ => continue)
    .foldLeft(Option[Int](0)){

    case (result,i) if i%2 != 0 =>
          continue = false;
          // return whatever is appropriate either the accumulated sum or None.
          result
    case (optionSum,i) => optionSum.map( _ + i)

  }

evenSumहोना चाहिए Some(20)इस मामले में।


0

आप कॉलिंग कोड में इसे संभालते हुए, अपनी समाप्ति की कसौटी पर खरा उतरने के लिए एक अच्छा चुना हुआ अपवाद फेंक सकते हैं।


1
स्काला पहले से ही इस तरह की सुविधा के साथ आता है: scala-lang.org/api/current/scala/util/control/Breaks.html
Malte Schwerhoff

0

स्पैन का उपयोग करने वाला एक अधिक उपयोगी समाधान होगा:

val (l, r) = numbers.span(_ % 2 == 0)
if(r.isEmpty) Some(l.sum)
else None

... लेकिन यह सूची को दो बार ट्रेस करता है यदि सभी संख्याएँ समान हों


2
मुझे आपके समाधान द्वारा अनुकरणीय पार्श्व सोच पसंद है, लेकिन यह केवल सामान्य प्रश्न से निपटने के बजाए विशिष्ट उदाहरण को हल करता है कि कैसे एक गुना जल्दी समाप्त करने के लिए।
इनेमाइकिन

मैं यह दिखाना चाहता था कि रिवर्स कैसे करना है, एक फोल्ड को जल्दी समाप्त नहीं करना है, लेकिन केवल उन फोल्डिंग (इस मामले में योग) से अधिक मूल्यों को हम फोल्ड करना चाहते हैं
अर्जन

0

सिर्फ एक "अकादमिक" कारणों के लिए:

var headers = Source.fromFile(file).getLines().next().split(",")
var closeHeaderIdx = headers.takeWhile { s => !"Close".equals(s) }.foldLeft(0)((i, S) => i+1)

दो बार लेता है तो यह चाहिए लेकिन यह एक अच्छा एक लाइनर है। यदि "बंद" नहीं मिला तो यह वापस आ जाएगा

headers.size

एक और (बेहतर) यह एक है:

var headers = Source.fromFile(file).getLines().next().split(",").toList
var closeHeaderIdx = headers.indexOf("Close")
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.