Как исправить мой LINQ, чтобы правильно найти строки из списка в строке? - PullRequest
0 голосов
/ 24 марта 2020

Уже несколько часов я борюсь с LINQ, который должен находить города из списка объектов внутри списка адресов.

У меня есть список CityModel объектов, где:

public class CityModel
    {
        public string City { get; set; }
        public char CountryChar { get; set; }
    }

и список AddressModel объектов:

public class AddressModel
    {
        public string Address { get; set; }
        public char CountryChar { get; set; }
    }

В обоих случаях CountryChar - это первая буква страны, принадлежащая City или Address свойствам. Все строки и символы анализируются с ToLower(), поэтому все они в нижнем регистре.

Примеры CityModel объекта:

            cities.Add(new CityModel()
            {
                City = "singapore",
                CountryChar = 's'
            }); //Singapore in singapore
            cities.Add(new CityModel()
            {
                City = "anthony",
                CountryChar = 'u'
            }); //Anthony in United States

Два случая AddressModel объектов:

addressesM.Add(new AddressModel()
            {
                Address = "#20-06, gateway east, 152, beach road, singapore 189721",
                CountryChar = 's'
            });
            addressesM.Add(new AddressModel()
            {
                Address = "01-01, 8, anthony road, singapore 229957",
                CountryChar = 's'
            }); //note: Anthony

Идея моего LINQ - найти, является ли какой-либо из городов подстрокой моего свойства Address в каждом из объектов AddressModel. Если да, то проверьте, соответствует ли CountryChar для AddressModel CountryChar для CityModel.

My LINQ:

foreach (AddressModel address in addressesM)
            {
                string city = "xxx";
                i++;

                Console.WriteLine(i + " z " + addresses.Count());

                CityModel tocompare = cities.Where(collectionOfCities => address.Address.IndexOf(collectionOfCities.City) >= 0 &&
                (address.Address[address.Address.IndexOf(collectionOfCities.City) - 1] == ' ' ||
                address.Address[address.Address.IndexOf(collectionOfCities.City) - 1] == ',') &&
                (address.Address[address.Address.IndexOf(collectionOfCities.City) + collectionOfCities.City.Length] == ' ' ||
                address.Address[address.Address.IndexOf(collectionOfCities.City) + collectionOfCities.City.Length] == ',') &&
                collectionOfCities.CountryChar == address.CountryChar).FirstOrDefault();

                if (tocompare != null)
                {
                    TextInfo textInfo = new CultureInfo("en-US", false).TextInfo;

                    tocompare.City = textInfo.ToTitleCase(tocompare.City);

                    city = tocompare.City;
                }

                output.Add(city);
            }

Для первого случая моего AddressModel LINQ работает хорошо. Проблема возникает, когда внутри моего второго AddressModel есть слово "Энтони", а также есть Город под названием Anthony. В этом случае после проверки остальных условий LINQ для «Энтони» он добавляет мою строку output «xxx» и переходит к следующему AddressModel в списке.

Понятия не имею как это сделать после сбоя с городом "Энтони" программа проверит остальные города в списке?

РЕДАКТИРОВАТЬ:

некоторые адреса могут имеют почтовые индексы, содержащие цифры и заглавные буквы, примеры:

Блок 7, 1-й / 3-й этажи, 1690, Cailun Lu, Pudong Xinqu, Шанхай, 201203, Китай.

1-й этаж, 6, набережная Antoine-1er Le Ruscino, 98012 Монте-Карло, CEDEX, Монако.

1, Dole Drive, Westlake Village CA 91362-7300, США.

Здание Гратос, ул. Элефтериу Венизелу, 15, 105 64 Афины, Греция.

Некоторые названия городов могут содержать более 1 слова, примеры:

Fi sh Ястреб

Панама Сити

La Verne

1 Ответ

1 голос
/ 24 марта 2020

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

List<CityModel> cities = ...

Dictionary<(string city, char country), CityModel> citiesDict = cities
  .ToDictionary(item => (item.City, item.CountryChar), 
                item => item);

Затем мы должны изобрести извлечение названий городов (с возможными ложными срабатываниями * 1009) *); вероятно, last name (последовательная a..z буква) является хорошим выбором (для этого воспользуемся регулярными выражениями ):

// will return "singapore"
IEnumerable<string> CityNames(string address) {
  string name = Regex.Match(
     address, 
   @"\b[a-z]+\b", 
     RegexOptions.RightToLeft | RegexOptions.IgnoreCase).Value;

  if (!string.IsNullOrEmpty(name))
    yield return name;
}

Или более снисходительно ( любое имен, вернет "gateway", "east", "beach", "road", "singapore") реализация:

IEnumerable<string> CityNames(string address) {
  return Regex
    .Matches(address, @"\b[a-z]+\b", RegexOptions.IgnoreCase)
    .Cast<Match>()
    .Select(match => match.Value);
}

Затем мы можем построить окончательный Linq с помощью SelectMany:

List<AddressModel> addresses = ...

var result = addresses
  .SelectMany(item => CityNames(item.Address) // match all possible cities form address
     .Select(possibleCity => new { // actual city from possible city
        address = item,
        city    = citiesDict.TryGetValue((possibleCity, item.CountryChar),
                                          out var actualCity) 
          ? actualCity // Either Real City (if found), say, "singapore"
          : null       // null if not exits, say, "road"
      }))
  .Where(item => item.city != null); // Real City Only

Редактировать: Основная сложность здесь заключается в извлечении потенциальных названий городов ( Обработка естественного языка в общем случае ...). Если вы можете гарантировать, что части адреса (улица, город, страна и т. Д. c.) Разделены запятой ,, мы можем попробовать Split:

  IEnumerable<string> CityNames(string address) {
    return address
      .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
      .Select(item => Regex.Replace(item.Trim(), @"\s+", " ").ToLower())
      .Where(item => !string.IsNullOrEmpty(item));
  }

Теперь для "1st Floor, 6, quai Antoine-1er Le Ruscino, 98012 Monte Carlo, CEDEX, Monaco" мы ' будет иметь "1st floor" "6" "quai antoine-1er le ruscino" "98012 monte carlo", "cedex", "monaco". Обратите внимание, что 98012 добавлено к Monte Carlo. Если вы хотите полосы номеров и иметь "st floor", "quai antoine-er le ruscino" "monte carlo", "cedex", "monaco"

  IEnumerable<string> CityNames(string address) {
    return address
      .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
      .Select(item => Regex.Replace(item, "[0-9]+", ""))
      .Select(item => Regex.Replace(item.Trim(), @"\s+", " ").ToLower())
      .Where(item => !string.IsNullOrEmpty(item));
  }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...