我有一个项目列表(想想,目录中的文件),这些项目的顺序由用户任意管理。用户可以在其他项目之间插入项目,删除项目并移动它们。
将订单存储为每个商品的属性的最佳方式是什么,以便在插入或移动特定商品时,其他商品的订购属性不受影响?这些对象将存储在数据库中。
理想的实现方式可以支持无限数量的插入/重新排序。
我用来确定方法局限性的测试如下:
使用3个项目x,y,z,重复拍摄左边的项目并将其放在另外两个项目之间;然后把对象放在右边,把它放在另外两个之间;继续前进直到违反某些约束条件。
对于其他人的参考,我已经包含了一些我尝试过的算法。
答案 0 :(得分:4)
<强> 1.1。小数,双精度
将订单存储为小数。要在订单x和y之间插入两个项目,请将其顺序计算为x / 2 + y / 2。
限制:
精确度或性能。使用双精度,当分母变得太大时,我们最终得到x / 2 + y / 2 == x。在Javascript中,它只能处理25次shuffle。
function doubles(x,y,z) {
for (var i = 0; i < 10000000; i++) {
//x,y,z
//x->v1: y,v1,z
//z->v2: y,v2,v1
var v1 = y/2 + z/2
var v2 = y/2 + v1/2
x = y
y = v2
z = v1
if (x == y) {
console.log(i)
break
}
}
}
>doubles(1, 1.5, 2)
>25
<强> 1.2。小数,BigDecimal
与上面相同,但使用https://github.com/iriscouch/bigdecimal.js中的BigDecimal。在我的测试中,性能降低得非常快。它可能是其他框架的一个很好的选择,但不适用于客户端javascript。
我抛弃了那个实现,不再拥有它了。
<强> 2.1。级分强>
将订单存储为(分子,分母)整数元组。要在项目xN / xD和yN / yD之间插入项目,请为其指定值(xN + yN)/(xD + yD)(可以很容易地显示在其他两个数字之间)。
限制:
精度或溢出。
function fractions(xN, xD, yN, yD, zN, zD){
for (var i = 0; i < 10000000; i++) {
//x,y,z
//x->v1: y,v1,z
//z->v2: y,v2,v1
var v1N = yN + zN, v1D = yD + zD
var v2N = yN + v1N, v2D = yD + v1D
xN = yN, xD=yD
yN = v2N, yD=v2D
zN = v1N, zd=v1D
if (!isFinite(xN) || !isFinite(xD)) { // overflow
console.log(i)
break
}
if (xN/xD == yN/yD) { //precision
console.log(i)
break
}
}
}
>fractions(1,1,3,2,2,1)
>737
<强> 2.2。减少GCD的分数
与上述相同,但使用最大公共计数器算法减少分数:
function gcd(x, y) {
if(!isFinite(x) || !isFinite(y)) {
return NaN
}
while (y != 0) {
var z = x % y;
x = y;
y = z;
}
return x;
}
function fractionsGCD(xN, xD, yN, yD, zN, zD) {
for (var i = 0; i < 10000000; i++) {
//x,y,z
//x->v1: y,v1,z
//z->v2: y,v2,v1
var v1N = yN + zN, v1D = yD + zD
var v2N = yN + v1N, v2D = yD + v1D
var v1gcd=gcd(v1N, v1D)
var v2gcd=gcd(v2N, v2D)
xN = yN, xD = yD
yN = v2N/v2gcd, yD=v2D/v2gcd
zN = v1N/v1gcd, zd=v1D/v1gcd
if (!isFinite(xN) || !isFinite(xD)) { // overflow
console.log(i)
break
}
if (xN/xD == yN/yD) { //precision
console.log(i)
break
}
}
}
>fractionsGCD(1,1,3,2,2,1)
>6795
第3。字母强>
使用字母顺序。这个想法是从一个字母表开始(比如,[32..126]的ascii可打印范围),并增长字符串。因此,('O'是我们范围的中间),在“a”和“c”之间插入,使用“b”,在“a”和“b”之间插入,使用“aO”,依此类推。
限制:
字符串会长到不适合数据库。
function middle(low, high) {
for(var i = 0; i < high.length; i++) {
if (i == low.length) {
//aa vs aaf
lowcode=32
hicode = high.charCodeAt(i)
return low + String.fromCharCode( (hicode - lowcode) / 2)
}
lowcode = low.charCodeAt(i)
hicode = high.charCodeAt(i)
if(lowcode==hicode) {
continue
}
else if(hicode - lowcode == 1) {
// aa vs ab
return low + 'O';
} else {
// aa vs aq
return low.slice(0,i) + String.fromCharCode(lowcode + (hicode - lowcode) / 2)
}
}
}
function alpha(x,y,z, N) {
for (var i = 0; i < 10000; i++) {
//x,y,z
//x->v1: y,v1,z
//z->v2: y,v2,v1
var v1 = middle(y, z)
var v2 = middle(y, v1)
x = y
y = v2
z = v1
if(x.length > N) {
console.log(i)
break
}
}
}
>alpha('?', 'O', '_', 256)
1023
>alpha('?', 'O', '_', 512)
2047
答案 1 :(得分:2)
也许我错过了一些基本的东西,我承认我对javascript知之甚少,但你肯定可以实现一个双向链表来处理这个问题吗?然后重新排序a,b,c,d,e,f,g,h以在d和e之间插入X,您只需取消链接d-> e,链接d-> X然后链接X-&gt; e和等等。
因为在上面的任何一个场景中,要么你的精确度都会耗尽(你的无限排序会丢失),要么就会得到很长的排序标识符而没有内存:)
答案 2 :(得分:1)
软件公理#1:保持简单直到你找到一个引人注目的,真实的,可靠的理由让它变得更复杂。
所以,我认为当DOM已经为你做这件事时,维护你自己的订单属性是额外的和不必要的代码和维护。为什么不让DOM维持秩序,你可以随时为当前的订单动态生成一组脑死亡的简单序列号? CPU可以快速生成所有项目的新序列号,无论何时需要或随时更改。而且,如果您想在服务器上保存这个新的顺序,只需将整个序列发送到服务器。
实现其中一个拆分序列,这样你总是可以插入更多的对象,而无需重新编号,这将是很多代码和很多bug的机会。在证明你确实需要这种复杂程度之前,你不应该去那里。
答案 3 :(得分:0)
将项目存储在数组中,并使用splice()
插入和删除元素。
或者这是不可接受的,因为您在回复链接列表时所做的评论?
答案 4 :(得分:0)
您尝试解决的问题是潜在的插入排序,它具有简单的O(n ^ 2)实现。但是有一些方法可以改善它。 假设有一个与每个元素关联的订单变量。您可以巧妙地为这些订单分配变量之间的大间隙,并获得一个摊销的O(n * log(n))机制。看(Insertion sort is nlogn)