Ruby에서 문자열을 주어진 길이의 덩어리로 자르는 가장 좋은 방법은 무엇입니까?


88

Ruby에서 문자열을 주어진 길이의 하위 문자열로 묶는 우아하고 효율적인 방법을 찾고 있습니다.

지금까지 내가 생각 해낼 수있는 최선의 방법은 다음과 같습니다.

def chunk(string, size)
  (0..(string.length-1)/size).map{|i|string[i*size,size]}
end

>> chunk("abcdef",3)
=> ["abc", "def"]
>> chunk("abcde",3)
=> ["abc", "de"]
>> chunk("abc",3)
=> ["abc"]
>> chunk("ab",3)
=> ["ab"]
>> chunk("",3)
=> []

대신 chunk("", n)반환 할 수 있습니다 . 그렇다면 다음을 메서드의 첫 번째 줄로 추가하십시오.[""][]

return [""] if string.empty?

더 나은 솔루션을 추천 하시겠습니까?

편집하다

이 우아하고 효율적인 솔루션에 대해 Jeremy Ruten에게 감사드립니다. [편집 : 비효율적입니다!]

def chunk(string, size)
    string.scan(/.{1,#{size}}/)
end

편집하다

string.scan 솔루션은 2.4 초 밖에 걸리지 않는 원래 슬라이스 기반 솔루션과 비교하여 512k를 1k 청크로 10000 번 자르는 데 약 60 초가 걸립니다.


원래 솔루션은 가능한 한 효율적이고 우아합니다. 문자열의 각 문자를 검사 할 필요가 없으며 전체를 배열로 바꾼 다음 다시 되돌릴 필요가 없습니다.
android.weasel

답변:


158

사용 String#scan:

>> 'abcdefghijklmnopqrstuvwxyz'.scan(/.{4}/)
=> ["abcd", "efgh", "ijkl", "mnop", "qrst", "uvwx"]
>> 'abcdefghijklmnopqrstuvwxyz'.scan(/.{1,4}/)
=> ["abcd", "efgh", "ijkl", "mnop", "qrst", "uvwx", "yz"]
>> 'abcdefghijklmnopqrstuvwxyz'.scan(/.{1,3}/)
=> ["abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz"]

좋습니다. 이제 훌륭합니다! 더 나은 방법이 있어야한다는 것을 알았습니다. Jeremy Ruten에게 감사합니다.
MiniQuark

3
def chunk (문자열, 크기); string.scan (/. {1, # {size}} /); 종료
MiniQuark

1
와우, 지금 바보 같아요. 나는 스캔이 어떻게 작동하는지 확인하는 것도 신경 쓰지 않았습니다.
Chuck

18
이 솔루션에주의하십시오. 이것은 정규 표현식이며, 그 /.비트는 개행 문자를 제외한 모든 문자를 포함한다는 것을 의미합니다 \n. 당신은 줄 바꿈 사용 포함 할 경우string.scan(/.{4}/m)
professormeowingtons

1
얼마나 영리한 솔루션입니까! 나는 정규 표현식을 좋아하지만이 목적을 위해 수량자를 사용하지는 않을 것입니다. 감사합니다 Jeremy Ruten
Cec

18

다른 방법은 다음과 같습니다.

"abcdefghijklmnopqrstuvwxyz".chars.to_a.each_slice(3).to_a.map {|s| s.to_s }

=> [ "abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz"]


15
또는 :"abcdefghijklmnopqrstuvwxyz".chars.each_slice(3).map(&:join)
Finbarr

3
개행 문자가 포함 된 문자열에서 작동하기 때문에이 기능을 좋아합니다.
Steve Davis

1
이것이 허용되는 솔루션이어야합니다. 길이가 pattern 과 일치하지 않으면 scan을 사용하면 마지막 토큰이 삭제 될 수 있습니다 .
count0

6

문자열이 청크 크기의 배수라는 것을 알고 있다면 이것이 가장 효율적인 솔루션이라고 생각합니다.

def chunk(string, size)
    (string.length / size).times.collect { |i| string[i * size, size] }
end

그리고 부품

def parts(string, count)
    size = string.length / count
    count.times.collect { |i| string[i * size, size] }
end

3
귀하의 문자열은 교체 할 경우 청크 크기의 배수 일 필요는 없습니다 string.length / size(string.length + size - 1) / size이 패턴은 정수 절단 처리하는 C 코드에서 일반적입니다 -.
질소

3

큰 문자열을 처리하고 한 번에 모든 청크를 저장할 필요가없는 경우 약간 다른 경우에 대한 또 다른 솔루션이 있습니다. 이런 식으로 한 번에 하나의 청크를 저장하고 문자열을 분할하는 것보다 훨씬 빠르게 수행합니다.

io = StringIO.new(string)
until io.eof?
  chunk = io.read(chunk_size)
  do_something(chunk)
end

매우 큰 문자열의 경우,이은 에 의해 지금까지 그것을 할 수있는 가장 좋은 방법은 . 이 메모리에 전체 문자열을 읽고 점점 피할 Errno::EINVAL같은 오류를 Invalid argument @ io_fread하고 Invalid argument @ io_write.
Joshua Pinter

2

약 593MB 데이터를 18991 32KB 조각으로 자르는 작은 테스트를했습니다. ctrl + C를 누르기 전에 100 % CPU를 사용하여 슬라이스 + 맵 버전을 15 분 이상 실행했습니다. String # unpack을 사용하는이 버전은 3.6 초 만에 완료되었습니다.

def chunk(string, size)
  string.unpack("a#{size}" * (string.size/size.to_f).ceil)
end

1
test.split(/(...)/).reject {|v| v.empty?}

그렇지 않으면 세트 사이에 공백이 포함되므로 거부가 필요합니다. 내 regex-fu는 내 머리 꼭대기에서 바로 수정하는 방법을 보지 못했습니다.


스캔 aproach는 일치하지 않는 caracteres를 잊어 버릴 것입니다. 즉 : 만약 u가 3 개의 부분에 10 개의 길이의 문자열 조각을 가지고 시도한다면, 당신은 3 개의 부분을 갖게 될 것이고 1 개의 요소는 떨어질 것이고, 당신의 aproach는 그렇게하지 않을 것입니다.
vinicius gati

1

청크 크기보다 작을 수있는 문자열의 마지막 부분을 고려하는 더 나은 솔루션 :

def chunk(inStr, sz)  
  return [inStr] if inStr.length < sz  
  m = inStr.length % sz # this is the last part of the string
  partial = (inStr.length / sz).times.collect { |i| inStr[i * sz, sz] }
  partial << inStr[-m..-1] if (m % sz != 0) # add the last part 
  partial
end

0

염두에두고있는 다른 제약이 있습니까? 그렇지 않으면 다음과 같은 간단한 일을하고 싶은 끔찍한 유혹을받을 것입니다.

[0..10].each {
   str[(i*w),w]
}

단순하고 우아하며 효율적인 것을 제외하고는 제약이 없습니다. 나는 당신의 아이디어를 좋아하지만 그것을 방법으로 번역 해 주시겠습니까? [0..10]은 아마도 약간 더 복잡해질 것입니다.
MiniQuark

str [i w ... (i + 1) * w] 대신 str [i w, w] 를 사용하도록 예제를 수정했습니다 . Tx
MiniQuark

이것은 [0..10] .each가 아니라 (1..10) .collect 여야합니다. [1..10]은 하나의 요소 (범위)로 구성된 배열입니다. (1..10)은 범위 자체입니다. 그리고 + each +는 블록에 의해 반환 된 값이 아니라 호출 된 원래 컬렉션 (이 경우 [1..10])을 반환합니다. 여기에 + map +를 원합니다.
Chuck

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.