스칼라리스트 연결, ::: 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.