Следующее решение решает проблему программно. Он закодирован в go
, но я считаю, что его можно легко адаптировать к любому языку.
Сначала я создам весь фрагмент кода, а затем разобью его.
func (store *PersonStore) Search(name string) ([]Person, error) {
context := context.Background()
persons := make([]*Person, 0)
query := datastore.NewQuery("Person").Filter("Name >= ", strings.ToLower(name)).Order("Name")
keys, err := store.client.GetAll(context, query, &persons)
if err != nil {
return nil, fmt.Errorf("unable to search for persons w/ names containing %s - %v", name, err)
}
filteredPersons := make([]*Perons, 0)
for i, p := range persons {
p.ID = keys[i].ID
if !strings.Contains(p.Name, strings.ToLower(name)) {
break
} else {
filteredPersons = append(filteredPersons, p)
}
}
}
1 - Предположение в нижнем регистре
Для того, чтобы этот код работал, мы сначала должны сделать очень строгое предположение, что все имена в нижнем регистре . Если по какой-либо причине вы не можете сделать это предположение, вы все равно можете частично использовать этот код, но он будет менее эффективен.
2 - запрос к хранилищу данных
Первая часть кода предназначен для извлечения хранилища данных для лиц, чье имя совпадает с желаемым шаблоном.
context := context.Background()
persons := make([]*Person, 0)
query := datastore.NewQuery("Person").Filter("Name >= ", strings.ToLower(name)).Order("Name")
keys, err := store.client.GetAll(context, query, &persons)
if err != nil {
return nil, fmt.Errorf("unable to search for persons w/ names containing %s - %v", name, err)
}
Мы используем strings.ToLower(name)
, чтобы убедиться, что мы не получим ALL Person
с. В идеале name
также следует обрезать, но обычно это делается во внешнем интерфейсе, поэтому мы здесь это опускаем.
Еще раз, это основано на предположении, что все Name
s находятся ниже дело. Если вы не можете этого допустить, вы всегда можете использовать
query := datastore.NewQuery("Person").Filter("Name >= ", name).Order("Name")
Вы просто получите начальный список с ( возможно много ) больше Person
с.
Наконец, мы упорядочиваем наш извлеченный список с .Order("Name")
, так как это будет базовая точка второй части кода.
3 - Фильтр по именам
Вплоть до здесь это был простой GetAll
кусок кода. Нам все еще нужно вставить ключи в Person
структуры. Нам нужно найти способ оптимизировать это. Для этого мы можем опираться на тот факт, что список persons
и keys
имеют точную длину и порядок. Таким образом, предстоящий for
l oop начинается точно так же, как обычная вставка ключа в структурный бит.
for i, p := range persons {
p.ID = keys[i].ID
Следующий бит - это то место, где происходит оптимизация: поскольку мы знаем, что Person
s упорядочены к Name
мы уверены, что, как только strings.Contains(p.Name, strings.ToLower(name))
не соответствует действительности, мы выбрали все Person
s, чей Name
соответствует нашим критериям, то есть, как только p.Name
не начинается с Rob
больше, но с Roc
или Rod
или чем-то лексикографически большим, чем это.
if !strings.Contains(p.Name, strings.ToLower(name)) {
break
Затем мы можем избежать нашего l oop, используя инструкцию break
, надеясь, что проанализировали только первые несколько элементы persons
списка, которые соответствуют нашим критериям. Они попадают в оператор else
:
} else {
filteredPersons = append(filteredPersons, p)
}
4 - Фильтр имен без предположения о нижнем регистре
Как я уже говорил ранее, если вы не можете предположить, что все имена в нижнем регистре, Вы все еще можете использовать этот код, но он не будет оптимизирован, поскольку вам обязательно нужно будет просмотреть полный список persons
, возвращаемый запросом.
Код должен выглядеть следующим образом:
filteredPersons := make([]*Perons, 0)
for i, p := range persons {
p.ID = keys[i].ID
if strings.Contains(p.Name, strings.ToLower(name)) {
filteredPersons = append(filteredPersons, p)
}
}