LINQ通过集合进行多次迭代,而不是一次迭代

时间:2016-04-03 14:27:42

标签: c# linq

我有这堂课:

public class SimHasher {
    int count = 0;

    //take each string and make an int[] out of it
    //should call Hash method lines.Count() times
    public IEnumerable<int[]> HashAll(IEnumerable<string> lines) {
        //return lines.Select(il => Hash(il));
        var linesCount = lines.Count();
        var hashes = new int[linesCount][];
        for (var i = 0; i < linesCount; ++i) {
            hashes[i] = Hash(lines.ElementAt(i));
        }
        return hashes;
    }

    public int[] Hash(string line) {
        Debug.WriteLine(++count);
        //stuff
    }
}

当我运行一个调用HashAll的程序并将IEnumerable<string>传递给1000个元素时,它会按预期运行:循环1000次,在程序的调试控制台中写入1到1000的数字在1秒内完成。但是,如果我用LINQ语句替换HashAll方法的代码,就像这样:

public IEnumerable<int[]> HashAll(IEnumerable<string> lines) {
    return lines.Select(il => Hash(il));
}

行为似乎取决于调用HashAll的位置。
如果我从这个测试方法中调用它

[Fact]
public void SprutSequentialIntegrationTest() {
    var inputContainer = new InputContainer(new string[] {
        @"D:\Solutions\SimHash\SimHashTests\R.in"
    });
    var simHasher = new SimHasher();
    var documentSimHashes = simHasher.HashAll(inputContainer.InputLines); //right here
    var queryRunner = new QueryRunner(documentSimHashes);
    var queryResults = queryRunner.RunAllQueries
        (inputContainer.Queries);

    var expectedQueryResults = System.IO.File.ReadAllLines(
        @"D:\Solutions\SimHash\SimHashTests\R.out")
        .Select(eqr => int.Parse(eqr));
    Assert.Equal(expectedQueryResults, queryResults);
}

调试控制台中的计数器达到大约13,000,即使只有1000条输入线。它还需要大约6秒钟才能完成,但仍然可以产生与循环版本相同的结果 如果我从像这样的Main方法运行它

static void Main(string[] args) {
    var inputContainer = new InputContainer(args);
    var simHasher = new SimHasher();
    var documentSimHashes = simHasher.HashAll(inputContainer.InputLines);
    var queryRunner = new QueryRunner(documentSimHashes);
    var queryResults = queryRunner.RunAllQueries
        (inputContainer.Queries);
    foreach (var queryResult in queryResults) {
        Console.WriteLine(queryResult);
    }
}

它开始立即写入输出控制台,非常慢,而调试控制台中的计数器则达到数万。当我尝试逐行调试时,它会直接进入foreach循环并逐个写出结果。经过一些谷歌搜索,我发现这是由于LINQ查询被懒惰评估。但是,每次懒惰地评估结果时,调试控制台中的计数器增加1000以上,甚至超过输入行数。
导致Hash方法调用的原因是什么?可以从这些片段中推断出来吗?

2 个答案:

答案 0 :(得分:2)

您获得的迭代次数超出预期的原因是LINQ调用多次迭代Count()

当您在IEnumerable<T>上致电Count时,LINQ会尝试查看是否有LengthIEnumerable<T>以避免迭代,但是当没有快捷方式时,它会迭代ElementAt(i)一直到最后。

同样,当您调用i时,LINQ会尝试查看是否存在索引器,但通常会将集合迭代到IEnumerable<T>点。这会使你的循环呈现O(n 2 )。

您可以通过调用ToList()ToArray()IEnumerable<T>存储在列表或数组中,轻松解决问题。这将迭代Count一次,然后使用{{1}}和索引来避免进一步的迭代。

答案 1 :(得分:1)

ElementAt()不允许随机访问。

class PascalListenerImpl extends pascalBaseListener { STGroup stg = new STGroupFile("./pascal.stg"); String programName; @Override public void enterProgram(pascalParser.ProgramContext ctx) { List<ParseTree> values = ctx.children; ArrayList<String> valueNames = new ArrayList<String>(); programName = ctx.programHeading().getChild(1).getText(); for (int i = 3; i<ctx.getChildCount(); i+=) ST st = stg.getInstanceOf("enterProgram") st.add("v", programName); } 方法实际上loop through整个序列,直到它到达第N个元素。