Коллекции
Предпосылки: Циклы (while), Функции (определение, вызов, параметры, возврат значения).
Функция final_total(subtotal, discount, tax_rate) хорошо считает один заказ. Но если заказов тридцать, неудобно держать subtotal_1, subtotal_2, subtotal_3 и так далее. Цикл не умеет проходить по набору отдельных имён. Нужна структура, которая хранит несколько значений как одно целое.
Массив
Массив (array — «ряд», «набор») — это упорядоченная последовательность значений, к каждому из которых можно обратиться по номеру. Номер называется индексом и начинается с нуля:
order_totals = [1155.0, 890.0, 1320.0]
puts order_totals[0]
puts order_totals[2]Теперь три значения лежат не в трёх переменных, а в одной структуре. По ней можно пройти циклом:
i = 0
while i < 3
puts order_totals[i]
i = i + 1
endЭто снимает часть нагрузки с памяти человека: вместо набора отдельных имён остаётся одна переменная и индекс.
В C массив объявляется с фиксированным размером:
int order_totals[3] = {1155, 890, 1320};Если элементов станет больше, код нужно менять.
Динамический массив
В реальной программе количество элементов заранее неизвестно. Сегодня заказов три, завтра тридцать, потом триста.
В Ruby массивы динамические: можно начать с пустого и добавлять элементы по мере необходимости:
order_totals = []
order_totals << 1155.0
order_totals << 890.0
order_totals << 1320.0Когда места не хватает, массив расширяется. Как именно он растёт внутри, разбирается в динамическом массиве. Обратите внимание: присваивание order_totals[100] = 0 также может создать «дырки». Здесь мы используем <<, чтобы показать обычный путь добавления в конец.
Итерация
Итерация (от лат. iterare — «повторять») — один проход по элементам коллекции. Коллекция и цикл вместе позволяют обрабатывать много значений одним правилом:
order_totals = [1155.0, 890.0, 1320.0]
sum = 0
i = 0
while i < 3
sum = sum + order_totals[i]
i = i + 1
end
puts sumОдин цикл вместо трёх почти одинаковых строк. Позже мы увидим и более короткие способы обхода коллекций.
Хеш
Массив удобен, когда важен порядок: первый элемент, второй, третий. Но часть данных естественнее искать не по номеру, а по имени поля.
Если хранить заказ как два параллельных массива, связь между ними приходится держать в голове:
fields = ["subtotal", "discount", "tax_rate"]
values = [1200, 150, 0.10]Хеш (hash — от англ. «нарезать, перемешать»: ключ превращается во внутренний адрес) хранит пары ключ -> значение:
order = {
"subtotal" => 1200,
"discount" => 150,
"tax_rate" => 0.10
}
puts order["subtotal"]
puts order["discount"]Теперь структура сама показывает, что именно хранится внутри. Не нужно помнить, что values[1] — это скидка. Как хеш устроен внутри и почему поиск по ключу быстрее перебора — в хеш-таблице.
Вложенные структуры
Коллекции можно комбинировать. Например, массив хешей:
def final_total(subtotal, discount, tax_rate)
taxable = subtotal - discount
taxable + (taxable * tax_rate)
end
orders = [
{ "customer" => "Alice", "subtotal" => 1200, "discount" => 150, "tax_rate" => 0.10 },
{ "customer" => "Bob", "subtotal" => 900, "discount" => 0, "tax_rate" => 0.10 },
{ "customer" => "Charlie", "subtotal" => 1400, "discount" => 200, "tax_rate" => 0.10 }
]
i = 0
while i < orders.length # .length — количество элементов
order = orders[i]
total = final_total(order["subtotal"], order["discount"], order["tax_rate"])
puts order["customer"]
puts total
i = i + 1
endФункция по-прежнему считает один заказ, а коллекция хранит сразу много заказов. Так данные уже можно держать группой, передавать в функции и обрабатывать в цикле.
Следующий вопрос не про форму данных, а про то, где эти массивы и хеши вообще живут в памяти. Почему иногда copy = orders не делает независимую копию — и изменение через copy меняет и orders?
Sources
- Thomas, D. et al., 2023, Programming Ruby 3.3. Pragmatic Bookshelf.
- Knuth, D., 1998, The Art of Computer Programming, Vol. 3: Sorting and Searching. Addison-Wesley.