考虑矩阵
[
1 2 3 0 0 0
2 1 2 4 0 0
3 2 1 2 5 0
0 7 2 1 2 6
0 0 8 2 1 2
0 0 0 9 2 1
]
和向量 v [1,2,3,4,5,6]
对于每一行,v 的涉及系数下方:
[1,2,3]
[1,2,3,4]
[1,2,3,4,5]
[ 2,3,4,5,6]
[ 3,4,5,6]
[ 4,5,6]
如您所见,您只需要跟踪v 的窗口。
那个窗口原来是[1,2,3,4,5] (for i = 0, 1, 2)
然后,每隔i 将该窗口向右移动一次(并最终将其截断以使最后一行不超出v 的范围...)
现在请注意,当您向右移动时,您只需要知道来自v 的下一个值,并且只要您没有弄脏该值(通过写入v),您的新窗口就有效。
对于行i,窗口为[i-n;i+n],要修改的系数为v[i]。对于下一个窗口,您需要知道没有被弄脏的v[i+n+1]。一切都很好。
所以算法就像
window = circularbuffer(2n+1) //you push to the right, and if length > 2n+1, you remove first elem
for i = 0; i<v.size()
v[i] = prod(row_i, window) // only for the row_i coeffs...
if i >= n && < M-3
window.push(v[i+n+1])
else if i>= M-3
window.shift() // just remove the first value
const N = 2
const M_SIZE = 10
function toString(M){
return M.map(x=>x.join(' ')).join('\n')
}
const { M, v } = (_ => {
const M = Array(M_SIZE).fill(0).map(x=>Array(M_SIZE).fill(0))
let z = 1
for(let i = 0; i<M_SIZE; ++i){
for(let j = -N; j<=N; ++j){
if(i+j >= 0 && i+j <M_SIZE){
M[i][i+j] = (z++ % (N*2))+1
}
}
}
const v = Array(M.length).fill(0).map((x,i)=>i)
return { M, v}
})()
function classic(M, v){
return M.map(r => r.reduce((acc, x, j) => acc + v[j]*x, 0))
}
function inplace(M, v){
// captn inefficiency
const circBuf = (init => {
let buf = init
return {
push (x) {
buf.push(x)
buf.shift()
},
shift() {
buf.shift()
},
at (i) { return buf[i] },
toString() {
return buf.join(' ')
}
}
})(v.slice(0, 2 * N + 1))
const sparseProd = (row, buf) => {
let s = 0
row.forEach((x, j) => s += x * buf.at(j))
return s
}
const sparseRows = M.map(r => r.filter(x => x !== 0))
sparseRows.forEach((row, i) => {
v[i] = sparseProd(row, circBuf)
if (i >= sparseRows.length - 3 ) {
circBuf.shift()
} else {
if (i >= N) {
circBuf.push(v[i + N + 1])
}
}
})
}
console.log('classic prod', classic(M, v))
inplace(M, v)
console.log('inplace prod', v)