Лениво создавая перечислимые с перерывами между
У меня есть коллекция предметов IEnumerable<object> obs
, У меня есть другая коллекция предметов IEnumerable<object> data
,
Для каждого ob
в obs
Мне нужно найти первый пункт в data
имеет то же значение в определенном свойстве, что и ob
, Например, я мог бы искать первый элемент в data
это имеет то же самое ToString()
значение как ob
, Когда найден первый элемент, у которого совпадают значения свойств, я делаю что-то с найденным элементом данных, а затем проверяю следующий ob
в obs
, Если ничего не найдено, выкидываю ошибку.
Вот наивный подход:
foreach (object ob in obs)
{
foreach (object dataOb in data)
if (ob.ToString() == dataOb.ToString())
{
... // do something with dataOb
goto ContinueOuter;
}
throw new Exception("No matching data found.");
ContinueOuter: ;
}
Недостатком является то, что я рассчитываю dataOb.ToString()
каждый раз, что не нужно. Я мог бы кешировать это:
IDictionary<object, string> dataToDataStr = new Dictionary<object, string>();
foreach (object dataObj in data) // collect all ToString values in advance
dataToDataStr.Add(dataObj, dataObj.ToString());
foreach (object ob in obs)
{
foreach (object dataOb in dataToDataStr.Keys)
if (ob.ToString() == dataToDataStr[dataOb])
{
... // do something with dataOb
goto ContinueOuter;
}
throw new Exception("No matching data found.");
ContinueOuter: ;
}
Недостатком является то, что я рассчитываю все ToString()
значения, даже если это не обязательно. Я мог бы найти все соответствующие объекты данных в первой половине сбора данных.
Как я могу создать dataToDataStr
словарь (или любая другая перечисляемая структура данных, которая позволяет мне извлекать как объект, так и его значение ToString, вычисляемое только один раз)?
Вот код (смешанный с псевдокодом) того, что я имею в виду:
IDictionary<object, string> dataToDataStr = new Dictionary<object, string>();
object lastProcessedDataOb = null;
foreach (object ob in obs)
{
foreach (object dataOb in dataToDataStr.Keys)
if (ob.ToString() == dataToDataStr[dataOb])
{
... // do something with dataOb
goto ContinueOuter;
}
foreach (object dataOb in data STARTING AFTER lastProcessedDataOb)
// if lastProcessedDataOb == null, start with the first entry of data
{
dataToDataStr.Add(dataOb, dataOb.ToString();
lastProcessedDataOb = dataOb;
if (ob.ToString() == dataToDataStr[dataOb])
{
... // do something with dataOb
goto ContinueOuter;
}
}
throw new Exception("No matching data found.");
ContinueOuter: ;
}
Я знаю, это легко, если data
был LinkedList
или любая коллекция с индексированным доступом (тогда я мог бы сохранить узел связанного списка или индекс как lastProcessedDataOb
), но это не так - это IEnumerable
, Может быть yield return
можно использовать здесь?
2 ответа
Если ваши коллекции действительно большие, и вы действительно не хотите оценивать ToString
за каждый предмет data
Вы можете использовать следующий подход:
- Создать кеш уже рассчитанных предметов
- Если какой-то предмет найден в кеше - это здорово, у нас есть совпадение.
В противном случае - продолжайте заполнять кеш, перебирая
data
Коллекция, пока мы не найдем совпадение. Это может быть эффективно сделано с ручным управлением**Enumerator**
сбора данных (вместо использованияforeach
).IEnumerable<object> obs; IEnumerable<object> data; Dictionary<string, object> dataCache = new Dictionary<string, object>(); var dataIterator = data.GetEnumerator(); foreach (var ob in obs) { var obText = ob.ToString(); object matchingDataItem = null; if (!dataCache.TryGetValue(obText, out matchingDataItem)) { while (dataIterator.MoveNext()) { var currentData = dataIterator.Current; var currentDataText = currentData.ToString(); if (!dataCache.ContainsKey(currentDataText)) // Handle the case when data collection contains duplicates { dataCache.Add(currentDataText, currentData); if (currentDataText == obText) { matchingDataItem = currentData; break; } } } } if (matchingDataItem != null) { Console.WriteLine("Matching item found for " + obText); } else { throw new Exception("No matching data found."); } }
Таким образом, вы можете гарантировать, что итерация data
сбор только до того момента, когда все obs
предметы найдены и вы не будете оценивать ToString
за каждый товар более одного раза.
PS: я надеюсь, что "ToString" только для примера, и у вас там есть некоторые сложные вычисления, которые стоят таких сложностей...
Совершенно забыл, что LINQ использует ленивую оценку... Это должно работать (я использую нотацию нового значения кортежа C# 7):
IEnumerable<(object, string)> objStrPairs = data.Select(o => (o, o.ToString()));
foreach (object ob in obs)
{
foreach ((object, string) dataPair in objStrPairs)
if (ob.ToString() == objStrPairs.Item2)
{
... // do something with objStrPairs.Item1
goto ContinueOuter;
}
throw new Exception("No matching data found.");
ContinueOuter: ;
}