Помните собирали такие задания. Но то для серьезных вещей, а у меня более мелкое, близкое нам — кодинг.
Придумать не получалось — а вот когда затребовала среда — оно как бы само нашлось. Просто взял из рабочего проекта задание, которое можно выделить и реализовать без какой-либо связи с проектом.
Почему это не тривиально? Т.к. либо слишком простое придумывается (на что есть готовый ответ в сети), либо слишком сложное, для которого нужно погружать в детали проекта. А тут как бы вещь в себе, но при этом ответа в сети готового не найдете. Код компилируется, не требует зависимостей от библиотек.
Итак, полный текст, который можно скопировать и дать GPT:
| Для GPT |
| Язык Dart. Требуется реализовать методы retrieveItems и getItemFromCache менеджера кеширования — класс ObjectDdCacheManager.
Условия:
1. Учитывать FetchDataPolicy — если never, то запрос к API не выполняем, возвращаем то что есть в кеше (или ничего). Если onEmptyCache — то делаем запрос к API только в случае, если в кеше нет данных. Если always — то всегда делаем запрос к API и обновляем данные кеша.
2. Учитывать CacheUpdateMode. Если entities — то только заменяем сущности. Если box — то удаляем старую коробку целиком со всеми объектами.
3. Учитывать фильтр Filter. Если установлен, то запрос к базе делаем через getBeyKey (вид фильтра entityKey) или select (вид range). Фильтр применяем и для удаления сущностей в режиме entities — удаляем все сущности из коробки, которые соответствуют фильтру (а если фильтр не установлен — то просто заменяем сущности методом putAll).
4. Учитывать флаг useMemoryCache. Если установлен, то кроме базы данных используем дополнительно и кеш в памяти — _memoryCache. С ним работаем так же как с базой (можно добавить обертку по аналогии с моим моком ObjectDdMock). Если данные есть в _memoryCache и не нужно обновлять данные — в базу не лезем.
Для удобства, чтобы код собирался, добавлен раздел с моками — его трогать не нужно.
// ****************************** MOCKS ******************************
class ErrorInfo {
final int errorCode;
ErrorInfo(this.errorCode);
}
class ResultOrError<TResult> {
late bool isSuccess;
TResult? result;
ErrorInfo? error;
ResultOrError._fromResult(this.result) {
isSuccess = true;
error = null;
}
ResultOrError._fromError(this.error) {
isSuccess = false;
result = null;
}
factory ResultOrError.fromResult(TResult result) =>
ResultOrError._fromResult(result);
factory ResultOrError.fromError(ErrorInfo error) =>
ResultOrError._fromError(error);
}
class ApiObject1 {
final Map<String, String> items = {"item1": "value1", "item2": "value2"};
}
abstract class DataEntity {
String get key;
}
class DataEntity1 extends DataEntity {
String name;
String value;
@override
String get key => name;
DataEntity1(this.name, this.value);
}
class Model1 {
final String item;
Model1(this.item);
}
class ObjectDdMock<TDataEntity extends DataEntity> {
String get boxKey => TDataEntity.runtimeType.toString();
static final Map<String, Map<String, dynamic>> _boxes = {};
List<TDataEntity> getAll() {
return _boxes[boxKey]?.values.cast<TDataEntity>().toList() ?? [];
}
TDataEntity? getBeyKey(String key) {
final box = _boxes[boxKey];
if (null == box) return null;
return box[key] as TDataEntity;
}
List<TDataEntity> select(String startKey, String endKey) {
final box = _boxes[boxKey];
if (null == box) return [];
final allEntities = getAll();
List<String> keys = allEntities
.map((e) => e.key)
.where((k) => k.compareTo(startKey) >= 0 && k.compareTo(endKey) <= 0)
.toList();
keys.sort();
List<TDataEntity> result = [];
for (final key in keys) {
result.add(box[key]!);
}
return result;
}
void putAll(List<TDataEntity> entities) {
for (final entity in entities) {
put(entity);
}
}
void put(TDataEntity entity) {
if (!_boxes.containsKey(boxKey)) {
_boxes[boxKey] = {entity.key: entity};
} else {
_boxes[boxKey]![entity.key] = entity;
}
}
void deleteBox() {
if (_boxes.containsKey(boxKey)) {
_boxes.remove(boxKey);
}
}
void deleteEntities(List<TDataEntity> entities) {
final box = _boxes[boxKey];
if (null != box) {
for (final entity in entities) {
box.remove(entity.key);
}
}
}
}
// ****************************** END MOCKS ******************************
enum FetchDataPolicy { never, onEmptyCache, always }
enum CacheUpdateMode { entities, box }
enum FilterKind { entityKey, range }
abstract class Filter {
FilterKind get kind;
}
class FilterByKey implements Filter {
String key;
@override
FilterKind get kind => FilterKind.entityKey;
FilterByKey(this.key);
}
class FilterByRange implements Filter {
String startKey;
String endKey;
@override
FilterKind get kind => FilterKind.range;
FilterByRange({required this.startKey, required this.endKey});
}
abstract class CacheManager<TApiObject, TDataEntity extends DataEntity,
TModel> {
final Future<ResultOrError<TApiObject>> Function() fetchFromApi;
final List<TDataEntity> Function(TApiObject) transformToDataEntities;
final TModel Function(TDataEntity) transformToModel;
final String boxName;
final bool useMemoryCache;
CacheManager(
{required this.fetchFromApi,
required this.transformToDataEntities,
required this.transformToModel,
required this.boxName,
this.useMemoryCache = false});
Future<ResultOrError<List<TModel>>> retrieveItems(
{FetchDataPolicy fetchDataPolicy = FetchDataPolicy.onEmptyCache,
CacheUpdateMode cacheUpdateMode = CacheUpdateMode.entities,
Filter? filter});
Future<TModel?> getItemFromCache(String key);
}
final Map<String, Map<String, dynamic>> _memoryCache = {};
class ObjectDdCacheManager<TApiObject, TDataEntity extends DataEntity, TModel>
extends CacheManager<TApiObject, TDataEntity, TModel> {
ObjectDdCacheManager(
{required super.fetchFromApi,
required super.transformToDataEntities,
required super.transformToModel,
required super.boxName});
@override
Future<ResultOrError<List<TModel>>> retrieveItems(
{FetchDataPolicy fetchDataPolicy = FetchDataPolicy.onEmptyCache,
CacheUpdateMode cacheUpdateMode = CacheUpdateMode.entities,
Filter? filter}) {
// TODO: implement retrieveItems
throw UnimplementedError();
}
@override
Future<TModel?> getItemFromCache(String key) {
// TODO: implement getItemFromCache
throw UnimplementedError();
}
}
void main() async {
final cacheManager = ObjectDdCacheManager(
fetchFromApi: () async {
return ResultOrError.fromResult(ApiObject1());
},
transformToDataEntities: (apiObject) {
return apiObject.items.keys
.map((k) => DataEntity1(k, apiObject.items[k]!))
.toList();
},
transformToModel: (dataEntity) {
return Model1(dataEntity.value);
},
boxName: 'box1');
final items = await cacheManager.retrieveItems();
print(items.result!.length);
}
|
| |
Код с возможностью запуска тут:
http://tpcg.io/_WD2ZDE
Вот такой результат выдает
| GPT-4o |
| class ObjectDdCacheManager<TApiObject, TDataEntity extends DataEntity, TModel>
extends CacheManager<TApiObject, TDataEntity, TModel> {
ObjectDdCacheManager({
required super.fetchFromApi,
required super.transformToDataEntities,
required super.transformToModel,
required super.boxName,
});
@override
Future<ResultOrError<List<TModel>>> retrieveItems({
FetchDataPolicy fetchDataPolicy = FetchDataPolicy.onEmptyCache,
CacheUpdateMode cacheUpdateMode = CacheUpdateMode.entities,
Filter? filter,
}) async {
final memoryCache = _memoryCache[boxName];
final ObjectDdMock<TDataEntity> dbCache = ObjectDdMock<TDataEntity>();
List<TDataEntity> entities;
// Работа с кешем в памяти
if (useMemoryCache && memoryCache != null) {
entities = _filterEntities(memoryCache.values.cast<TDataEntity>(), filter);
if (fetchDataPolicy == FetchDataPolicy.never || (fetchDataPolicy == FetchDataPolicy.onEmptyCache && entities.isNotEmpty)) {
return ResultOrError.fromResult(entities.map(transformToModel).toList());
}
}
// Работа с кешем в базе
entities = _filterEntities(dbCache.getAll(), filter);
if (fetchDataPolicy == FetchDataPolicy.never) {
return ResultOrError.fromResult(entities.map(transformToModel).toList());
}
if (fetchDataPolicy == FetchDataPolicy.onEmptyCache && entities.isNotEmpty) {
return ResultOrError.fromResult(entities.map(transformToModel).toList());
}
// Запрос данных из API
final apiResult = await fetchFromApi();
if (!apiResult.isSuccess) {
return ResultOrError.fromError(apiResult.error!);
}
final newEntities = transformToDataEntities(apiResult.result!);
if (cacheUpdateMode == CacheUpdateMode.entities) {
if (filter != null) {
final filteredEntities = _filterEntities(dbCache.getAll(), filter);
dbCache.deleteEntities(filteredEntities);
}
dbCache.putAll(newEntities);
} else if (cacheUpdateMode == CacheUpdateMode.box) {
dbCache.deleteBox();
dbCache.putAll(newEntities);
}
if (useMemoryCache) {
_memoryCache[boxName] = {for (var e in newEntities) e.key: e};
}
return ResultOrError.fromResult(newEntities.map(transformToModel).toList());
}
@override
Future<TModel?> getItemFromCache(String key) async {
if (useMemoryCache) {
final memoryCache = _memoryCache[boxName];
if (memoryCache != null && memoryCache.containsKey(key)) {
return transformToModel(memoryCache[key] as TDataEntity);
}
}
final ObjectDdMock<TDataEntity> dbCache = ObjectDdMock<TDataEntity>();
final entity = dbCache.getBeyKey(key);
if (entity != null) {
if (useMemoryCache) {
_memoryCache[boxName] ??= {};
_memoryCache[boxName]![key] = entity;
}
return transformToModel(entity);
}
return null;
}
List<TDataEntity> _filterEntities(List<TDataEntity> entities, Filter? filter) {
if (filter == null) return entities;
if (filter.kind == FilterKind.entityKey) {
final key = (filter as FilterByKey).key;
return entities.where((e) => e.key == key).toList();
} else if (filter.kind == FilterKind.range) {
final startKey = (filter as FilterByRange).startKey;
final endKey = filter.endKey;
return entities.where((e) => e.key.compareTo(startKey) >= 0 && e.key.compareTo(endKey) <= 0).toList();
}
return entities;
}
}
|
| |
Тут:
https://chatgpt.com/share/67793e85-4a44-8000-b3e3-fa47a8d2663f
Вот o1-preview через API — ответ стоил
$0.14
| o1-preview |
| class ObjectDdCacheManager<TApiObject, TDataEntity extends DataEntity,
TModel> extends CacheManager<TApiObject, TDataEntity, TModel> {
// Define the boxKey using TDataEntity's type
final String boxKey = TDataEntity.toString();
// Create an instance of ObjectDdMock for database operations
final ObjectDdMock<TDataEntity> _database = ObjectDdMock<TDataEntity>();
ObjectDdCacheManager(
{required super.fetchFromApi,
required super.transformToDataEntities,
required super.transformToModel,
required super.boxName,
super.useMemoryCache = false});
// Helper method to get data from cache (memory or database)
Future<List<TDataEntity>> _getDataFromCache(Filter? filter) async {
if (useMemoryCache) {
// Try to get data from _memoryCache
final box = _memoryCache[boxKey];
if (box != null) {
if (filter == null) {
// Return all data
return box.values.cast<TDataEntity>().toList();
} else if (filter.kind == FilterKind.entityKey) {
FilterByKey filterByKey = filter as FilterByKey;
final entity = box[filterByKey.key] as TDataEntity?;
return entity != null ? [entity] : [];
} else if (filter.kind == FilterKind.range) {
FilterByRange filterByRange = filter as FilterByRange;
final entities = box.values
.cast<TDataEntity>()
.where((e) =>
e.key.compareTo(filterByRange.startKey) >= 0 &&
e.key.compareTo(filterByRange.endKey) <= 0)
.toList();
return entities;
}
}
}
// Get data from database
if (filter == null) {
return _database.getAll();
} else if (filter.kind == FilterKind.entityKey) {
FilterByKey filterByKey = filter as FilterByKey;
final entity = _database.getBeyKey(filterByKey.key);
return entity != null ? [entity] : [];
} else if (filter.kind == FilterKind.range) {
FilterByRange filterByRange = filter as FilterByRange;
return _database.select(filterByRange.startKey, filterByRange.endKey);
}
return [];
}
// Helper method to store data into cache (memory and database)
Future<void> _storeDataInCache(List<TDataEntity> entities,
CacheUpdateMode cacheUpdateMode, Filter? filter) async {
if (cacheUpdateMode == CacheUpdateMode.box) {
if (useMemoryCache) {
// Remove the box from _memoryCache and add entities
_memoryCache[boxKey] = {for (var e in entities) e.key: e};
}
// Delete box from database and add entities
_database.deleteBox();
_database.putAll(entities);
} else if (cacheUpdateMode == CacheUpdateMode.entities) {
if (filter != null) {
// Delete entities matching the filter
if (useMemoryCache) {
final box = _memoryCache[boxKey];
if (box != null) {
if (filter.kind == FilterKind.entityKey) {
FilterByKey filterByKey = filter as FilterByKey;
box.remove(filterByKey.key);
} else if (filter.kind == FilterKind.range) {
FilterByRange filterByRange = filter as FilterByRange;
final keysToRemove = box.keys
.where((k) =>
k.compareTo(filterByRange.startKey) >= 0 &&
k.compareTo(filterByRange.endKey) <= 0)
.toList();
for (final k in keysToRemove) {
box.remove(k);
}
}
}
}
// Delete entities from the database
if (filter.kind == FilterKind.entityKey) {
FilterByKey filterByKey = filter as FilterByKey;
final entity = _database.getBeyKey(filterByKey.key);
if (entity != null) {
_database.deleteEntities([entity]);
}
} else if (filter.kind == FilterKind.range) {
FilterByRange filterByRange = filter as FilterByRange;
final entitiesToDelete =
_database.select(filterByRange.startKey, filterByRange.endKey);
_database.deleteEntities(entitiesToDelete);
}
}
// Add new entities
if (useMemoryCache) {
final box = _memoryCache.putIfAbsent(boxKey, () => {});
for (var e in entities) {
box[e.key] = e;
}
}
_database.putAll(entities);
}
}
@override
Future<ResultOrError<List<TModel>>> retrieveItems(
{FetchDataPolicy fetchDataPolicy = FetchDataPolicy.onEmptyCache,
CacheUpdateMode cacheUpdateMode = CacheUpdateMode.entities,
Filter? filter}) async {
try {
bool needToFetch = false;
List<TDataEntity> cachedEntities = await _getDataFromCache(filter);
if (fetchDataPolicy == FetchDataPolicy.always) {
needToFetch = true;
} else if (fetchDataPolicy == FetchDataPolicy.onEmptyCache &&
cachedEntities.isEmpty) {
needToFetch = true;
} else if (fetchDataPolicy == FetchDataPolicy.never) {
needToFetch = false;
}
if (needToFetch) {
// Fetch from API
final result = await fetchFromApi();
if (!result.isSuccess) {
return ResultOrError.fromError(result.error!);
}
// Transform API object to data entities
List<TDataEntity> entities =
transformToDataEntities(result.result as TApiObject);
// Update cache
await _storeDataInCache(entities, cacheUpdateMode, filter);
// Update cachedEntities with newly fetched data
cachedEntities = await _getDataFromCache(filter);
}
// Transform data entities to models
final models = cachedEntities.map(transformToModel).toList();
return ResultOrError.fromResult(models);
} catch (e) {
return ResultOrError.fromError(ErrorInfo(-1)); // Replace with appropriate error code
}
}
@override
Future<TModel?> getItemFromCache(String key) async {
try {
if (useMemoryCache) {
final box = _memoryCache[boxKey];
if (box != null) {
final entity = box[key] as TDataEntity?;
if (entity != null) {
return transformToModel(entity);
}
}
}
// Get from database
final entity = _database.getBeyKey(key);
if (entity != null) {
return transformToModel(entity);
}
return null;
} catch (e) {
return null;
}
}
}
|
| |
| Google Gemini |
| final Map<String, Map<String, dynamic>> _memoryCache = {};
class ObjectDdCacheManager<TApiObject, TDataEntity extends DataEntity, TModel>
extends CacheManager<TApiObject, TDataEntity, TModel> {
ObjectDdCacheManager(
{required super.fetchFromApi,
required super.transformToDataEntities,
required super.transformToModel,
required super.boxName,
super.useMemoryCache});
ObjectDdMock<TDataEntity> get _db => ObjectDdMock<TDataEntity>();
ObjectDdMock<TDataEntity> get _memory => ObjectDdMock<TDataEntity>();
@override
Future<ResultOrError<List<TModel>>> retrieveItems(
{FetchDataPolicy fetchDataPolicy = FetchDataPolicy.onEmptyCache,
CacheUpdateMode cacheUpdateMode = CacheUpdateMode.entities,
Filter? filter}) async {
List<TDataEntity> cachedEntities = [];
if (useMemoryCache) {
cachedEntities = _memory.getAll();
}
if (cachedEntities.isEmpty) {
cachedEntities = _db.getAll();
}
if (fetchDataPolicy == FetchDataPolicy.never) {
return ResultOrError.fromResult(
cachedEntities.map(transformToModel).toList());
}
if (fetchDataPolicy == FetchDataPolicy.onEmptyCache &&
cachedEntities.isNotEmpty) {
return ResultOrError.fromResult(
cachedEntities.map(transformToModel).toList());
}
final apiResult = await fetchFromApi();
if (!apiResult.isSuccess) {
return ResultOrError.fromError(apiResult.error!);
}
final newEntities = transformToDataEntities(apiResult.result!);
if (cacheUpdateMode == CacheUpdateMode.box) {
_db.deleteBox();
if (useMemoryCache) {
_memory.deleteBox();
}
} else if (filter != null) {
List<TDataEntity> entitiesToDelete = [];
if (filter.kind == FilterKind.entityKey) {
final keyFilter = filter as FilterByKey;
final entity = _db.getBeyKey(keyFilter.key);
if (entity != null) {
entitiesToDelete.add(entity);
}
if (useMemoryCache) {
final memoryEntity = _memory.getBeyKey(keyFilter.key);
if (memoryEntity != null) {
entitiesToDelete.add(memoryEntity);
}
}
} else if (filter.kind == FilterKind.range) {
final rangeFilter = filter as FilterByRange;
entitiesToDelete = _db.select(rangeFilter.startKey, rangeFilter.endKey);
if (useMemoryCache) {
entitiesToDelete.addAll(_memory.select(rangeFilter.startKey, rangeFilter.endKey));
}
}
_db.deleteEntities(entitiesToDelete);
if (useMemoryCache) {
_memory.deleteEntities(entitiesToDelete);
}
}
_db.putAll(newEntities);
if (useMemoryCache) {
_memory.putAll(newEntities);
}
return ResultOrError.fromResult(newEntities.map(transformToModel).toList());
}
@override
Future<TModel?> getItemFromCache(String key) async {
if (useMemoryCache) {
final memoryEntity = _memory.getBeyKey(key);
if (memoryEntity != null) {
return transformToModel(memoryEntity);
}
}
final entity = _db.getBeyKey(key);
return entity != null ? transformToModel(entity) : null;
}
}
|
| |
Так же просьба, у кого есть возможность, позапускать на других версиях. Позже добавлю комменты.
З.Ы.
Так же добавляйте свое понимание — хорошее ли задание для ИИ, сколько бы у вас заняло времени.