Перебор данных

Итерация данных по индексу

Последнее обновление: 17.09.2023

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

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

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

/* API модуля - функция принимает индекс элемента и возвращает некоторые данные */
int getX(int index);


/* Реализация API модуля */
#define MAX_ELEMENTS 4
// структура, которая представляет данные
struct Data
{
    int x;
    int y;
};
// сами данные
static struct Data elements[MAX_ELEMENTS]={{1,2},{3,4},{5,6},{7,8}};
// функция для доступа к данным по индексу
int getX(int index)
{
  return elements[index].x;
}


/* Код клиента, который использует API */
int x1 = getX(1);
int x2 = getX(2);

Благодаря функции-абстракции над обращением к элементу по индексу мы можем предоставить клиенту только те данные, которые ему нужны и к которым он может иметь доступ. А если в будущем структура данных измениться, на стороне клиента код не изменится. Данный паттерн еще называют External Iterator или внешний итератор.

Рассмотрим небольшой пример. Пусть в заголовочном файле account.h у нас определен следующий API:

#define ACCOUNT_NUMBER 200
char* getUserName(size_t index);

Константа ACCOUNT_NUMBER представляет максимальное количество данных - учетных данных пользователей, а функция getUserName возвращает имя пользователя по определенному индексу.

В файле account.c определим реализацию API:

#include <stdio.h>
#include "account.h"

#define NAME_SIZE 20
#define PASSWORD_SIZE 20
// структура данных
struct ACCOUNT
{
  char login[NAME_SIZE];
  char password[PASSWORD_SIZE];
};
// типовые данные
static struct ACCOUNT accounts[ACCOUNT_NUMBER] = 
{
  {"Tom", "qwerty"}, {"Bob", "123456"}, 
  {"Sam", "6589967"}, {"Tim", "eruiyiu"}
};

// возвращаем логин
char* getUserName(size_t index)
{
  return accounts[index].login;
}

Здесь данные представлены структурой Account, которая хранит условно имя пользователя/логин и его пароль. И также определен типовой набор данных - массив accounts. Пароль в этой структуре может представлять такие данные, которые не должны быть видны извне, поэтому определяем функционал только для возвращения логина с помщью функции getUserName, которая получает индекс пользователя в массиве accounts.

Определим основной файл программы - app.c, в котором будем обращаться к данным:

#include <stdio.h>
#include "account.h"

int main(void)
{
  for(size_t i=0; i < ACCOUNT_NUMBER; i++)
  {
    char* username = getUserName(i);
    if(username[0] == 'T')
    {
      printf("%s\n", username);
    }
  }
}

Здесь проходим по всем элементам и получаем имя каждого пользователя с помощью функции getUserName. Для примера выводим на консоль все имена пользователей, которые начинаются на "T".

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

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850