<commit_before>package fpinscala.laziness

/** Implement a lazy list
 *
 * @author    "<NAME> <<EMAIL>>"
 * @version   1.1
 * @since     1.0
 */
sealed trait Stream[+A] { self =>

  import Stream._

  def headOption1: Option[A] = self match {
    case Empty => None
    case Cons(h, t) => Some(h())   // Explicitly evaluate the thunk
  }

  def headOption: Option[A] = 
    foldRight(None: Option[A])((a, _) => Some(a))

  def headSafe[B>:A](default: B): B =
    headOption getOrElse default 

  def tailOption: Option[Stream[A]] = self match {
    case Empty => None
    case Cons(h, t) => Some(t())
  } 

  def tailSafe: Stream[A] =
    tailOption getOrElse empty[A]

  def toList1: List[A] = self match {
    case Cons(h, t) => h() :: t().toList1
    case Empty => Nil
  }

  def drop(n: Int): Stream[A] = 
    if (n < 1) self
    else self match {
      case Cons(_, t) => t().drop(n-1)
      case _ => Empty
    }

  def dropWhile(p: A => Boolean): Stream[A] =
    self match {
      case Cons(h, t) if p(h()) => t().dropWhile(p)
      case _ => self
    }

  def take(n: Int): Stream[A] = 
    if (n < 1) Empty
    else self match {
      case Cons(h, t) => cons(h(), t().take(n-1))
      case _ => Empty
    }

  def takeWhile1(p: A => Boolean): Stream[A] =
    self match {
      case Cons(h, t) if p(h()) => cons(h(), t().takeWhile1(p))
      case _ => Empty
    }

  def exists1(p: A => Boolean): Boolean =
    self match {
      case Cons(h, t) => p(h()) || t().exists1(p)
      case _ => false
    }

  /** Reduce a stream right to left with a function and initial value.
   *
   *  @param z initial value of the accumulator (value
   *           for empty stream)
   *  @param f folding function applied to stream
   *  @return A Stream of values
   *  @note Return value generated from stream elements
   *        right to left.
   *  @note Second parameter to f not strict, allows for
   *        early termination from the recursion.
   *  
   */
  def foldRight[B](z: => B)(f: (A, => B) => B): B =
    self match {
      case Cons(h, t) => f(h(), t().foldRight(z)(f))
      case _ => z
    }

  /** Reduce a stream left to right with a function and initial value.
   *
   *  @param z initial value of the accumulator (value
   *           for empty stream)
   *  @param f folding function applied to stream
   *  @return A Stream of values
   *  @note Return value generated from stream elements
   *        left to right.
   *  @note Not infinite Stream safe.
   *  @note unchecked match used to gag harmless compiler warning.
   *  
   */
  def foldLeft[B](z: B)(f: (B, A) => B): B = {
    var accum = (z, self)
    while (accum._2 != Empty) accum = (accum._2: @unchecked) match {
      case Cons(h, t) => (f(accum._1, h()), t())
    }
    accum._1
  }

  def exists(p: A => Boolean): Boolean =
    foldRight(false)((a, b) => p(a) || b)

  def forAll(p: A => Boolean): Boolean =
    foldRight(true)((a, b) => p(a) && b)

  def takeWhile(p: A => Boolean): Stream[A] =
    foldRight(empty[A])((a, b) => 
      if (p(a)) cons(a, b)
      else Empty)

  def toList: List[A] =
    foldRight(Nil: List[A])((a, as) => a :: as.toList)

  def mapFR[B](f: A => B): Stream[B] =
    foldRight(empty[B])((a, bs) => cons(f(a), bs))

  def map[B](f: A => B): Stream[B] = {
    def uf(s: Stream[A]): Option[(B, Stream[A])] = s match {
      case Cons(a, as) => Some( (f(a()), as()) )
      case Empty => None
    }
    unfold(self)(uf)
  }

