Программирование на языке Ruby - Хэл Фултон 31 стр.


7.23. Определение числа дней в месяце

В текущей версии Ruby еще нет встроенной функции для этой цели. Но ее можно без труда написать самостоятельно:

require 'date'

def month_days(month,year=Date.today.year)

mdays = [nil,31,28,31,30,31,30,31,31,30,31.30,31]

mdays[2] = 29 if Date.leap?(year)

mdays[month]

end

days = month_days(5) # 31 (May)

days = month_days(2,2000) # 29 (February 2000)

days = month_days(2,2100) # 28 (February 2000)

7.24. Разбиение месяца на недели

Представьте, что нужно разбить месяц на недели, например чтобы напечатать календарь. Эту задачу решает приведенный ниже код. Возвращаемый массив состоит из подмассивов, по семь элементов в каждом. При этом первому элементу каждого внутреннего массива соответствует воскресенье. Начальные элементы для первой недели и конечные для второй могут быть равны nil.

def calendar(month,year)

days = month_days(month,year)

t = Time.mktime(year,month,1)

first = t.wday

list = *1..days

weeks = [[]]

week1 = 7 - first

week1.times { weeks[0] << list.shift }

nweeks = list.size/7 + 1

nweeks.times do |i|

weeks[i+1] ||= []

7.times do

break if list.empty?

weeks[i+1] << list.shift

end

end

pad_first = 7-weeks[0].size

pad_first.times { weeks[0].unshift(nil) }

pad_last = 7-weeks[0].size

pad_last.times { weeks[-1].unshift(nil) }

weeks

end

arr = calendar(12,2008) # [[nil, 1, 2, 3, 4, 5, 6],

# [7, 8, 9, 10, 11, 12, 13],

# [14, 15, 16, 17, 18, 19, 20],

# [21, 22, 23, 24, 25, 26, 27],

# [28, 29, 30, 31, nil, nil, nil]]

Чтобы было понятнее, распечатаем этот массив массивов:

def print_calendar(month,year)

weeks = calendar(month,year)

weeks.each do |wk|

wk.each do |d|

item = d.nil? ? " "*4 : " %2d " % d

print item

end

puts

end

puts

end

# Выводится:

# 1 2 3 4 5 6

# 7 8 9 10 11 12 13

# 14 15 16 17 18 19 20

# 21 22 23 24 25 26 27

# 28 29 30 31

7.25. Заключение

В этой главе мы рассмотрели класс Time, который является оберткой для функций из стандартной библиотеки языка С. Были показаны его возможности и ограничения.

Мы также узнали, зачем существуют классы Date и DateTime и какую функциональность они предоставляют. Мы научились выполнять преобразования между этими классами и добавили несколько собственных полезных методов.

На этом обсуждение даты и времени завершается. Переходим к массивам, хэшам и другим перечисляемым структурам в Ruby.

Глава 8. Массивы, хэши и другие перечисляемые структуры

Все детали должны соединяться без усилий. Помните, что механизм, который вы пытаетесь собрать, вами же был и разобран.

Если не удается соединить детали, на то должна быть причина.

Ни в коем случае не пользуйтесь молотком.

Руководство по техническому обслуживанию компании IBM (1925)

Простых переменных для практического программирования недостаточно. В любом современном языке поддерживаются более сложные виды структурированных данных и предоставляются механизмы для создания новых абстрактных типов данных.

Исторически самой первой и широко распространившейся составной структурой данных был массив. Давным-давно, еще в языке ФОРТРАН, массивы назывались индексированными переменными; сегодня они несколько видоизменились, но основная идея во всех языках одна и та же.

Относительно недавно очень популярной структурой стали хэши. Как и массив, хэш представляет собой индексированный набор данных. Но, в отличие от массива, в качестве индекса может выступать любой объект. (В Ruby, как и в большинстве других языков, элементы массива индексируются числами.)

Наконец, мы рассмотрим сам модуль Enumerable и разберемся, как он работает. И массивы, и хэши подмешивают этот модуль. То же самое может сделать и любой другой класс, которому необходима аналогичная функциональность. Но не будем забегать вперед. Начнем с массивов.

8.1. Массивы

В Ruby массивы индексируются целыми числами; индексация начинается с нуля, как в языке С. На этом, впрочем, сходство и заканчивается.

Массивы в Ruby динамические. Можно (хотя это и не обязательно) задать размер массива при создании. Но после создания он может расти без вмешательства со стороны программиста.

