我需要根据两个值在表中查找结果来计算结果。表是这样的:
Bar <10 20 30 40 50 60
Foo
<1.0 .14 .17 .22 .29 .31 .45
1.1 .16 .18 .25 .32 .37 .51
1.2 .19 .20 .29 .37 .41 .53
1.3 .21 .22 .32 .44 .49 .59
1.4 .25 .26 .34 .51 .52 .68
1.5 .29 .31 .39 .53 .54 .71
,其中顶部的数字是Bar值的范围(其中给出的数字是范围的顶部),左侧的数字是Foo值的范围。如果Bar = 24, Foo=1.3,查找结果是。32。(当然,上面的数字是虚构的,真正的表格大约是25 × 25。)
所有这些都必须在javascript中完成,包括存储查找值。
一种可能的方法是将值存储为哈希的哈希:
var lookup = { 1.0: {10:.14, 20: .17, 30:.22}); etc. etc.
,其中外部值是Foo值,每个Foo值映射到一个对象,该对象将Bar值映射到答案。凌乱且难以阅读,但相当明确。
另一种方法是将值存储为数组的数组,并从其他地方获取索引。也就是说,我将值存储在顶部:
var BarColumns = [10, 20, 30, 50, 50, 60];
和下面的
var FooRows = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5];
当给定一些值时,我使用上面的列表将这些值转换为索引,然后使用这些索引在包含答案的二维数组中查找:
var lookup = [
[.14, .17, .22, .29, .31, .45],
[.16, .18, .25. .32, .37, .51],
etc.
];
这与原始表更接近,但是没有头,表数据根本不是很容易读,我认为这更难维护——特别是如果列改变了。
另一个选项——超显式,在每个对象中重复范围:
var lookup = [{foo:1.0, bar:10, result:.14}, {foo:1.0, bar:20, result: .17}, etc.];
这是非常明确的,但应该是巨大的25 X 25的值。
所以,我的问题是:什么是最好的方式来存储我的原始表,这样我可以用它来查找,也有它是人类可读和可维护的?我上面的一个想法?或者完全是别的什么?我应该补充的要点:1)实际查找不会经常发生,所以我不太担心性能(在合理范围内),2)我从客户端获得了表格数据作为Excel电子表格,但最好的结果是如果我能把最终的javascript交给客户端并让他们维护它-因此关注可读性。
jsFiddle Demo
var Range = function(lower,upper){
this.lower = lower;
this.upper = upper;
};
var DataPoint = function(range,bar,value){
this.range = range;
this.bar = bar;
this.value = value;
};
var LookUp = function(){
this.DataPoints = [];
};
LookUp.prototype.add = function(data){
this.DataPoints.push(data);
};
LookUp.prototype.load = function(BarColumns,FooRows,ValueColumns){
ValueColumns = ValueColumns.split(" ").filter(Boolean);
for( var n = 0; n < FooRows.length; n++ ){
var range = 0;
for( var i = 0 ; i < BarColumns.length; i++ ){
var val = parseFloat(ValueColumns[(BarColumns.length * n) + i],10);
var point = new DataPoint(new Range(range,BarColumns[i]),FooRows[n],val);
this.add(point);
range = BarColumns[i];
}
}
};
LookUp.prototype.find = function(x,bar){
for(var i = 0; i < this.DataPoints.length; i++){
var point = this.DataPoints[i];
if( x > point.range.lower && x < point.range.upper && point.bar == bar){
return point.value;
}
}
};
样本数据
注意:valCols字符串只是复制粘贴你的网格。然而,这可以很容易地从Excel中复制出来。在每一行的末尾进行串联,然后在串联列的底部,串联所有这些,它将与这里显示的valCols相同。
var BarColumns = [10, 20, 30, 50, 50, 60];
var FooRows = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5];
var valCols = ".14 .17 .22 .29 .31 .45 .16 .18 .25 .32 .37 .51 .19 .20 .29 .37 .41 .53 .21 .22 .32 .44 .49 .59 .25 .26 .34 .51 .52 .68 .29 .31 .39 .53 .54 .71";
设置
var lookup = new LookUp();
lookup.load(BarColumns,FooRows,valCols);
使用console.log(lookup.find(24,1.3));//3.2
alert(lookup.find(24,1.3));//3.2
我更喜欢使用数组的数组来存储查找表,因此查找可以从二进制搜索中受益。
var foo = [1, 2, 3, 4, 5];
var bar = [6, 7, 8, 9, 10];
var table = [];
// populate example table
for (var i = 0; i < bar.length; i++) {
table[i] = [];
for (var j = 0; j < foo.length; j++) {
table[i][j] = foo[j] * bar[i];
}
}
console.log(table);
var binarySearch = function (arr, x) {
var a = 0;
var b = arr.length - 1;
var m;
if (x > arr[b]) throw "Search key is too big";
while (b > a) {
m = Math.floor((a + b) / 2);
if (arr[m] == x) return m;
if (arr[m] > x) {
if (m - 1 < 0) return 0;
if (x > arr[m - 1]) return m;
b = m - 1;
} else {
if (m + 1 < arr.length && x < arr[m + 1]) return m + 1;
a = m + 1;
}
}
return a;
}
console.assert(binarySearch(foo, 0.5) == 0);
console.assert(binarySearch(foo, 1) == 0);
console.assert(binarySearch(foo, 1.5) == 1);
console.assert(binarySearch(foo, 2) == 1);
console.assert(binarySearch(foo, 2.5) == 2);
//console.log(binarySearch(foo, 6));
var lookup = function (f, b) {
var col = binarySearch(foo, f);
var row = binarySearch(bar, b);
return table[row][col];
}
console.assert(lookup(3, 8) == 24);
console.assert(lookup(2.5, 8) == 24);
console.assert(lookup(2.5, 7.5) == 24);
console.assert(lookup(4, 9) == 36);
小提琴:http://jsfiddle.net/3Lw5C/2/
由于非技术人员将管理它,我们可以构建一个UI来管理foobar范围和查找表值。然后,所有的值都可以序列化,并且很容易地复制和粘贴到js代码中。
var columns = 3;
var rows = 3;
var html = '';
for (var i = 0; i < columns+1; i++) {
html += '<tr>';
for (var j = 0; j < rows+1; j++) {
if (i == 0 && j == 0) {
html += '<td></td>';
continue;
}
var classname = '';
if (i == 0) {
classname = 'foo';
} else if (j == 0) {
classname = 'bar';
} else {
classname = 'data';
}
html += '<td class="'+classname+'"><input type="text" value="0" /></td>';
}
html += '</tr>';
}
$('table').html(html);
var getFoo = function() {
var foo = [];
$('td.foo').each(function(i,td) {
foo[i] = parseFloat($(td).children('input')[0].value);
})
return foo;
};
var getBar = function() {
var bar = [];
$('td.bar').each(function(i,td) {
bar[i] = parseFloat($(td).children('input')[0].value);
})
return bar;
};
var getData = function() {
var data = [];
var j;
$('td.data').each(function(i,td) {
if (i%columns==0) {
j = i/columns;
data[j] = [];
}
data[j][i%columns] = parseFloat($(td).children('input')[0].value);
})
return data;
};
var config = {};
config['foo'] = getFoo();
config['bar'] = getBar();
config['data'] = getData();
$('div').text(JSON.stringify(config));
fiddle: http://jsfiddle.net/MLKsf/
散列对于查找相同的值很好,但是对于表值之间的值,二维数组应该更好。
如果Foo是精确值而Bar不是,我将使用一维数组的哈希。
对于查找范围值和标头,每个值使用一维数组
您真的需要底层数据是人类可读的吗?或者您只是需要一种对开发人员友好的方式来访问给定的值?
如果我们抛开人类可读的问题,这看起来像是一个相当标准的内存vs性能权衡。你可以使用一个紧凑的表示(二维数组),然后提供一个用户友好的函数,比如lookup(foo, bar)
,它做一点处理来获得值,或者你可以用一个更详细的格式存储数据(哈希的哈希),并允许直接lookup[foo][bar]
访问。请注意,2d数组也是JS中哈希的哈希值,因此可能很少或没有内存差异。我将放弃超显式的想法,因为它的查找时间可能比其他两种都要慢得多,因为在最坏的情况下,您需要遍历整个列表以找到正确的值。
这里有一些进一步的考虑:
-
底层数据的可读性到底有多重要?哈希的哈希在这里可能有一个小的优势,但只是一个小的优势(例如,它不太适合
console.table()
进行调试)。 -
数据总是密集的,或者你可能有稀疏的表,其中二维数组将有一堆null ?在这种情况下,哈希的哈希可能是一种更紧凑和可用的表示。
-
数据是如何到达你的,哪些格式需要更多的预处理?如果你真的把它直接放在代码中,那么任何一种格式都可以通过明智的选项卡变得相对易读。
-
你几乎肯定想在任何情况下提供一个
lookup()
函数,这样你就不会硬编码的实现作为一个2d数组到处并锁定自己。 -
如何容易得到正确的范围位置为给定的输入值?这将取决于你是否知道范围是均匀间隔的,总是一个给定的小数位数等,在这种情况下,你可以用数学来确定哈希键;如果范围刻度是任意的,那么"头数组"方法有优点,因为您可能需要遍历头以找到正确的键。