복붙노트

[SCALA] 어떻게 풍부하게 - 내 라이브러리 스칼라 컬렉션에 패턴을 적용합니까?

SCALA

어떻게 풍부하게 - 내 라이브러리 스칼라 컬렉션에 패턴을 적용합니까?

스칼라에서 사용할 수있는 가장 강력한 패턴 중 하나는 동적 방법 해상도를 필요로하지 않고 기존 클래스에 메소드를 추가하는 표시 암시 적 변환을 사용하는 풍부하게 - 내 - 라이브러리 * 패턴입니다. 예를 들어, 우리는 모든 문자열들이 있었다, 우리가 할 수 얼마나 많은 공백 문자를 계산하는 방법 공간을했다달라고 :

class SpaceCounter(s: String) {
  def spaces = s.count(_.isWhitespace)
}
implicit def string_counts_spaces(s: String) = new SpaceCounter(s)

scala> "How many spaces do I have?".spaces
res1: Int = 5

일반적인 컬렉션을 처리 할 때 불행하게도,이 패턴은 문제로 실행됩니다. 예를 들어, 질문의 수는 컬렉션을 순차적으로 그룹화 항목에 대해 질문했다. 거기는 한 번에 작동에 내장 아무것도 없다, 그래서 이것은 일반적인 수집 C와 일반적인 요소 유형 A를 사용하여 풍부하게 - 내 라이브러리 패턴을위한 이상적인 후보자 같다 :

class SequentiallyGroupingCollection[A, C[A] <: Seq[A]](ca: C[A]) {
  def groupIdentical: C[C[A]] = {
    if (ca.isEmpty) C.empty[C[A]]
    else {
      val first = ca.head
      val (same,rest) = ca.span(_ == first)
      same +: (new SequentiallyGroupingCollection(rest)).groupIdentical
    }
  }
}

물론, 그것은 작동하지 않습니다 제외. REPL은 우리에게 알려줍니다 :

<console>:12: error: not found: value C
               if (ca.isEmpty) C.empty[C[A]]
                               ^
<console>:16: error: type mismatch;
 found   : Seq[Seq[A]]
 required: C[C[A]]
                 same +: (new SequentiallyGroupingCollection(rest)).groupIdentical
                      ^

