懒洋洋地建立一个可枚举的,中间有中断

lazily building up an enumerable with breaks in between

我有一个对象集合 IEnumerable<object> obs。 我有另一个对象集合 IEnumerable<object> data.

对于 obs 中的每个 ob,我需要在 data 中找到与 属性 中具有相同值的第一个项目 ob .例如,我可以在 data 中查找与 ob 具有相同 ToString() 值的第一个项目。当找到 属性 值匹配的第一个项目时,我对找到的数据项目做一些事情,然后检查 obs 中的下一个 ob。如果找到 none,我会抛出一个错误。

这是一个天真的方法:

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

完全忘记了 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: ;
}

如果您的集合非常大并且您真的不想为 data 的每个项目评估 ToString,您可以使用以下方法:

  1. 创建已计算项目的缓存
  2. 如果找到某个项目,我会缓存 - 太好了,我们有匹配项。
  3. 否则 - 通过遍历 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.");
        }
    }
    

通过这种方式,您可以保证仅在找到所有 obs 项时迭代 data 集合,并且您不会评估 ToString每个项目不止一次.

PS: 我希望 "ToString" 只是一个例子,你在那里有一些复杂的计算,值得这么复杂......