Более общее решение дает стандартная библиотека CSV. Есть также усовершенствованный инструмент под названием FasterCSV. Поищите его в сети, он не входит в стандартный дистрибутив Ruby.
2.24. Преобразование строки в число (десятичное или иное)
Есть два основных способа преобразовать строку в число: методы Integer и Float модуля Kernel и методы to_i и to_f класса String. (Имена, начинающиеся с прописной буквы, например Integer, обычно резервируются для специальных функций преобразования.)
Простой случай тривиален, следующие два предложения эквивалентны:
x = "123".to_i # 123
y = Integer("123") # 123
Но если в строке хранится не число, то поведение этих методов различается:
x = junk".to_i # Молча возвращает 0.
y = Integer("junk") # Ошибка.
Метод to_i прекращает преобразование, как только встречает первый символ, не являющийся цифрой, а метод Integer в этом случае возбуждает исключение:
x = "123junk".to_i # 123
y = Integer("123junk") # Ошибка.
Оба метода допускают наличие пропусков в начале и в конце строки:
x = " 123 ".to_i # 123
y = Integer(" 123 ") # 123
Преобразование строки в число с плавающей точкой работает аналогично:
x = "3.1416".to_f # 3.1416
y = Float("2.718") # 2.718
Оба метода понимают научную нотацию:
x = Float("6.02е23") # 6.02е23
y = "2.9979246е5".to_f # 299792.46
Методы to_i и Integer также по-разному относятся к системе счисления. По умолчанию, естественно, подразумевается система по основанию 10, но другие тоже допускаются (это справедливо и для чисел с плавающей точкой).
Говоря о преобразовании из одной системы счисления в другую, мы всегда имеем в виду строки. Ведь целое число неизменно хранится в двоичном виде.
Следовательно, преобразование системы счисления - это всегда преобразование одной строки в другую. Здесь мы рассмотрим преобразование из строки (обратное преобразование рассматривается в разделах 5.18 и 5.5).
Числу в тексте программы может предшествовать префикс, обозначающий основание системы счисления. Префикс 0b обозначает двоичное число, 0 - восьмеричное, а 0x - шестнадцатеричное.
Метод Integer такие префиксы понимает, а метод to_i - нет:
x = Integer("0b111") # Двоичное - возвращает 7.
y = Integer("0111") # Восьмеричное - возвращает 73.
z = Integer("0x111") # Шестнадцатеричное - возвращает 291.
x = "0b111".to_i # 0
y = "0111".to_i # 0
z = "0x111".to_i # 0
Однако у метода to_i есть необязательный второй параметр для указания основания. Обычно применяют только четыре основания: 2, 8, 10 (по умолчанию) и 16. Впрочем, префиксы не распознаются даже при определении основания.
x = "111".to_i(2) # 7
y = "111".to_i(8) # Восьмеричное - возвращает 73.
z = "111".to_i(16) # Шестнадцатеричное - возвращает 291.
x = "0b111".to_i # 0
y = "0111".to_i # 0
z = "0x111".to_i # 0
Из-за "стандартного" поведения этих методов цифры, недопустимые при данном основании, обрабатываются по-разному:
x = "12389".to_i(8) # 123 (8 игнорируется).
y = Integer("012389") # Ошибка (8 недопустима).
Хотя полезность этого и сомнительна, метод to_i понимает основания вплоть до 36, когда в представлении числа допустимы все буквы латинского алфавита. (Возможно, это напомнило вам о base64-кодировании; дополнительную информацию по этому поводу вы найдете в разделе 2.37.)
x = "123".to_i(5) # 66
y = "ruby".to_i (36) # 1299022
Для преобразования символьной строки в число можно также воспользоваться методом scanf из стандартной библиотеки, которая добавляет его в модуль Kernel, а также классы IO и String:
str = "234 234 234"
x, y, z = str.scanf("%d %o %x") # 234, 156, 564
Метод scanf реализует всю имеющую смысл функциональность стандартных функций scanf, sscanf и fscanf из библиотеки языка С. Но строки, представляющие двоичные числа, он не обрабатывает.
2.25. Кодирование и декодирование строк в кодировке rot13
Rot13 - наверное, самый слабый из известных человечеству шифров. Исторически он просто препятствовал "случайному" прочтению текста. Он часто встречается в конференциях Usenet; например, так можно закодировать потенциально обидную шутку или сценарий фильма "Звездные войны. Эпизод 13" накануне премьеры. Принцип кодирования состоит в смещении символов относительно начала алфавита (латинского) на 13: А превращается в N, В - в О и т.д. Строчные буквы смещаются на ту же величину; цифры, знаки препинания и прочие символы игнорируются. Поскольку 13 - это ровно половина от 26 (число букв в латинском алфавите), то функция является обратной самой себе, то есть ее повторное применение восстанавливает исходный текст.
Ниже приведена реализация этого метода, добавленного в класс String, никаких особых комментариев она не требует:
class String
def rot13
self.tr("A-Ma-mN-Zn-z","N-Zn-zA-Ma-m")
end
end
joke = "Y2K bug"
joke13 = joke.rot13 # "L2X oht"
episode2 = "Fcbvyre: Naanxva qbrfa'g trg xvyyrq."
puts episode2.rot13
2.26. Шифрование строк
Иногда нежелательно, чтобы строки можно было легко распознать. Например, пароли не следует хранить в открытом виде, какими бы ограничительными ни были права доступа к файлу.
В стандартном методе crypt применяется стандартная функция с тем же именем для шифрования строки по алгоритму DES. Она принимает в качестве параметра "затравку" (ее назначение то же, что у затравки генератора случайных чисел). На платформах, отличных от UNIX, параметр может быть иным.
Ниже показано тривиальное приложение, которое запрашивает пароль, знакомый любителям Толкиена:
coded = "hfCghHIE5LAM."
puts "Говори, друг, и жми Enter!"
print "Пароль: " password = gets.chop
if password.crypt("hf") == coded
puts "Добро пожаловать!"
else
puts "Кто ты, орк?"
end
Стоит отметить, что на такое шифрование не стоит полагаться в серверных Web-приложениях, поскольку пароль, введенный в поле формы, все равно передаётся по сети в открытом виде. В таких случаях проще всего воспользоваться протоколом SSL (Secure Sockets Layer). Разумеется, никто не запрещает пользоваться шифрованием на сервере, но по другой причине - чтобы защитить пароль в хранилище, а не во время передачи по сети.
2.27. Сжатие строк
Для сжатия строк и файлов применяется библиотека Zlib.
Зачем может понадобиться сжимать строки? Возможно, чтобы ускорить ввод/вывод из базы данных, оптимизировать использование сети или усложнить распознавание строк.
В классах Deflate и Inflate имеются методы класса deflate и inflate соответственно. У метода deflate (он выполняет сжатие) есть дополнительный параметр, задающий режим сжатия. Он определяет компромисс между качеством сжатия и скоростью. Если значение равно BEST_COMPRESSION, то строка сжимается максимально, но это занимает сравнительно много времени. Значение BEST_SPEED задает максимальную скорость, но при этом строка сжимается хуже. Подразумеваемое по умолчанию значение DEFAULT_COMPRESSION выбирает компромиссный режим.
require 'zlib'
include Zlib
long_string = ("abcde"*71 + "defghi"*79 + "ghijkl"*113)*371
# long_string состоит из 559097 символов.
s1 = Deflate.deflate(long_string,BEST_SPEED) # 4188 символов.
s3 = Deflate.deflate(long_string) # 3568 символов
s2 = Deflate.deflate(long_string,BEST_COMPRESSION) # 2120 символов
Неформальные эксперименты показывают, что скорость отличается примерно в два раза, а плотность сжатия - в обратной пропорции на ту же величину. И скорость, и плотность сильно зависят от состава строки. Разумеется, на скорость влияет и имеющееся оборудование.
Имейте в виду, что существует пороговое значение длины строки. Если строка короче, то сжимать ее практически бесполезно (если только вы не хотите сделать ее нечитаемой). В этом случае неизбежные накладные расходы могут даже привести к тому, что сжатая строка окажется длиннее исходной.
2.28. Подсчет числа символов в строке
Метод count подсчитывает число вхождений в строку символов из заданного набора:
s1 = "abracadabra"
a = s1.count("с") # 1
b = s1.count("bdr") # 5
Строковый параметр ведет себя как простое регулярное выражение. Если он начинается с символа ^, то берется дополнение к списку:
c = s1.count("^а") # 6
d = s1.count ("^bdr") # 6
Дефис обозначает диапазон символов:
e = s1.count("a-d") # 9
f = s1.count("^a-d") # 2
2.29. Обращение строки
Для обращения строки служит метод reverse (или его вариант для обращения "на месте" reverse!):
s1 = "Star Trek"
s2 = s1.reverse # "kerT ratS"
si.reverse! # si теперь равно "kerT ratS"
Пусть требуется обратить порядок слов (а не символов). Тогда можно сначала воспользоваться методом String#split, который вернет массив слов. В классе Array тоже есть метод reverse, поэтому можно обратить массив, а затем с помощью метода join объединить слова в новую строку:
phrase = "Now here's a sentence"
phrase.split(" ").reverse.join(" ")
# "sentence a here's Now"
2.30. Удаление дубликатов
Цепочки повторяющихся символов можно сжать до одного методом squeeze:
s1 = "bookkeeper"
s2 = s1.squeeze # "bokeper"
s3 = "Hello..."
s4 = s3.squeeze # "Helo."
Если указан параметр, то будут удаляться только дубликаты заданных в нем символов:
s5 = s3.squeeze(".") # "Hello."
Этот параметр подчиняется тем же правилам, что и параметр метода count (см. раздел 2.28), то есть допускаются дефис и символ ^. Имеется также метод squeeze!.
2.31. Удаление заданных символов
Метод delete удаляет из строки те символы, которые включены в список, переданный в качестве параметра:
s1 = "To be, or not to be"
s2 = s1.delete("b") # "To e, or not to e"
s3 = "Veni, vidi, vici!"
s4 = s3.delete(",!") # "Veni vidi vici"
Этот параметр подчиняется тем же правилам, что и параметр метода count (см. раздел 2.28), то есть допускаются символы - (дефис) и ^ (каре). Имеется также метод delete!.
2.32. Печать специальных символов
Метод dump позволяет получить графическое представление символов, которые обычно не печатаются вовсе или вызывают побочные эффекты:
s1 = "Внимание" << 7 << 7 << 7 # Добавлено три символа ASCII BEL.
puts s1.dump # Печатается: Внимание\007\007\007
s2 = "abc\t\tdef\tghi\n\n"
puts s2.dump # Печатается: abc\t\tdef\tghi\n\n
s3 = "Двойная кавычка: \""
puts s3.dump # Печатается: Двойная кавычка: \"
При стандартном значении переменной $KCODE метод dump дает такой же эффект, как вызов метода inspect для строки. Переменная $KCODE рассматривается в главе 4.
2.33. Генерирование последовательности строк
Изредка бывает необходимо получить "следующую" строку. Так, следующей для строки "aaa" будет строка "aab" (затем "aac", "aad" и так далее). В Ruby для этой цели есть метод succ:
droid = "R2D2"
improved = droid.succ # "R2D3"
pill = "Vitamin B"
pill2 = pill.succ # "Vitamin C"
He рекомендуется применять этот метод, если точно не известно, что начальное значение предсказуемо и разумно. Если начать с какой-нибудь экзотической строки, то рано или поздно вы получите странный результат.
Существует также метод upto, который в цикле вызывает succ, пока не будет достигнуто конечное значение:
"Files, A".upto "Files, X" do | letter |
puts "Opening: #{letter}"
end
# Выводится 24 строки.
Еще раз подчеркнем, что эта возможность используется редко, да и то на ваш страх и риск. Кстати, метода, возвращающего "предшествующую" строку, не существует.
2.34. Вычисление 32-разрядного CRC
Контрольный код циклической избыточности (Cyclic Redundancy Checksum, CRC) - хорошо известный способ получить "сигнатуру" файла или произвольного массива байтов. CRC обладает тем свойством, что вероятность получения одинакового кода для разных входных данных равна 1/2**N, где N - число битов результата (чаще всего 32).
Вычислить его позволяет библиотека zlib, написанная Уэно Кацухиро (Ueno Katsuhiro). Метод crc32 вычисляет CRC для строки, переданной в качестве параметра.
require 'zlib'
include Zlib
crc = crc32("Hello") # 4157704578
crc = crc32(" world!",crc) # 461707669
crc = crc32("Hello world!") # 461707669 (то же, что и выше)
В качестве необязательного второго параметра можно передать ранее вычисленный CRC. Результат получится такой, как если бы конкатенировать обе строки и вычислить CRC для объединения. Это полезно, например, когда нужно вычислить CRC файла настолько большого, что прочитать его можно только по частям.
2.35. Вычисление МD5-свертки строки
Алгоритм MD5 вырабатывает 128-разрядный цифровой отпечаток или дайджест сообщения произвольной длины. Это разновидность свертки, то есть функция шифрования односторонняя, так что восстановить исходное сообщение по дайджесту невозможно. Для Ruby имеется расширение, реализующее MD5; интересующиеся могут найти его в каталоге ext/md5 стандартного дистрибутива.
Для создания нового объекта MD5 есть два эквивалентных метода класса: new и md5:
require 'md5'
hash = MD5.md5
hash = MD5.new
Есть также четыре метода экземпляра: clone, digest, hexdigest и update. Метод clone просто копирует существующий объект, а метод update добавляет новые данные к объекту:
hash.update("Дополнительная информация...")
Можно создать объект и передать ему данные за одну операцию:
secret = MD5.new("Секретные данные")
Если задан строковый аргумент, он добавляется к объекту путем обращения к методу update. Повторные обращения эквивалентны одному вызову с конкатенированными аргументами:
# Эти два предложения:
сryptic.update("Данные...")
cryptic.update(" еще данные.")
# ... эквивалентны одному такому:
cryptic.update("Данные... еще данные.")
Метод digest возвращает 16-байтовую двоичную строку, содержащую 128-разрядный дайджест.
Но наиболее полезен метод hexdigest, который возвращает дайджест в виде строки в коде ASCII, состоящей из 32 шестнадцатеричных символов, соответствующих 16 байтам. Он эквивалентен следующему коду:
def hexdigest
ret = ''
digest.each_byte {|i| ret << sprintf{'%02x' , i) }