JavaScript递归用于洗钱检测。

7
我正在进行一项统计分析,以确定在某个时间范围内,是否通过将较大的交易拆分成几个较小的交易来隐藏它的可能性。我的做法是将较大的数据集分成较小的子集(目前是12个数组),然后对每个子集运行一系列循环,以确定任何元素的组合是否能够达到目标范围。
以下是我的当前代码:
amounts_matrix = [1380.54,9583.33,37993.04,3240.96...]


matrix_amounts = amounts_matrix.length
total_permutations = 0;
total_hits = 0;
target_range = 1
target = 130000
low_threshold = target - target_range
high_threshold = target + target_range
entries = []
range = 12


for(x = 0; x< matrix_amounts-(range-1); x++){
    amounts = amounts_matrix.slice(x, x+range)
    total_amounts = range


    for(i = 0; i< total_amounts; i++){
        entries.push(amounts[i])
        totalcheck(entries)
        entries = []
    }

    for(i = 0; i< total_amounts; i++){
        for(j = i+1; j< total_amounts; j++){
            entries.push(amounts[i])
            entries.push(amounts[j])
            totalcheck(entries)
            entries = []
        }
    }

    ...

    for(i = 0; i< total_amounts; i++){
        for(j = i+1; j< total_amounts; j++){
            for(k = j+1; k< total_amounts; k++){
                for(l = k+1; l< total_amounts; l++){
                    for(m = l+1; m< total_amounts; m++){
                        for(n = m+1; n< total_amounts; n++){
                            for(o = n+1; o< total_amounts; o++){
                                for(p = o+1; p< total_amounts;p++){
                                    for(q = p+1; q< total_amounts;q++){
                                        for(r = q+1; r< total_amounts;r++){
                                            for(s = r+1; s< total_amounts;s++){
                                                for(t = s+1; t< total_amounts;t++){
                                                    entries.push(amounts[i])
                                                    entries.push(amounts[j])
                                                    entries.push(amounts[k])
                                                    entries.push(amounts[l])
                                                    entries.push(amounts[m])
                                                    entries.push(amounts[n])
                                                    entries.push(amounts[o])
                                                    entries.push(amounts[p])
                                                    entries.push(amounts[q])
                                                    entries.push(amounts[r])
                                                    entries.push(amounts[s])
                                                    entries.push(amounts[t])
                                                    totalcheck(entries)
                                                    entries = []
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }


}


function totalcheck(array){ 

    total_permutations += 1;

    sum_amount = 0
    for(z = 0; z < array.length; z++){
        sum_amount += array[z]
    }

    if(sum_amount > low_threshold && sum_amount < high_threshold){
        total_hits += 1
        console.log(array)
        console.log(sum_amount.toFixed(2))
        console.log("---------------")
    }

}

console.log("overall total hits = " + total_hits)
console.log("overall total permutations = " + total_permutations)

我对那些循环的深度感到非常尴尬,我想用一个函数来概括它们,只需告诉它要运行X个循环,而不必像这样一步步构建。 我发现的排列函数对我来说都不太可行,因为它们都构建了包含完整可能性的数组; 在我的函数中,我想要在进行循环时针对目标进行检查,以避免出现巨大的数组并遇到内存问题。 我该如何构建一个递归循环来实现这一点?


你不能合理地创建一个用于创建循环的函数,eval() 函数可能会对你有所帮助,但是一个可以操作运行次数的函数可以很容易地实现,并且甚至可以提供回调函数以为每个循环提供独特的功能。 - Ben
因为我想要能够使用比仅仅12个窗口更大的窗口,这将使我进入50!长甚至更大的数组领域。 - asetniop
当给定一个基础集合如[1,2,3,4]和目标值为7时,它应该输出[3,4]和[1,2,4]。 - asetniop
排序不是一个选项,财务方面意味着它们必须保持在原始序列中,以便您可以在一定时间范围内检查组合。 - asetniop
1
我理解 'amounts_matrix' 不能被重新组织,但是 'amounts' 是你从中进行循环的子采样数组,除非我弄错了。这就是我建议你排序的原因,它会显著提高效率,并包含逻辑。 - Attack68
显示剩余5条评论
4个回答

1
你可以建立一个索引列表,然后进行检查:
 const positions = Array.from({length: 12}, (_, i) => i);

现在我们需要获取最高索引,将其增加,当达到数组上限时,我们增加第二个最高索引,以此类推,这样我们就可以慢慢地遍历所有组合:
 function next(){
   for(let i = positions.length - 1; i >= 0; i--){
      if(positions[i] < amounts.length){
        positions[i]++;
        return true;
      }
      if(i == 0) return false;
      positions[i] = positions[i - 1] + 2;
   }
 }

如果这看起来有点可怕,在这里尝试一下 现在我们已经得到了索引,我们只需要将它们所指向的数组值相加,直到找到我们的目标:
  do {
    const sum = positions.reduce((sum, pos) => sum + amounts[pos], 0);
    if(sum === target) break;
  } while(next())

要获取所有不同长度的排列和,只需使用不同长度多次运行整个程序。

这看起来非常有前途。让我好好琢磨一下,但我认为它会解决问题的。 - asetniop
它变得有些奇怪,似乎无法保持在参数范围内(最后一列的计数最终会变得太高;应该最大为10,但却上升到11和12)。不过,我将此方法作为我的答案基础,所以谢谢你! - asetniop

1

我最终采用了Jonas W的索引建议,并且以下是对我有效的内容。我可以通过更改range_window变量来更改窗口大小。

const amounts_matrix = [1380.54,9583.33,37993.04,3240.96,9583.33,814.24,6000.00.....


total_permutations = 0;
total_hits = 0;
target_range = 1
target = 130000
low_threshold = target - target_range
high_threshold = target + target_range
range_window = 12
batch_max = 12


for(x = 0; x < amounts_matrix.length-(range_window-1); x++){
    amounts = amounts_matrix.slice(x, x + range_window)

    for(batch_size = 0; batch_size <= batch_max; batch_size++){

        const positions = Array.from({length: batch_size}, (_, i) => i);
        //calculate the upper thresholds for each position
        var position_thresholds = new Array(batch_size)
        for(i = 0; i < positions.length; i++){
            position_thresholds[i] = i + amounts.length - positions.length
        }   

        var position = positions[positions.length-1];

        while(positions[0] < position_thresholds[position]){
            stormy_loop(positions, position)
        }

    }
}


function stormy_loop(positions, position){
    if(positions[position] <= position_thresholds[position]){
        totalcheck(positions)
        positions[position] += 1;
    }
    else{
        while(positions[position] > position_thresholds[position]){
            position -= 1
            positions[position] += 1;
        }
        cascade(positions,position)   
    }
}

function cascade(positions,position){
    base = positions[position]
    for(i = position + 1; i < positions.length; i++){
        position += 1
        base += 1
        positions[position] = base;
    }
}


function totalcheck(array){ 

    total_permutations += 1;
    output_array = []

    sum_amount = 0
    for(z = 0; z < array.length; z++){
        sum_amount += amounts[array[z]]
        output_array.push(amounts[array[z]])
    }

    if(sum_amount > low_threshold && sum_amount < high_threshold){
        total_hits += 1
        console.log(output_array)
        console.log(sum_amount.toFixed(2))
        console.log("total hits = " + total_hits)
        console.log("total permutations = " + total_permutations)
        console.log("---------------")
    }

}

1
我总是很高兴看到有人真正理解了我的回答 :) 做得好! - Jonas Wilms

1

既然您已经将问题标记为“递归”,让我们来构建一个递归。

我们还可以假设我们将提供有序输入的函数,这样我们就可以避免所有的n个k子集,而选择在下一个数量太大时尽早退出。(如果输入未排序,我们可以在下面的函数中简单地删除对“太大”的检查。)

(请注意,JavaScript至少在浏览器中提供有限的递归深度,因此您可能需要考虑将该过程转换为显式堆栈迭代。)

// Returns indexes of elements that compose sums within the specified range
function f(amounts, i, low, high, k, sum, indexes){
  if (!k)
    return low < sum && sum < high ? [indexes] : [];
    
  if (i == amounts.length || amounts.length - i < k)
    return [];
  
  if (sum + amounts[i + 1] > high)
    return low < sum ? [indexes] : [];

  let _indexes = indexes.slice();
  _indexes.push(i);

  return f(amounts, i + 1, low, high, k - 1, sum + amounts[i], _indexes)
           .concat(f(amounts, i + 1, low, high, k, sum, indexes));
}

console.log(JSON.stringify(f([1,2,3,4], 0, 6, 8, 3, 0, [])));
console.log(JSON.stringify(f([1,2,3,4], 0, 4, 7, 2, 0, [])));
console.log(JSON.stringify(f([1,2,3,4], 0, 4, 7, 3, 0, [])));

上述版本将搜索限制为特定数量的交易,k。我最初发布的版本是针对一般的k,意味着任何基数的子集:

function f(amounts, i, low, high, sum, indexes){
  if (i == amounts.length)
    return low < sum && sum < high ? [indexes] : [];
  
  if (sum + amounts[i + 1] > high)
    return low < sum ? [indexes] : [];

  let _indexes = indexes.slice();
  _indexes.push(i);
  
  return f(amounts, i + 1, low, high, sum + amounts[i], _indexes)
           .concat(f(amounts, i + 1, low, high, sum, indexes));
}

console.log(JSON.stringify(f([1,2,3,4], 0, 6, 8, 0, [])));
console.log(JSON.stringify(f([1,2,3,4], 0, 4, 7, 0, [])));


0
 for(i = 0; i< total_amounts; i++){
        for(j = i+1; j< total_amounts; j++){
            for(k = j+1; k< total_amounts; k++){
                for(l = k+1; l< total_amounts; l++){
                    for(m = l+1; m< total_amounts; m++){
                        for(n = m+1; n< total_amounts; n++){
                            for(o = n+1; o< total_amounts; o++){
                                for(p = o+1; p< total_amounts;p++){
                                    for(q = p+1; q< total_amounts;q++){
                                        for(r = q+1; r< total_amounts;r++){
                                            for(s = r+1; s< total_amounts;s++){
                                                for(t = s+1; t< total_amounts;t++){
                                                    entries.push(amounts[i])
                                                    entries.push(amounts[j])
                                                    entries.push(amounts[k])
                                                    entries.push(amounts[l])
                                                    entries.push(amounts[m])
                                                    entries.push(amounts[n])
                                                    entries.push(amounts[o])
                                                    entries.push(amounts[p])
                                                    entries.push(amounts[q])
                                                    entries.push(amounts[r])
                                                    entries.push(amounts[s])
                                                    entries.push(amounts[t])
                                                    totalcheck(entries)
                                                    entries = []
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

可以变成这样

function loopinator(){
    for(i=0; i<total_amounts; i++){
        for(j=0; j<11; j++){//You had 11 loops plus root loop
            entries.push(amounts[( i+j )]);//Will increase with each iteration of j loop, simulating loop branching
        }
        //Will run at same time as it did before, after all nested roots, but before next iteration of i loop
        totalcheck(entries);
        entries = [];

    }
}

这似乎只针对直接顺序发生的事情;如果我的基本集是[1,2,3,4,5,6],目标是9,它将捕获[2,3,4]和[4,5],但不会捕获[1,2,6]和[3,6]。 - asetniop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接