Массивы в Ruby неоднородны, то есть в них могут храниться данные разных типов. На самом деле в массиве хранятся только ссылки на объекты, а не объекты как таковые. Исключение составляют только непосредственные значения, например объекта класса Fixnum.

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

Наконец, класс Array в Ruby предоставляет немало полезных функций для работы с массивами: доступ, поиск, конкатенирование и т.п. В этом разделе мы изучим встроенную функциональность и расширим ее.

8.1.1. Создание и инициализация массива

Для создания массива применяется специальный метод класса []; перечисленные внутри скобок данные помещаются во вновь созданный массив. Ниже показаны три способа вызвать этот метод. (Массивы а, b и с инициализируются одинаково.)

a = Array.[] (1,2,3,4)

b = Array[1,2,3,4]

с = [1,2,3,4]

Имеется также метод класса new, который принимает 0,1 или 2 параметра. Первый параметр задает начальный размер массива (число элементов в нем). Второй определяет начальное значение каждого элемента:

d = Array.new # Создать пустой массив.

е = Array.new(3) # [nil, nil, nil]

f = Array.new(3, "blah") # ["blah", "blah", "blah"]

Обратите особое внимание на последний пример. Типичная "ошибка начинающего" - думать, что все объекты в этом массиве различны. На самом деле это три ссылки на один и тот же объект. Поэтому, если вы его измените (а не замените другим), то изменятся все элементы массива. Чтобы не попасть в эту ловушку, воспользуйтесь блоком. Блок будет вычисляться по одному разу для каждого элемента, поэтому все элементы окажутся различными объектами:

f[0].capitalize! # f равно: ["Blah", "Blah", "Blah"]

g = Array.new(3) { "blah" } # ["blah", "blah", "blah"]

g[0].capitalize! # g равно: ["Blah", "blah", "blah"]

8.1.2. Доступ к элементам массива и присваивание им значений

Получить ссылку на элемент и присвоить ему значение можно с помощью методов класса [] и []= соответственно. Каждый из них принимает один целочисленный параметр - либо пару целых чисел (начало и конец), либо диапазон. Отрицательные индексы отсчитываются от конца массива, начиная с -1.

Специальный метод экземпляра at реализует простейший случай получения ссылки на элемент. Поскольку он может принимать только один целочисленный параметр, то работает чуть быстрее.

a = [1, 2, 3, 4, 5, 6]

b = а[0] # 1

с = a.at(0) # 1

d = а[-2] # 5

е = a.at(-2) # 5

f = а[9] # nil

g = a.at(9) # nil

h = a[3,3] # [4, 5, 6]

i = a[2..4] # [3, 4, 5]

j = a[2...4] # [3, 4]

a[1] = 8 # [1, 8, 3, 4, 5, 6]

a[1,3] = [10, 20, 30] # [1, 10, 20, 30, 5, 6]

a[0..3] = [2, 4, 6, 8] # [2, 4, 6, 8, 5, 6]

a[-1] = 12 # [2, 4, 6, 8, 5, 12]

В следующем примере ссылка на элемент, расположенный за концом массива, приводит к росту массива. Отметим, что подмассив можно заменить другим массивом, содержащим больше элементов, чем было. В этом случае массив также автоматически вырастет.

k = [2, 4, 6, 8, 10]

k[1..2] = [3, 3, 3] # [2, 3, 3, 3, 8, 10]

k[7] = 99 # [2, 3, 3, 3, 8, 10, nil, 99]

Наконец, если одному элементу присвоить в качестве значения массив, то на место этого элемента будет вставлен вложенный массив (в отличие от присваивания диапазону):

m = [1, 3, 5, 7, 9]

m[2] = [20, 30] # [1,3, [20, 30], 7, 9]

# С другой стороны... m = [1, 3, 5, 7, 9]

m[2..2] = [20, 30] # [1, 3, 20, 30, 7, 9]

Метод slice - синоним метода []:

x = [0, 2, 4, 6, 8, 10, 12]

а = x.slice(2) # 4

b = x.slice(2,4) # [4, 6, 8, 10]

с = x.slice(2..4) # [4, 6, 8]

Специальные методы first и last возвращают первый и последний элемент массива соответственно. Если массив пуст, они возвращают nil:

x = %w[alpha beta gamma delta epsilon]

a = x.first # "alpha"

b = x.last # "epsilon"

Мы уже видели ранее, что иногда ссылка на элементы может возвращать целый подмассив. Но существуют и другие способы обратиться к нескольким элементам.

Метод values_at принимает список индексов и возвращает массив, содержащий только указанные элементы. Его можно использовать в тех случаях, когда диапазон не годится (так как нужные элементы находятся не в соседних позициях).