두 가지 문제가있다 : 어떻게 우리가 C [C [A] 빈 C [A] 목록에서 (또는 허공에서)을받을 수 있나요? 라인 대신 서열 [서열 [A] : 어떻게 우리는 C [C [A] 다시 같은 +에서받을 수 있나요?

* 이전 포주 - 내 - 라이브러리라고도합니다.

해결법

  1. ==============================

    1.이 문제를 이해하는 열쇠는 구축하고 컬렉션 라이브러리 컬렉션을 작업하는 두 가지 방법이 있다는 것을 깨닫게하는 것입니다. 하나는 모든 좋은 방법과 공공 컬렉션 인터페이스입니다. 컬렉션 라이브러리를 만드는 광범위하게 사용되지만, 어느되는, 다른 하나는 거의 외부에서 사용되지 않습니다, 건축업자이다.

    이 문제를 이해하는 열쇠는 구축하고 컬렉션 라이브러리 컬렉션을 작업하는 두 가지 방법이 있다는 것을 깨닫게하는 것입니다. 하나는 모든 좋은 방법과 공공 컬렉션 인터페이스입니다. 컬렉션 라이브러리를 만드는 광범위하게 사용되지만, 어느되는, 다른 하나는 거의 외부에서 사용되지 않습니다, 건축업자이다.

    풍요로운 우리의 문제는 정확히 같은 종류의 컬렉션을 반환 할 때 컬렉션 라이브러리 자체가 직면하고있는 동일 하나입니다. 그것은 우리가 컬렉션을 구축하고자하지만, 일반적으로 작업 할 때, 우리는 "컬렉션이 이미있는 동일한 유형"을 참조 할 수있는 방법이없는 것이다. 그래서 우리는 빌더가 필요합니다.

    이제 질문은 : 우리가 어디에서 우리의 빌더를받을 수 있나요? 명백한 장소는 컬렉션 자체입니다. 이 작동하지 않습니다. 우리는 이미 우리가 컬렉션의 유형을 잊어 가고 있다고, 일반적인 컬렉션에 이동에 결정했다. 그래서 컬렉션이 우리가 원하는 유형의 더 많은 컬렉션을 생성하는 것 빌더를 반환 할 수에도 불구하고, 그것은 종류가 무엇인지 모르겠다.

    대신, 우리는 주위에 떠있다 CanBuildFrom의 implicits에서 우리의 건축업자를 얻을. 다음은 입력 및 출력 유형을 일치하고 당신에게 적절하게 입력 빌더를 제공하기위한 목적으로 특별히 존재한다.

    그래서, 우리는 할 두 가지 개념 도약을 가지고 :

    의 예를 살펴 보자.

    class GroupingCollection[A, C[A] <: Iterable[A]](ca: C[A]) {
      import collection.generic.CanBuildFrom
      def groupedWhile(p: (A,A) => Boolean)(
        implicit cbfcc: CanBuildFrom[C[A],C[A],C[C[A]]], cbfc: CanBuildFrom[C[A],A,C[A]]
      ): C[C[A]] = {
        val it = ca.iterator
        val cca = cbfcc()
        if (!it.hasNext) cca.result
        else {
          val as = cbfc()
          var olda = it.next
          as += olda
          while (it.hasNext) {
            val a = it.next
            if (p(olda,a)) as += a
            else { cca += as.result; as.clear; as += a }
            olda = a
          }
          cca += as.result
        }
        cca.result
      }
    }
    implicit def iterable_has_grouping[A, C[A] <: Iterable[A]](ca: C[A]) = {
      new GroupingCollection[A,C](ca)
    }
    

    의이 떨어져 보자. 첫째, 컬렉션의-컬렉션을 구축하기 위해, 우리는 우리가 컬렉션의 두 가지 유형을 구축해야합니다 알고 C를 [A] 함께 모든 그룹을 수집 각 그룹 및 C [C [A]에 대한. 따라서, 우리는 두 빌더, [A]의 C를 같이 소요 구축 하나, 및 C를 소요 한 [A]의 필요 및 C [C [A] S를 구축한다. CanBuildFrom의 유형 서명을 보면, 우리는 참조

    CanBuildFrom[-From, -Elem, +To]
    

    CanBuildFrom은 우리가 시작있어 수집의 종류 알고 싶은 것을 의미합니다 - 우리의 경우를, 그것은 C의 [A], 다음 생성 된 컬렉션의 요소와 해당 모음의 유형입니다. 그래서 우리는 암시 적 매개 변수 cbfcc 및 CBFC 사람들을 입력합니다.

    이를 실현하는 데, 그 대부분의 작업을합니다. 우리는 우리에게 빌더를 제공하기 위해 우리의 CanBuildFroms을 사용할 수 있습니다 (당신이해야 할 모든 적용)입니다. 그리고 하나의 빌더, + =와 컬렉션을 구축이 궁극적으로 결과로 예상되는 컬렉션으로 변환하고, 자신을 비우고 분명 다시 시작할 준비가 될 수 있습니다. 건축업자는 우리의 첫 번째 컴파일 오류를 해결하는 빈 시작, ​​우리는 대신 재귀 빌더를 사용하고 있기 때문에, 두 번째 오류는 사라집니다.

    실제로 작업을 수행하는 알고리즘이 아닌 - - 마지막으로 작은 세부 사항은 암시 적 변환입니다. 우리는 새로운 GroupingCollection [A, C]하지 [A, C [A]를 사용합니다. 클래스 선언 그것이 전달 된 A의 그것 자체를 채우고 하나 개의 매개 변수에 대한 C 되었기 때문이다. 그래서 우리는 단지 그것에게 C 형 손, 그것은 [A] 그것의 C를 만들 수 있습니다. 당신이 다른 방법을 시도하는 경우 사소한 세부 사항,하지만 당신은 컴파일 시간 오류를 얻을 수 있습니다.

    여기서, I는 "동일한 요소"수집보다 조금 더 일반적인 방법을 변경 한 - 순차적 인 요소의 테스트가 실패 할 때마다 오히려 방법은 떨어져 원래의 수집을 잘라냅니다.

    의 행동에 우리의 방법을 보자 :

    scala> List(1,2,2,2,3,4,4,4,5,5,1,1,1,2).groupedWhile(_ == _)
    res0: List[List[Int]] = List(List(1), List(2, 2, 2), List(3), List(4, 4, 4), 
                                 List(5, 5), List(1, 1, 1), List(2))
    
    scala> Vector(1,2,3,4,1,2,3,1,2,1).groupedWhile(_ < _)
    res1: scala.collection.immutable.Vector[scala.collection.immutable.Vector[Int]] =
      Vector(Vector(1, 2, 3, 4), Vector(1, 2, 3), Vector(1, 2), Vector(1))
    

    효과가있다!

    즉 연속 두 암시 적 변환을 필요로하기 때문에 유일한 문제는, 우리가하지 일반적으로 할 배열이 방법을 사용할 수있다이다. 그래서, 배열에 대해 별도의 암시 적 변환을 작성 WrappedArray에 캐스팅, 등이 극복하는 방법은 여러 가지가 있습니다.

    편집 : 배열과 문자열과 같은 처리에 대한 나의 선호하는 방법은 코드가 더 일반적인 만든 다음 배열도 작동하는 방법으로 다시보다 구체적으로 만들기 위해 적절한 암시 적 변환을 사용하는 것입니다. 이 특별한 경우 :

    class GroupingCollection[A, C, D[C]](ca: C)(
      implicit c2i: C => Iterable[A],
               cbf: CanBuildFrom[C,C,D[C]],
               cbfi: CanBuildFrom[C,A,C]
    ) {
      def groupedWhile(p: (A,A) => Boolean): D[C] = {
        val it = c2i(ca).iterator
        val cca = cbf()
        if (!it.hasNext) cca.result
        else {
          val as = cbfi()
          var olda = it.next
          as += olda
          while (it.hasNext) {
            val a = it.next
            if (p(olda,a)) as += a
            else { cca += as.result; as.clear; as += a }
            olda = a
          }
          cca += as.result
        }
        cca.result
      }
    }
    

    여기에서 우리는 우리가 제공하는 암시를 추가 한 반복 가능 [A] C에서 - 이것은 단지 신원이 될 것이다 대부분의 콜렉션들에 대해 (예를 들어 목록 [A] 이미 인의 Iterable [A])하지만, 배열 것이있을 것입니다 실제 암시 적 변환. 그리고, 결과적으로, 우리는 C는 [A] <요구 사항 하락했습니다에서 Iterable [A] - 우리는 기본적으로 그냥 <% 노골적인에 대한 요구 사항을 만들었을, 그래서 우리는 것 대신 컴파일러 채우기를 갖는에서 명시 적으로 사용할 수 있습니다 거기에 우리를 위해. 또한, 우리는 우리의 컬렉션의-컬렉션 C [C [A]]는 제한을 완화 한 - 대신, 우리가 우리가 원하는 것을 할 나중에 채울 어떤 D [C]를합니다. 나중에이를 채울려고하고 있기 때문에, 우리는 클래스 수준 대신 메소드 수준으로까지 밀어했습니다. 그렇지 않으면 기본적으로 동일합니다.

    이제 문제는이를 사용하는 방법입니다. 일반 컬렉션의 경우, 우리가 할 수있는 :

    implicit def collections_have_grouping[A, C[A]](ca: C[A])(
      implicit c2i: C[A] => Iterable[A],
               cbf: CanBuildFrom[C[A],C[A],C[C[A]]],
               cbfi: CanBuildFrom[C[A],A,C[A]]
    ) = {
      new GroupingCollection[A,C[A],C](ca)(c2i, cbf, cbfi)
    }
    

    이제 C 연결하는 위치 [A]에 대한 C와 C [C [A] D [C]에 대한. 이 유형은 무엇에 해당하는 직선을 유지할 수 있도록 우리가 새로운 GroupingCollection에 대한 호출에 명시 적 제네릭 형식이 필요합니까합니다. 암시 만약 c2i 덕분 : C는 [A] =>의 Iterable [A]이 자동 배열을 처리한다.

    그러나 우리는 문자열을 사용하기 위해 무엇을하려는 경우, 잠깐? 당신이 "문자열의 문자열"을 가질 수 있기 때문에 이제 우리는 문제에있어. 여분의 추상화 할 수있는 곳이다 : 우리는 문자열을 유지하기 적합 D 뭔가를 호출 할 수 있습니다. 의는 벡터를 선택하고 다음을 보자 :

    val vector_string_builder = (
      new CanBuildFrom[String, String, Vector[String]] {
        def apply() = Vector.newBuilder[String]
        def apply(from: String) = this.apply()
      }
    )
    
    implicit def strings_have_grouping(s: String)(
      implicit c2i: String => Iterable[Char],
               cbfi: CanBuildFrom[String,Char,String]
    ) = {
      new GroupingCollection[Char,String,Vector](s)(
        c2i, vector_string_builder, cbfi
      )
    }
    

    우리는 문자열의 벡터의 건물을 처리하기 위해 새로운 CanBuildFrom이 필요합니다 (그러나 우리가 Vector.newBuilder [문자열]을 호출 할 필요가 있기 때문에 이것은 정말 간단합니다), 그리고 우리는 GroupingCollection가되도록 모든 유형에서 작성해야 현명 입력했습니다. 문자열은 문자의 집합에서 할 수 있도록 우리가 이미하는 [문자열, 숯불, 문자열] CanBuildFrom 주위에 떠 가지고 있습니다.

    현실을 사용해보십시오 :

    scala> List(true,false,true,true,true).groupedWhile(_ == _)
    res1: List[List[Boolean]] = List(List(true), List(false), List(true, true, true))
    
    scala> Array(1,2,5,3,5,6,7,4,1).groupedWhile(_ <= _) 
    res2: Array[Array[Int]] = Array(Array(1, 2, 5), Array(3, 5, 6, 7), Array(4), Array(1))
    
    scala> "Hello there!!".groupedWhile(_.isLetter == _.isLetter)
    res3: Vector[String] = Vector(Hello,  , there, !!)
    
  2. ==============================

    2.이 기준으로는 렉스 그의 뛰어난 답을 주었을 때 그것이 스칼라 컬렉션을보다 "풍부"로 많은 쉽게 커밋합니다. 간단한 경우에는 다음과 같을 수,

    이 기준으로는 렉스 그의 뛰어난 답을 주었을 때 그것이 스칼라 컬렉션을보다 "풍부"로 많은 쉽게 커밋합니다. 간단한 경우에는 다음과 같을 수,

    import scala.collection.generic.{ CanBuildFrom, FromRepr, HasElem }
    import language.implicitConversions
    
    class FilterMapImpl[A, Repr](val r : Repr)(implicit hasElem : HasElem[Repr, A]) {
      def filterMap[B, That](f : A => Option[B])
        (implicit cbf : CanBuildFrom[Repr, B, That]) : That = r.flatMap(f(_).toSeq)
    }
    
    implicit def filterMap[Repr : FromRepr](r : Repr) = new FilterMapImpl(r)
    

    이는 모든 GenTraversableLikes에 filterMap 동작을 존중에 "같은 결과 유형 '추가

    scala> val l = List(1, 2, 3, 4, 5)
    l: List[Int] = List(1, 2, 3, 4, 5)
    
    scala> l.filterMap(i => if(i % 2 == 0) Some(i) else None)
    res0: List[Int] = List(2, 4)
    
    scala> val a = Array(1, 2, 3, 4, 5)
    a: Array[Int] = Array(1, 2, 3, 4, 5)
    
    scala> a.filterMap(i => if(i % 2 == 0) Some(i) else None)
    res1: Array[Int] = Array(2, 4)
    
    scala> val s = "Hello World"
    s: String = Hello World
    
    scala> s.filterMap(c => if(c >= 'A' && c <= 'Z') Some(c) else None)
    res2: String = HW
    

    그리고 질문에서 예를 들어,이 솔루션은 지금처럼 보인다

    class GroupIdenticalImpl[A, Repr : FromRepr](val r: Repr)
      (implicit hasElem : HasElem[Repr, A]) {
      def groupIdentical[That](implicit cbf: CanBuildFrom[Repr,Repr,That]): That = {
        val builder = cbf(r)
        def group(r: Repr) : Unit = {
          val first = r.head
          val (same, rest) = r.span(_ == first)
          builder += same
          if(!rest.isEmpty)
            group(rest)
        }
        if(!r.isEmpty) group(r)
        builder.result
      }
    }
    
    implicit def groupIdentical[Repr : FromRepr](r: Repr) = new GroupIdenticalImpl(r)
    

    샘플 REPL 세션,

    scala> val l = List(1, 1, 2, 2, 3, 3, 1, 1)
    l: List[Int] = List(1, 1, 2, 2, 3, 3, 1, 1)
    
    scala> l.groupIdentical
    res0: List[List[Int]] = List(List(1, 1),List(2, 2),List(3, 3),List(1, 1))
    
    scala> val a = Array(1, 1, 2, 2, 3, 3, 1, 1)
    a: Array[Int] = Array(1, 1, 2, 2, 3, 3, 1, 1)
    
    scala> a.groupIdentical
    res1: Array[Array[Int]] = Array(Array(1, 1),Array(2, 2),Array(3, 3),Array(1, 1))
    
    scala> val s = "11223311"
    s: String = 11223311
    
    scala> s.groupIdentical
    res2: scala.collection.immutable.IndexedSeq[String] = Vector(11, 22, 33, 11)
    

    또, 같은 결과 유형 원리가 groupIdentical 직접 GenTraversableLike에 정의되어 있었다 된 것이라고 정확히 같은 방식으로 관찰되었다 있습니다.

  3. ==============================

    3.이 기준으로 마법 주문이 약간 마일 그의 뛰어난 답을 주었을 때 그것이 무엇 변경 커밋.

    이 기준으로 마법 주문이 약간 마일 그의 뛰어난 답을 주었을 때 그것이 무엇 변경 커밋.

    작품에 따라,하지만 정식인가? 나는 대포 중 하나를 수정 바랍니다. (또는 오히려, 대포, 큰 총 중 하나.) 바인드 뷰는 배열과 문자열에 응용 프로그램을 잃고, 상한 경우. 바운드가 GenTraversableLike 또는 TraversableLike 경우 중요하지 않는 것; 하지만 IsTraversableLike 당신에게 GenTraversableLike을 제공합니다.

    import language.implicitConversions
    import scala.collection.{ GenTraversable=>GT, GenTraversableLike=>GTL, TraversableLike=>TL }
    import scala.collection.generic.{ CanBuildFrom=>CBF, IsTraversableLike=>ITL }
    
    class GroupIdenticalImpl[A, R <% GTL[_,R]](val r: GTL[A,R]) {
      def groupIdentical[That](implicit cbf: CBF[R, R, That]): That = {
        val builder = cbf(r.repr)
        def group(r: GTL[_,R]) {
          val first = r.head
          val (same, rest) = r.span(_ == first)
          builder += same
          if (!rest.isEmpty) group(rest)
        }
        if (!r.isEmpty) group(r)
        builder.result
      }
    }
    
    implicit def groupIdentical[A, R <% GTL[_,R]](r: R)(implicit fr: ITL[R]):
      GroupIdenticalImpl[fr.A, R] =
      new GroupIdenticalImpl(fr conversion r)
    

    아홉 삶 고양이를 피부 하나 개 이상의 방법이있다. 이 버전은 내 소스가 GenTraversableLike로 변환되면, 한 내가 GenTraversable의 결과를 만들 수있는, 단지 그렇게 말한다. 나는 예전에 repr에 관심이 아니에요.

    class GroupIdenticalImpl[A, R](val r: GTL[A,R]) {
      def groupIdentical[That](implicit cbf: CBF[GT[A], GT[A], That]): That = {
        val builder = cbf(r.toTraversable)
        def group(r: GT[A]) {
          val first = r.head
          val (same, rest) = r.span(_ == first)
          builder += same
          if (!rest.isEmpty) group(rest)
        }
        if (!r.isEmpty) group(r.toTraversable)
        builder.result
      }
    }
    
    implicit def groupIdentical[A, R](r: R)(implicit fr: ITL[R]):
      GroupIdenticalImpl[fr.A, R] =
      new GroupIdenticalImpl(fr conversion r)
    

    이 첫 번째 시도는 GenTraversableLike에에 repr의 추한 변환이 포함되어 있습니다.

    import language.implicitConversions
    import scala.collection.{ GenTraversableLike }
    import scala.collection.generic.{ CanBuildFrom, IsTraversableLike }
    
    type GT[A, B] = GenTraversableLike[A, B]
    type CBF[A, B, C] = CanBuildFrom[A, B, C]
    type ITL[A] = IsTraversableLike[A]
    
    class FilterMapImpl[A, Repr](val r: GenTraversableLike[A, Repr]) { 
      def filterMap[B, That](f: A => Option[B])(implicit cbf : CanBuildFrom[Repr, B, That]): That = 
        r.flatMap(f(_).toSeq)
    } 
    
    implicit def filterMap[A, Repr](r: Repr)(implicit fr: ITL[Repr]): FilterMapImpl[fr.A, Repr] = 
      new FilterMapImpl(fr conversion r)
    
    class GroupIdenticalImpl[A, R](val r: GT[A,R])(implicit fr: ITL[R]) { 
      def groupIdentical[That](implicit cbf: CBF[R, R, That]): That = { 
        val builder = cbf(r.repr)
        def group(r0: R) { 
          val r = fr conversion r0
          val first = r.head
          val (same, other) = r.span(_ == first)
          builder += same
          val rest = fr conversion other
          if (!rest.isEmpty) group(rest.repr)
        } 
        if (!r.isEmpty) group(r.repr)
        builder.result
      } 
    } 
    
    implicit def groupIdentical[A, R](r: R)(implicit fr: ITL[R]):
      GroupIdenticalImpl[fr.A, R] = 
      new GroupIdenticalImpl(fr conversion r)
    
  4. from https://stackoverflow.com/questions/5410846/how-do-i-apply-the-enrich-my-library-pattern-to-scala-collections by cc-by-sa and MIT license