스칼라리스트 연결, ::: vs ++


362

스칼라에서 목록 :::++연결 목록 간에 차이점이 있습니까?

scala> List(1,2,3) ++ List(4,5)
res0: List[Int] = List(1, 2, 3, 4, 5)

scala> List(1,2,3) ::: List(4,5)
res1: List[Int] = List(1, 2, 3, 4, 5)

scala> res0 == res1
res2: Boolean = true

에서 문서 것 같습니다 ++반면 더 일반적이다 :::입니다 List- 특정. 후자는 다른 기능 언어로 사용되기 때문에 제공됩니까?


4
또한 다음 :::과 같이 시작하는 모든 방법과 같은 접두사 연산자입니다.:
Ben Jackson

3
답변은 스칼라가 스칼라의 목록과 연산자 균일 성 (또는 후자의 부족)을 중심으로 진화 된 방식을 거의 묘사합니다. 아주 간단한 것이 스칼라 학습자의 시간을 혼란스럽게하고 낭비하기 위해 아주 작은 꼬리를 가지고 있다는 것은 불행한 일입니다. 2.12에서 수평을 유지하기를 바랍니다.
matanster

답변:


321

유산. 목록은 원래 기능적인 언어로 정의되었습니다.

1 :: 2 :: Nil // a list
list1 ::: list2  // concatenation of two lists

list match {
  case head :: tail => "non-empty"
  case Nil          => "empty"
}

물론 스칼라는 다른 컬렉션을 임시 방식으로 발전시켰다. 2.8이 나왔을 때 코드 재사용 및 일관성있는 API를 극대화하기 위해 컬렉션이 다시 디자인되었으므로 두 컬렉션, 심지어 반복자 ++를 연결 하는 데 사용할 수 있습니다 . 그러나 목록은 사용되지 않는 하나 또는 두 개를 제외하고 원래 연산자를 유지해야합니다.


19
그래서 피하는 것이 좋습니다 :::에 찬성 ++지금은? +:대신에 ::?를 사용하십시오 .
Luigi Plinge

37
::패턴 일치로 인해 유용합니다 (다니엘 두 번째 예 참조). 당신은 그렇게 할 수 없습니다+:
패러다임

1
@Luigi List대신 사용 하는 Seq경우 관용적 List방법을 사용할 수도 있습니다 . 반면 에 다른 유형 으로 변경 하기가 더 어려워집니다 .
Daniel C. Sobral

2
나는 하나가 모두 목록 관용적을 운영하고 있습니다 (같은 것을 그것을 잘 발견 :::::) 다른 컬렉션에 공통되는 일반적인 작업. 나는 언어에서 어떤 조작도 포기하지 않을 것이다.
조르지오

21
@paradigmatic 스칼라 2.10가 :++:객체 추출기.
0__

97

항상 사용하십시오 :::. 효율성과 형식 안전성이라는 두 가지 이유가 있습니다.

능률

x ::: y ::: z올바른 연관성이 x ++ y ++ z있기 때문에 보다 빠릅니다 :::. x ::: y ::: z는로 해석되며 x ::: (y ::: z)이는 알고리즘보다 빠릅니다 (x ::: y) ::: z(후자는 O (| x |) 더 많은 단계 필요).

타입 안전

:::당신 과 함께 두 개의 연결할 수 있습니다 List. 를 사용 ++하면에 컬렉션을 추가 할 수 있습니다 List.

scala> List(1, 2, 3) ++ "ab"
res0: List[AnyVal] = List(1, 2, 3, a, b)

++다음과 혼합하기도 쉽습니다 +.

scala> List(1, 2, 3) + "ab"
res1: String = List(1, 2, 3)ab

9
두 목록을 연결하면 차이가 없지만 3 이상인 경우 좋은 지적이 있으며 빠른 벤치 마크로 확인했습니다. 그러나 효율성이 걱정 x ::: y ::: z되면로 교체해야합니다 List(x, y, z).flatten. pastebin.com/gkx7Hpad
Luigi Plinge

3
왼쪽 연관 연결에 더 많은 O (x) 단계가 필요한 이유를 설명하십시오. 둘 다 O (1)에서 작동한다고 생각했습니다.
pacman

6
@pacman리스트는 단독으로 연결되어 하나의리스트를 다른리스트에 추가하기 위해 첫 번째리스트의 사본을 끝에 붙여야합니다. 따라서 첫 번째 목록의 요소 수와 관련하여 연결은 O (n)입니다. 두 번째 목록의 길이는 런타임에 영향을 미치지 않으므로 짧은 목록을 긴 목록에 추가하는 것보다 긴 목록을 짧은 목록에 추가하는 것이 좋습니다.
puhlen

1
@pacman Scala의 목록은 변경할 수 없습니다 . 따라서 연결을 수행 할 때 마지막 링크를 교체 할 수 없습니다. 처음부터 새 목록을 만들어야합니다.
ZhekaKozlov

4
@pacman 복잡성은 항상 길이에 선형 적 x이며 y( z어떤 경우에도 반복되지 않으므로 런타임에 영향을 미치지 않으므로 짧은 목록에 긴 목록을 다른 방법보다 추가하는 것이 좋습니다) 점근 적 복잡성은 전체 이야기를 말하지 않습니다. x ::: (y ::: z)반복 y하고 추가 z한 다음 x결과 를 반복 하고 추가합니다 y ::: z. x그리고 y두 번 반복된다. (x ::: y) ::: z반복 x하고 추가 y한 다음 결과 x ::: y와 반복을 반복합니다 z. y여전히 한 번 반복되지만 x이 경우 두 번 반복됩니다.
puhlen

84

:::++모든 트래 버터 블과 함께 사용할 수 있지만 목록에서만 작동합니다 . 현재 구현 (2.9.0)에서 ++다시 내리는 :::인수도 경우 List.


4
따라서 ::: 및 ++를 사용하여 작업하는 것은 매우 쉽습니다. 코드 / 스타일에 혼란을 줄 수 있습니다.
ses

24

다른 점은 첫 번째 문장이 다음과 같이 구문 분석된다는 것입니다.

scala> List(1,2,3).++(List(4,5))
res0: List[Int] = List(1, 2, 3, 4, 5)

두 번째 예는 다음과 같이 구문 분석됩니다.

scala> List(4,5).:::(List(1,2,3))
res1: List[Int] = List(1, 2, 3, 4, 5)

따라서 매크로를 사용하는 경우주의해야합니다.

또한 ++두 목록에 대해 호출 :::하지만 목록에서 목록으로 빌더를 갖는 암시 적 값을 요청하므로 더 많은 오버 헤드가 발생합니다. 그러나 마이크로 벤치 마크는 그러한 의미에서 유용한 것을 입증하지 못했습니다. 컴파일러가 그러한 호출을 최적화한다고 생각합니다.

예열 후 마이크로 벤치 마크.

scala>def time(a: => Unit): Long = { val t = System.currentTimeMillis; a; System.currentTimeMillis - t}
scala>def average(a: () => Long) = (for(i<-1 to 100) yield a()).sum/100

scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ++ List(e) } })
res1: Long = 46
scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ::: List(e ) } })
res2: Long = 46

Daniel C. Sobrai가 말했듯이을 사용하여 컬렉션의 모든 내용을 목록에 추가 할 수 ++있지만 목록을 :::연결하는 것만 가능합니다.


20
지나치게 단순한 마이크로 벤치 마크를 게시하지 말고 투표하십시오.
Mikaël Mayer
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.