文字列のn行目のインデックスを取得
文字列のn行目の先頭が文字列先頭から何文字目かを取得する関数。 4 つ書いてみた。
function getLineIndex1(str, lineno) { var curline = 0; var len = str.length; for(var i = 0; i < len; i ++) { if(curline == lineno) return i; var c = str.charAt(i); if(c == "\r") { if(str.charAt(i+1) == "\n") i ++; curline ++; } if(c == "\n") { curline ++; } } return null; } function getLineIndex2(str, lineno) { var curline = 0; var len = str.length; for(var i = 0; i < len; i ++) { if(curline == lineno) return i; var c = str.charCodeAt(i); if(c == 0x0d) { if(str.charCodeAt(i+1) == 0x0a) i ++; curline ++; } if(c == 0x0a) { curline ++; } } return null; } function getLineIndex3(str, lineno) { if(str.length > 0 && lineno == 0) return 0; var i = 0; var result; var tag = new Object; try { str.replace(/(?:\r\n|\n|\r(?!\n))(?!$)/g, function(s,l) { if(++i == lineno) { result = l + s.length; throw tag; } }); return null; } catch(e) { if(e !== tag) throw e; return result; } } function getLineIndex4(str, lineno) { var i = 0; var result; var tag = new Object; try { str.replace(/.*(?:\r\n|\n|\r|.$)/g, function(s,l) { if(i++ == lineno) { result = l; throw tag; } }); return null; } catch(e) { if(e !== tag) throw e; return result; } }
getLineIndex1 は普通に 1 文字ずつ charAt で舐めていく方法。 getLineIndex2 は getLineIndex1 の charAt を charCodeAt にかえただけ。 getLineIndex3 は String#replace を使って。 getLineIndex4 はその変形。
テストと時間計測
// http://developer.mozilla.org/ja/Core_JavaScript_1.5_Reference/Global_Objects/Array/forEach if (!Array.prototype.forEach) Array.prototype.forEach = function(fun, thisp) { var len = this.length; for (var i = 0; i < len; i++) if (i in this) fun.call(thisp, this[i], i, this); }; var testCount; var testNGs; function tests(name, callback) { testCount = 0; testNGs = []; callback(); if(testNGs.length == 0) { print(name+": all test passed ("+testCount+")"); } else { print(name+": " + (testCount - testNGs.length)+" pass "+testNGs.length+" fail"); testNGs.forEach(function(arg) { var i = arg[0], a = arg[1], b = arg[2]; print(" "+i+": "+uneval(a)+" != "+uneval(b)); }); } } function is(a,b) { if(a != b) { testNGs.push([testCount, a, b]); } testCount ++; } var fns = [getLineIndex1, getLineIndex2, getLineIndex3, getLineIndex4]; fns.forEach(function(fn){ tests(fn.name, function(){ is(fn("abc",0), 0); is(fn("",0), null); is(fn("\n",0), 0); is(fn("abc",1), null); is(fn("\n",1), null); is(fn("\nA",1), "\n".length); is(fn("ABC\nDEF",1), "ABC\n".length); is(fn("ABC\nDEF",2), null); is(fn("ABC\nDEF\n",2), null); is(fn("ABC\nDEF\nGHI",2), "ABC\nDEF\n".length); is(fn("\r\n",0), 0); is(fn("\r\n",1), null); is(fn("\r\nA",1), "\r\n".length); is(fn("ABC\r\nDEF",1), "ABC\r\n".length); is(fn("ABC\r\nDEF",2), null); is(fn("ABC\r\nDEF\r\n",2), null); is(fn("ABC\r\nDEF\r\nGHI",2), "ABC\r\nDEF\r\n".length); }); }); var a = []; var N = 5000; for(var i = 0; i < N; i ++) { var l = (i / 50)|0; while(l--) { a[a.length] = "A"; } a[a.length] = "\r\n"; } var str = a.join(""); fns.forEach(function(fn){ var n = N - 1; var start = + new Date; var result = fn(str, n); var end = + new Date; print(fn.name+':'+result+':'+(end - start)+' ms'); });
もっと高速な方法やエレガントな方法があったら教えてください><