Как лучше всего разделить строку на куски заданной длины в Ruby?

Я искал элегантный и эффективный способ разбить строку на подстроки заданной длины в 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?

Вы бы порекомендовали лучшее решение?

Изменить

Спасибо Джереми Рутену за это элегантное и эффективное решение: [редактировать: НЕ эффективно!]

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

Изменить

Решению string.scan требуется около 60 секунд, чтобы разбить 512k на 1k фрагментов 10000 раз, по сравнению с исходным решением на основе срезов, которое занимает всего 2,4 секунды.


person MiniQuark    schedule 16.04.2009    source источник
comment
Ваше исходное решение максимально эффективно и элегантно: нет необходимости проверять каждый символ строки, чтобы знать, где его разрезать, и нет необходимости превращать все это в массив, а затем обратно.   -  person android.weasel    schedule 11.06.2019


Ответы (9)


Используйте 1_:

>> '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"]
person Paige Ruten    schedule 16.04.2009
comment
Хорошо, теперь это отлично! Я знал, что должен быть способ получше. Большое спасибо Джереми Рутен. - person MiniQuark; 16.04.2009
comment
def чанк (строка, размер); string.scan (/. {1, # {size}} /); конец - person MiniQuark; 16.04.2009
comment
Вау, теперь я чувствую себя глупо. Я даже не удосужился проверить, как работает сканирование. - person Chuck; 16.04.2009
comment
Будьте осторожны с этим раствором; это регулярное выражение, и бит /. в нем означает, что он будет включать все символы, ЗА ИСКЛЮЧЕНИЕМ новой строки \n. Если вы хотите включить новые строки, используйте string.scan(/.{4}/m) - person professormeowingtons; 25.07.2013
comment
Какое умное решение! Мне нравятся регулярные выражения, но я бы не стал использовать квантификатор для этой цели. Спасибо Джереми Рутен - person Cec; 13.05.2016
comment
Это эффективно? - person juliangonzalez; 08.06.2017
comment
Не будет работать с большими размерами. too big number for repeat range: /.{1,240000}/m - person 2rs2ts; 30.10.2019

Вот еще один способ сделать это:

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

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

person Jason    schedule 04.02.2011
comment
Или: "abcdefghijklmnopqrstuvwxyz".chars.each_slice(3).map(&:join) - person Finbarr; 17.11.2012
comment
Мне нравится этот, потому что он работает со строками, содержащими символы новой строки. - person Steve Davis; 16.08.2013
comment
Это должно быть приемлемое решение. Использование сканирования может привести к потере последнего токена, если длина не соответствует шаблону. - person count0; 26.10.2016

Я думаю, что это наиболее эффективное решение, если вы знаете, что ваша строка кратна размеру блока

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
person davispuh    schedule 26.07.2015
comment
Ваша строка не должна быть кратной размеру блока, если вы замените string.length / size на (string.length + size - 1) / size - этот шаблон распространен в коде C, который имеет дело с целочисленным усечением. - person nitrogen; 19.08.2015

Вот еще одно решение для немного другого случая, когда при обработке больших строк нет необходимости хранить все фрагменты одновременно. Таким образом, он сохраняет по одному фрагменту за раз и работает намного быстрее, чем нарезка строк:

io = StringIO.new(string)
until io.eof?
  chunk = io.read(chunk_size)
  do_something(chunk)
end
person prcu    schedule 20.09.2018
comment
Для очень больших строк это безусловно лучший способ сделать это. Это позволит избежать чтения всей строки в память и получения Errno::EINVAL ошибок, таких как Invalid argument @ io_fread и Invalid argument @ io_write. - person Joshua Pinter; 25.10.2020

Я провел небольшой тест, который разбил около 593 МБ данных на 18991 фрагмент по 32 КБ. Ваша версия slice + map работала не менее 15 минут с использованием 100% ЦП, прежде чем я нажал ctrl + C. Эта версия с использованием String # распаковала за 3,6 секунды:

def chunk(string, size)
  string.unpack("a#{size}" * (string.size/size.to_f).ceil)
end
person Per Wigren    schedule 20.02.2020

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

Отклонение необходимо, потому что в противном случае между наборами остается пробел. Мое регулярное выражение не совсем готово к тому, чтобы сразу же понять, как это исправить.

person Chuck    schedule 16.04.2009
comment
Подход сканирования забудет о несовпадающих признаках, то есть: если вы попробуете с отрезком строки длиной 10 на 3 части, у вас будет 3 части, и 1 элемент будет отброшен, ваш подход этого не делает, так что лучше. - person vinicius gati; 24.01.2014

Лучшее решение, которое учитывает последнюю часть строки, которая может быть меньше размера блока:

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
person kirkytullins    schedule 06.02.2019

Вы имеете в виду еще какие-то ограничения? В противном случае у меня возникло бы ужасное искушение сделать что-нибудь простое, например

[0..10].each {
   str[(i*w),w]
}
person Charlie Martin    schedule 16.04.2009
comment
У меня действительно нет никаких ограничений, кроме того, что есть что-то простое, элегантное и эффективное. Мне нравится ваша идея, но не могли бы вы воплотить ее в методе? [0..10], вероятно, станет немного сложнее. - person MiniQuark; 16.04.2009
comment
Я исправил свой пример, чтобы использовать str [i w, w] вместо str [i w ... (i + 1) * w]. Tx - person MiniQuark; 16.04.2009
comment
Это должно быть (1..10) .collect, а не [0..10] .each. [1..10] - массив, состоящий из одного элемента - диапазона. (1..10) - это сам диапазон. И + each + возвращает исходную коллекцию, которую он вызвал (в данном случае [1..10]), а не значения, возвращаемые блоком. Мы хотим + карту + здесь. - person Chuck; 16.04.2009

Просто text.scan(/.{1,4}/m) решает проблему

person Vyacheslav    schedule 08.11.2020