고정 된 수의 요소를 사용하여 목록을 여러 목록으로 분할


119

최대 N 개의 항목이있는 목록으로 요소 목록을 분할하는 방법은 무엇입니까?

예 : 7 개의 요소가있는 목록이 주어지면 4 개의 그룹을 만들고 마지막 그룹은 더 적은 요소로 남겨 둡니다.

split(List(1,2,3,4,5,6,"seven"),4)

=> List(List(1,2,3,4), List(5,6,"seven"))

답변:


213

나는 당신이 찾고 있다고 생각합니다 grouped. 반복자를 반환하지만 결과를 목록으로 변환 할 수 있습니다.

scala> List(1,2,3,4,5,6,"seven").grouped(4).toList
res0: List[List[Any]] = List(List(1, 2, 3, 4), List(5, 6, seven))

25
스칼라 목록에는 모든 것이 있습니다.
J Atkin 2015 년

이상한 질문이 있습니다. 동일한 경우에 데이터를 시퀀스로 변환하면 Stream Object가 생성됩니다. 왜 그런 겁니까?
Rakshith

3
@Rakshith 별도의 질문처럼 들립니다. Scala는 데이터 구조를 선택하는 신비한 그놈을 가지고 있으며 당신을 위해 Stream을 선택했습니다. 목록을 원하면 목록을 요청해야하지만 그놈의 판단을 신뢰할 수도 있습니다.
Ion Freeman

12

슬라이딩 방식을 사용하여 작업을 수행하는 훨씬 더 쉬운 방법이 있습니다. 다음과 같이 작동합니다.

val numbers = List(1, 2, 3, 4, 5, 6 ,7)

목록을 크기 3의 작은 목록으로 나누고 싶다고 가정 해 보겠습니다.

numbers.sliding(3, 3).toList

너에게 줄 것이다

List(List(1, 2, 3), List(4, 5, 6), List(7))

9

또는 직접 만들고 싶다면 :

def split[A](xs: List[A], n: Int): List[List[A]] = {
  if (xs.size <= n) xs :: Nil
  else (xs take n) :: split(xs drop n, n)
}

사용하다:

scala> split(List(1,2,3,4,5,6,"seven"), 4)
res15: List[List[Any]] = List(List(1, 2, 3, 4), List(5, 6, seven))

편집 : 2 년 후이 구현을 검토 sizeO (n)이므로이 구현을 권장하지 않습니다. 따라서이 메서드는 O (n ^ 2)이므로 내장 메서드가 큰 목록에서 더 빨라지는 이유를 설명합니다. 아래 설명에 언급되어 있습니다. 다음과 같이 효율적으로 구현할 수 있습니다.

def split[A](xs: List[A], n: Int): List[List[A]] =
  if (xs.isEmpty) Nil 
  else (xs take n) :: split(xs drop n, n)

또는 (약간) 더 효율적으로 사용 splitAt:

def split[A](xs: List[A], n: Int): List[List[A]] =
  if (xs.isEmpty) Nil 
  else {
    val (ys, zs) = xs.splitAt(n)   
    ys :: split(zs, n)
  }

4
xs splitAt n조합의 대안 xs take nxs drop n
Kipton 씨인

1
이 고려 재귀 구현 스택을 폭발
제드 웨슬리 스미스

@Kipton, true이지만 결과를 임시 값으로 추출하여 메서드에 몇 줄을 추가해야합니다. 나는 빠른 벤치 마크를했고, 그것을 사용하는 것 splitAt대신에 take/ drop4 %의 주위에 평균 성능을 향상; 둘 다 700-1000 % 더 빠릅니다 .grouped(n).toList!
Luigi Plinge 2011 년

@Luigi, 와우. 왜 grouped-toList그렇게 느린 지에 대한 생각 이 있습니까? 버그처럼 들립니다.
Kipton Barros 2011 년

@Jed 당신은 극단적 인 경우에 맞지만 당신의 구현은 당신이 그것을 사용하는 것에 달려 있습니다. OP의 유스 케이스 ( grouped존재하지 않은 경우 :))의 경우 단순성이 최우선 요소입니다. 표준 라이브러리의 경우 안정성과 성능이 우아함을 능가해야합니다. 그러나 Programming in Scala 와 Normal-recursive (꼬리 재귀가 아닌) 호출의 표준 라이브러리 에는 많은 예제 가 있습니다. FP 도구 상자에서 표준적이고 중요한 무기입니다.
Luigi Plinge 2011 년

4

꼬리 재귀 대 재귀에 대한 논의가 있었기 때문에 분할 방법의 꼬리 재귀 버전을 추가하고 있습니다. 나는 tailrec 주석을 사용하여 구현이 실제로 꼬리 반응이없는 경우 컴파일러가 불평하도록 강제했습니다. 꼬리 재귀는 내부적으로 루프로 바뀌고 스택이 무한정 커지지 않으므로 큰 목록에서도 문제를 일으키지 않을 것이라고 생각합니다.

import scala.annotation.tailrec


object ListSplitter {

  def split[A](xs: List[A], n: Int): List[List[A]] = {
    @tailrec
    def splitInner[A](res: List[List[A]], lst: List[A], n: Int) : List[List[A]] = {
      if(lst.isEmpty) res
      else {
        val headList: List[A] = lst.take(n)
        val tailList : List[A]= lst.drop(n)
        splitInner(headList :: res, tailList, n)
      }
    }

    splitInner(Nil, xs, n).reverse
  }

}

object ListSplitterTest extends App {
  val res = ListSplitter.split(List(1,2,3,4,5,6,7), 2)
  println(res)
}

1
이 답변은 설명을 추가하여 개선 할 수 있습니다. 허용되는 답변이이를 수행하기위한 표준적이고 의도 된 방법 인 것처럼 보이기 때문에 누군가이 답변을 선호하는 이유를 설명해야합니다.
Jeffrey Bosboom

0

나는 이것이 take / drop 대신 splitAt을 사용하는 구현이라고 생각합니다.

def split [X] (n:Int, xs:List[X]) : List[List[X]] =
    if (xs.size <= n) xs :: Nil
    else   (xs.splitAt(n)._1) :: split(n,xs.splitAt(n)._2)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.