我有这堂课:
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
方法调用的原因是什么?可以从这些片段中推断出来吗?
答案 0 :(得分:2)
您获得的迭代次数超出预期的原因是LINQ调用多次迭代Count()
。
当您在IEnumerable<T>
上致电Count
时,LINQ会尝试查看是否有Length
或IEnumerable<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个元素。