  def filter(p: A => Boolean): Stream[A] =
    foldRight(empty[A])((a, as) =>
      if (p(a)) cons(a, as)
      else as )

  /** Strict operator wrapper for cons */
  def #::[B>:A](b: B): Stream[B] =
    cons(b, self)

  /** Prepend supertype stream */
  def #:::[B>:A](bs: Stream[B]): Stream[B] =
    bs.foldRight(self: Stream[B])((b,bs) => cons(b, bs))

  /** Append supertype stream */
  def :::#[B>:A](bs: Stream[B]): Stream[B] =
    foldRight(bs)((a,as) => cons(a, as))          // Book ans way

  /** Flatmap (Bind) for Streams */
  def flatMap1[B](f: A => Stream[B]): Stream[B] =
    foldRight(empty[B])((a, bs) => f(a) #::: bs)  // Scala library way

  /** Flatmap (Bind) for Streams */
  def flatMap[B](f: A => Stream[B]): Stream[B] =
    foldRight(empty[B])((a, bs) => f(a) :::# bs)  // Book answer way

  /** Return the first element which matches a predicate */
  def find(p: A => Boolean) =
    filter(p).headOption

  def zipWith[B,C](that: Stream[B])(f: (A, B) => C): Stream[C] =
    unfold((self, that)) {
      case (Cons(a, as), Cons(b, bs)) =>
        Some((f(a(), b()), (as(), bs())))
      case _ =>
        None
    }

  def zip[B](that: Stream[B]): Stream[(A,B)] =
    zipWith(that)((_,_))

  def zipAll[B](that: Stream[B]): Stream[(Option[A], Option[B])] =
    unfold((self, that)) {
      case (Cons(a, as), Cons(b, bs)) =>
        Some((Some(a()), Some(b())), (as(), bs()))
      case (Empty, Cons(b, bs)) =><commit_msg>Made fpinscala.laziness.Stream.flatMap implementation consistent with Scala Std Library's.<commit_after>package fpinscala.laziness

/** Implement a lazy list */
sealed trait Stream[+A] { self =>

  import Stream._

  def headOption1: Option[A] = self match {
    case Empty => None
    case Cons(h, t) => Some(h())   // Explicitly evaluate the thunk
  }

  def headOption: Option[A] = 
    foldRight(None: Option[A])((a, _) => Some(a))

  def headSafe[B>:A](default: B): B =
    headOption getOrElse default 

  def tailOption: Option[Stream[A]] = self match {
    case Empty => None
    case Cons(h, t) => Some(t())
  } 

  def tailSafe: Stream[A] =
    tailOption getOrElse empty[A]

  def toList1: List[A] = self match {
    case Cons(h, t) => h() :: t().toList1
    case Empty => Nil
  }

  def drop(n: Int): Stream[A] = 
    if (n < 1) self
    else self match {
      case Cons(_, t) => t().drop(n-1)
      case _ => Empty
    }

  def dropWhile(p: A => Boolean): Stream[A] =
    self match {
      case Cons(h, t) if p(h()) => t().dropWhile(p)
      case _ => self
    }

  def take(n: Int): Stream[A] = 
    if (n < 1) Empty
    else self match {
      case Cons(h, t) => cons(h(), t().take(n-1))
      case _ => Empty
    }

  def takeWhile1(p: A => Boolean): Stream[A] =
    self match {
      case Cons(h, t) if p(h()) => cons(h(), t().takeWhile1(p))
      case _ => Empty
    }

  def exists1(p: A => Boolean): Boolean =
    self match {
      case Cons(h, t) => p(h()) || t().exists1(p)
      case _ => false
    }

  /** Reduce a stream right to left with a function and initial value.
   *
   *  @param z initial value of the accumulator (value
   *           for empty stream)
   *  @param f folding function applied to stream
   *  @return A Stream of values
   *  @note Return value generated from stream elements
   *        right to left.
   *  @note Second parameter to f not strict, allows for
   *        early termination from the recursion.
   *  
   */
  def foldRight[B](z: => B)(f: (A, => B) => B): B =
    self match {
      case Cons(h, t) => f(h(), t().foldRight(z)(f))
      case _ => z
    }

  /** Reduce a stream left to right with a function and initial value.
   *
   *  @param z initial value of the accumulator (value
   *           for empty stream)
   *  @param f folding function applied to stream
   *  @return A Stream of values
   *  @note Return value generated from stream elements
   *        left to right.
   *  @note Not infinite Stream safe.
   *  @note unchecked match used to gag harmless compiler warning.
   *  
   */
  def foldLeft[B](z: B)(f: (B, A) => B): B = {
    var accum = (z, self)
    while (accum._2 != Empty) accum = (accum._2: @unchecked) match {
      case Cons(h, t) => (f(accum._1, h()), t())
    }
    accum._1
  }

  def exists(p: A => Boolean): Boolean =
    foldRight(false)((a, b) => p(a) || b)

  def forAll(p: A => Boolean): Boolean =
    foldRight(true)((a, b) => p(a) && b)

  def takeWhile(p: A => Boolean): Stream[A] =
    foldRight(empty[A])((a, b) => 
      if (p(a)) cons(a, b)
      else Empty)

  def toList: List[A] =
    foldRight(Nil: List[A])((a, as) => a :: as.toList)

  def mapFR[B](f: A => B): Stream[B] =
    foldRight(empty[B])((a, bs) => cons(f(a), bs))

  def map[B](f: A => B): Stream[B] = {
    def uf(s: Stream[A]): Option[(B, Stream[A])] = s match {
      case Cons(a, as) => Some( (f(a()), as()) )
      case Empty => None
    }
    unfold(self)(uf)
  }

  def filter(p: A => Boolean): Stream[A] =
    foldRight(empty[A])((a, as) =>
      if (p(a)) cons(a, as)
      else as )

  /** Strict operator wrapper for cons */
  def #::[B>:A](b: B): Stream[B] =
    cons(b, self)

  /** Prepend supertype stream */
  def #:::[B>:A](bs: Stream[B]): Stream[B] =
    bs.foldRight(self: Stream[B])((b,bs) => cons(b, bs))

  /** Append supertype stream */
  def :::#[B>:A](bs: Stream[B]): Stream[B] =
    foldRight(bs)((a,as) => cons(a, as))

  /** Flatmap (Bind) for Streams
   *
   *  @note Book's answers implementation
   *
   */
  def flatMap1[B](f: A => Stream[B]): Stream[B] =
    foldRight(empty[B])((a, bs) => f(a) :::# bs)

  /** Flatmap (Bind) for Streams
   *
   *  @note Scala Library's implementation
   *
   */
  def flatMap[B](f: A => Stream[B]): Stream[B] =
    foldRight(empty[B])((a, bs) => f(a) #::: bs)

  /** Return Option to first element which matches a predicate */
  def find(p: A => Boolean): Option[A] =
    filter(p).headOption

  def zipWith[B,C](that: Stream[B])(f: (A, B) => C): Stream[C] =
    unfold((self, that)) {
      case (Cons(a, as), Cons(b, bs)) =>
        Some((f(a(), b()), (as(), bs())))
      case _ =>
        None
    }

  def zip[B](that: Stream[B]): Stream[(A,B)] =
    zipWith(that)((_,_))

  def zipAll[B](that: Stream[B]): Stream[(Option[A], Option[B])] =
    unfold((self, that)) {
      case (Cons(a, as), Cons(b, bs)) =>
        Some((Some(a()), Some(b())), (as(), bs()))
      case (Empty, Cons(b, bs)) =>
{'fraction_non_alphanumeric': 0.1888268156424581, 'fraction_numerical': 0.0036566785170137127, 'mean_word_length': 2.5751633986928106, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 122, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7586473', 'n_tokens_mistral': 3741, 'n_tokens_neox': 3599, 'n_words': 1053}