Как эффективно разделить очень длинную (несколько миллионов символов) строку после каждого n-го символа в Ruby?

#arrays #ruby #string #split

#массивы #ruby #строка #разделить

Вопрос:

Допустим, у меня есть строка

 string = "hellohellohey"
  

Я хочу разделить ее на каждый 2-й символ, чтобы это выглядело так

 string = ["he","ll","oh","el","lo","he","y"]
  

Я пытался использовать scan(/.{2}/) метод, но если элемент массива не может быть разделен на 2, он не работает.

Редактировать: Необходимо сообщить вам, что 2-символьная вещь была примером. Я делаю что-то большое, поэтому я буду разделять его каждые 8 миллионов символов. Поэтому разделение ее на отдельные символы и использование each_slice здесь не работает. Это просто замораживает мой ноутбук.

Комментарии:

1. Попробуйте использовать .{1,2} для сопоставления 1 или 2 раза.

Ответ №1:

При обработке (очень) больших строк может быть полезно обернуть их в StringIO . Это обеспечивает эффективный файловый доступ к строке.

Вы можете, например, прочитать все n символов через StringIO#each :

 string = "hellohellohey"
string_io = StringIO.new(string)

string_io.each(5) do |substring|
  p substring
end
  

Вывод:

 "hello"
"hello"
"hey"
  

Комментарии:

1. Спасибо, это работает. Однако, это говорит мне, что это перечислитель, когда я пытаюсь подсчитать количество элементов в массиве. Редактировать: Неважно, я использовал .to_a

2. @tequila это ожидаемо. С помощью блока StringIO#each выводятся символы / подстроки. Без блока он возвращает перечислитель. Я предположил, что вы хотите пропустить создание массива и работать с подстроками.

Ответ №2:

Точка соответствует любому символу, кроме новой строки. Вы пытаетесь сопоставить любой символ 2 раза, и это не будет соответствовать последнему символу, если строка нечетная по длине.

Вы могли бы использовать квантификатор {1,2} , который является жадным, поэтому он сначала пытается сопоставить 2 раза.

 .{1,2}
  

Смотрите демонстрацию

Если вы хотите сопоставлять только символы нижнего регистра от a до z, вы также можете использовать [a-z] вместо точки.

Комментарии:

1. Также работает: /..?/ это всего лишь еще один символ. {} Версия, которая у вас есть здесь, более гибкая, так как вы могли бы сделать 2,5 или 9,300 или все, что вам нужно.

2. Кроме того, вы, вероятно, захотите добавить m модификатор, чтобы он . соответствовал любому символу (по умолчанию он не соответствует новой строке).

Ответ №3:

Вы можете объединить несколько методов, как показано ниже:

 string = 'hellohellohey'
string.chars.each_slice(2).map { |s| s.join }
# => ["he", "ll", "oh", "el", "lo", "he", "y"]
  

#chars преобразует строку в массив символов.

#each_slice разбивает массив на необходимое количество частей.

Обновление — без промежуточного / временного массива

Согласно комментариям, благодаря @Cary Swoveland, временного массива можно избежать следующим образом.

 string.each_char.each_slice(2).map { |s| s.join }
  

#each_char предоставляет перечислитель каждого символа.

Комментарии:

1. Небольшая точка, настолько маленькая, что она едва различима: я предлагаю вам использовать each_char вместо chars , когда за ним следует Enumerable метод, чтобы избежать создания временного массива string.chars .

2. Я понял вашу точку зрения @CarySwoveland, я начал с того же, но затем я не смог собрать 2 / требуемое количество символов символов после each_char . можете ли вы как-то связать ее в цепочку?

3. string.each_char.each_slice(2).map { |s| s.join } . Обратите внимание на возвращаемое значение string.each_char.each_slice(2) #=> #<Enumerator: #<Enumerator: "hellohellohey":each_char>:each_slice(2)> , своего рода составной перечислитель .

4. Помогает ли разделение строки, скажем, на 10000 символов за раз? string[0..10000]