В более ранних версиях Ruby метод values_at назывался indices (синоним indexes). Теперь эти названия не используются.

x = [10, 20, 30, 40, 50, 60]

y = x.values_at(0, 1, 4) # [10, 20, 50]

z = x.values_at(0..2,5) # [10, 20, 30, 60]

8.1.3. Определение размера массива

Метод length и его синоним size возвращают число элементов в массиве. (Как всегда, эта величина на единицу больше индекса последнего элемента.)

x = ["а", "b", "с", "d"]

а = x.length # 4

b = x.size # 4

Метод nitems отличается от предыдущих тем, что не учитывает элементы равные nil:

у = [1, 2, nil, nil, 3, 4]

с = у.size # 6

d = у.length # 6

е = y.nitems # 4

8.1.4. Сравнение массивов

При сравнении массивов возможны неожиданности - будьте осторожны!

Для сравнения массивов служит метод экземпляра <=>. Он работает так же, как в других контекстах, то есть возвращает -1 (меньше), 0 (равно) или 1 (больше). Методы == и != опираются на реализацию метода <=>.

Массивы сравниваются поэлементно; первая же пара несовпадающих элементов определяет результат всего сравнения. (Предпочтение отдается левее расположенным элементам, как при сравнении двух длинных целых чисел "на глазок", когда мы сравниваем по одной цифре за раз.)

а = [1, 2, 3, 9, 9]

b = [1, 2, 4, 1, 1]

с = а <=> b # -1 (то есть а < b)

Если все элементы равны, то массивы считаются равными. Если один массив длиннее другого и все элементы вплоть до длины более короткого массива равны, то более длинный массив считается большим.

d = [1, 2, 3]

е = [1, 2, 3, 4]

f = [1, 2, 3]

if d < е # false

puts "d меньше e"

end

if d == f

puts "d равно f" # Печатается "d равно f"

end

Поскольку класс Array не подмешивает модуль Comparable, то обычные операторы сравнения <, >, <= и >= для массивов не определены. Но при желании их легко определить самостоятельно:

class Array

def <(other)

(self <=> other) == -1

end

def <=(other)

(self < other) or (self == other)

end

def >(other)

(self <=> other) == 1

end

def >=(other)

(self > other) or (self == other)

end

end

Впрочем, было бы проще включить модуль Comparable:

class Array

include Comparable

end

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

if а < b

print "а < b" # Печатается "а < b"

else

print "а >= b"

end

if d < e

puts "d < e" # Печатается "d < e"

end

Может статься, что при сравнении массивов мы столкнемся с необходимостью сравнивать два элемента, для которых оператор <=> не определен или не имеет смысла. Следующий код приводит к возбуждению исключения (TypeError) во время выполнения, так как сравнение 3 <=> "x" лишено смысла:

g = [1, 2, 3]

h = [1, 2, "x"]

if g < h # Ошибка!

puts "g < h" # Ничего не выводится.

end

Если и это вас не смущает, то добавим, что сравнение на равенство и неравенство этом случае работает. Объясняется это тем, что объекты разных типов считаются неравными, хотя мы и не можем сказать, какой из них больше.

if g != h # Здесь ошибка не возникает.

puts "g != h" # Печатается "g != h"

end

Наконец, не исключено, что два массива, содержащих несравнимые типы данных, все равно можно сравнить с помощью операторов < и >. В примере ниже мы получаем определенный результат еще до того, как натолкнемся на несравнимые элементы:

i = [1, 2, 3]

j = [1, 2, 3, "x"]

if i < j # Здесь ошибка не возникает.

puts "i < j" # Печатается "i < j"

end

8.1.5. Сортировка массива

Самый простой способ отсортировать массив - воспользоваться встроенным методом sort:

words = %w(the quick brown fox)

list = words.sort # ["brown", "fox", "quick", "the"]

# Или отсортировать на месте:

words.sort! # ["brown", "fox", "quick", "the"]

Здесь предполагается, что все элементы массива сравнимы между собой. При сортировке неоднородного массива, например [1, 2, "tHRee", 4], обычно возникает ошибка.

В подобных случаях можно воспользоваться также блочной формой того же метода. Ниже предполагается, что у каждого элемента есть хотя бы метод to_s (преобразующий его в строку):

а = [1, 2, "three", "four", 5, 6]

b = a.sort {|x,y| x.to_s <=> y.to_s}

# b равно [1, 2, 5, 6, "four", "three"]

Назад Дальше