2011-01-13 28 views
18

Chỉnh sửa: Tôi xin lỗi, nhưng tôi quên đề cập đến rằng tôi sẽ cần giá trị của biến số lượt truy cập. Vì vậy, làm cho một vòng lặp không phải là một giải pháp tôi sợ.Số lượng biến được lồng trong vòng lặp

Tôi không chắc chắn nếu điều này là có thể ở tất cả, nhưng tôi muốn làm như sau. Để một hàm, một dãy số được chuyển. Mỗi số là giới hạn trên của một vòng lặp for, ví dụ, nếu mảng là [2, 3, 5], mã sau đây cần được thực hiện:

for(var a = 0; a < 2; a++) { 
    for(var b = 0; b < 3; b++) { 
      for(var c = 0; c < 5; c++) { 
       doSomething([a, b, c]); 
      } 
    } 
} 

Vì vậy, số lượng lồng nhau cho vòng là tương đương với chiều dài của mảng. Có cách nào để thực hiện công việc này không? Tôi đã suy nghĩ của việc tạo ra một đoạn mã mà thêm mỗi vòng lặp cho một chuỗi, và sau đó đánh giá nó thông qua eval. Tuy nhiên, tôi đã đọc rằng eval không phải là lựa chọn đầu tiên vì nó cũng có thể có kết quả nguy hiểm.

Kỹ thuật nào có thể phù hợp ở đây?

+2

Vì vậy, bạn chỉ muốn gọi một số chức năng một số lần tương đương với sản phẩm của các số trong một mảng thông qua trong? – Sean

+0

Không, tôi xin lỗi. Tôi cũng sẽ cần các biến của các vòng lặp (a, b và c ở đây). – pimvdb

Trả lời

17

Recursion có thể giải quyết vấn đề này gọn gàng:

function callManyTimes(maxIndices, func) { 
    doCallManyTimes(maxIndices, func, [], 0); 
} 

function doCallManyTimes(maxIndices, func, args, index) { 
    if (maxIndices.length == 0) { 
     func(args); 
    } else { 
     var rest = maxIndices.slice(1); 
     for (args[index] = 0; args[index] < maxIndices[0]; ++args[index]) { 
      doCallManyTimes(rest, func, args, index + 1); 
     } 
    } 
} 

Gọi như sau:

callManyTimes([2,3,5], doSomething); 
+0

Giải pháp của bạn cũng hoạt động như một sự quyến rũ. Thực ra bạn là người sạch nhất và dễ hiểu hơn đối với tôi. Cảm ơn rất nhiều – pimvdb

+0

Giải pháp tuyệt vời, giải pháp của bạn cũng là giải pháp nhanh nhất được đề xuất.Thử nghiệm của tôi (unscientific) cho thấy rằng nếu chúng ta lấy vòng lặp lồng nhau như là một điểm chuẩn 'X', thì: Sean:' 4X', Guffa: '8X', Mike Samuel:' 15X', Pointy: '28X'. – serg

3

Thiết lập một loạt các bộ đếm có cùng độ dài với mảng giới hạn. Sử dụng một vòng lặp đơn và tăng mục cuối cùng trong mỗi lần lặp. Khi nó đạt đến giới hạn của nó, bạn khởi động lại nó và tăng mục tiếp theo.

function loop(limits) { 
    var cnt = new Array(limits.length); 
    for (var i = 0; i < cnt.length; i++) cnt[i] = 0; 
    var pos; 
    do { 
    doSomething(cnt); 
    pos = cnt.length - 1; 
    cnt[pos]++; 
    while (pos >= 0 && cnt[pos] >= limits[pos]) { 
     cnt[pos] = 0; 
     pos--; 
     if (pos >= 0) cnt[pos]++; 
    } 
    } while (pos >= 0); 
} 
1

Không có sự khác biệt giữa thực hiện ba vòng 2, 3, 5 và một vòng lặp 30 (2 * 3 * 5).

function doLots (howMany, what) { 
    var amount = 0; 

    // Aggregate amount 
    for (var i=0; i<howMany.length;i++) { 
     amount *= howMany[i]; 
    }; 

    // Execute that many times.  
    while(i--) { 
     what(); 
    }; 
} 

Sử dụng:

doLots([2,3,5], doSomething); 
+1

Tôi thực sự xin lỗi, nhưng tôi cũng sẽ cần các giá trị của biến số lượt truy cập. Mặc dù tôi yêu giải pháp của bạn, thông tin đó bị mất. – pimvdb

+0

Bạn đánh tôi với nó. Ngoài ra, bạn cần loại thông tin nào? Bạn có thể đơn giản giữ tất cả các số nguyên trong mảng ban đầu mà bạn có không? Hay bạn cần phải tổ chức chúng theo một cách khác? – user535617

+0

Tôi đang cố gắng tạo một hàm mảng đa chiều chung, vì vậy tôi sẽ cần phải lấp đầy từng kết hợp các chỉ mục với một giá trị. Do đó lồng nhau cho các vòng lặp. Một cho vòng lặp làm cho các chỉ số bị mất và chỉ trả về một chỉ số (0 - 30 ở đây) – pimvdb

2

Thay vì nghĩ về lồng nhau for vòng, suy nghĩ về lời gọi hàm đệ quy. Để làm được lặp đi lặp lại, bạn muốn đưa ra quyết định (mã giả) sau:

if the list of counters is empty 
    then "doSomething()" 
else 
    for (counter = 0 to first counter limit in the list) 
     recurse with the tail of the list 

Đó có thể trông giống như thế này:

function forEachCounter(counters, fn) { 
    function impl(counters, curCount) { 
    if (counters.length === 0) 
     fn(curCount); 
    else { 
     var limit = counters[0]; 
     curCount.push(0); 
     for (var i = 0; i < limit; ++i) { 
     curCount[curCount.length - 1] = i; 
     impl(counters.slice(1), curCount); 
     } 
     curCount.length--; 
    } 
    } 
    impl(counters, []); 
} 

Bạn muốn gọi hàm với một cuộc tranh cãi đó là danh sách của bạn giới hạn đếm, và một đối số là hàm của bạn để thực hiện cho mỗi mảng đếm hiệu quả (phần "doSomething"). Hàm main ở trên thực hiện tất cả công việc thực tế trong một hàm bên trong. Trong hàm bên trong đó, đối số đầu tiên là danh sách giới hạn truy cập, mà sẽ được "cắt xuống" khi hàm được gọi đệ quy. Đối số thứ hai được sử dụng để giữ giá trị bộ đếm truy cập hiện tại, do đó "doSomething" có thể biết rằng đó là một lần lặp tương ứng với danh sách số lượng thực tế cụ thể.

Gọi chức năng sẽ trông như thế này:

forEachCounter([4, 2, 5], function(c) { /* something */ }); 
+1

Điều đó nghe có vẻ thú vị, cảm ơn rất nhiều. – pimvdb

1

Một giải pháp mà làm việc mà không bị phức tạp programatically sẽ được lấy nguyên và nhân tất cả.Vì bạn đang chỉ làm tổ của IFS, và chỉ có một trong cùng có chức năng, điều này sẽ làm việc:

var product = 0; 
for(var i = 0; i < array.length; i++){ 
    product *= array[i]; 
} 

for(var i = 0; i < product; i++){ 
    doSomething(); 
} 

Hoặc:

for(var i = 0; i < array.length; i++){ 
    for(var j = 0; j < array[i]; j++){ 
     doSomething(); 
    } 
} 
7

Đệ quy là quá mức cần thiết ở đây. Một giải pháp nhanh hơn nhiều:

function allPossibleCombinations(lengths, fn) { 
 
    var n = lengths.length; 
 

 
    var indices = []; 
 
    for (var i = n; --i >= 0;) { 
 
    if (lengths[i] === 0) { return; } 
 
    if (lengths[i] !== (lengths[i] & 0x7ffffffff)) { throw new Error(); } 
 
    indices[i] = 0; 
 
    } 
 

 
    while (true) { 
 
    fn.apply(null, indices); 
 
    // Increment indices. 
 
    ++indices[n - 1]; 
 
    for (var j = n; --j >= 0 && indices[j] === lengths[j];) { 
 
     if (j === 0) { return; } 
 
     indices[j] = 0; 
 
     ++indices[j - 1]; 
 
    } 
 
    } 
 
} 
 

 
allPossibleCombinations([3, 2, 2], function(a, b, c) { console.log(a + ',' + b + ',' + c); })

+0

Đó cũng là một cách khéo léo. Có thể bạn có thể giải thích mặc dù những gì bạn đang làm với 0x7fffffff? – pimvdb

+0

@pimvdb, đảm bảo rằng độ dài là số nguyên không âm để kiểm tra 'chỉ số [j] === độ dài [j]' bên dưới có cơ hội vượt qua. –

+0

Tôi thấy một số cách để ngưng tụ điều này và làm cho nó linh hoạt hơn cho trường hợp sử dụng của tôi: https://stackoverflow.com/a/44753698/552067 Cảm ơn Mike! –

0

Bạn có thể sử dụng các thuật toán tham lam để liệt kê tất cả các yếu tố của sản phẩm Descartes 0: 2 x 0: 3 x 0: 5. Thuật toán này được thực hiện bởi hàm của tôi greedy_backward bên dưới. Tôi không phải là một chuyên gia về Javascript và có lẽ chức năng này có thể được cải thiện.

function greedy_backward(sizes, n) { 
    for (var G = [1], i = 0; i<sizes.length; i++) G[i+1] = G[i] * sizes[i]; 
    if (n>=_.last(G)) throw new Error("n must be <" + _.last(G)); 
    for (i = 0; i<sizes.length; i++) if (sizes[i]!=parseInt(sizes[i]) || sizes[i]<1){ throw new Error("sizes must be a vector of integers be >1"); }; 
    for (var epsilon=[], i=0; i < sizes.length; i++) epsilon[i]=0; 
    while(n > 0){ 
    var k = _.findIndex(G, function(x){ return n < x; }) - 1; 
    var e = (n/G[k])>>0; 
    epsilon[k] = e; 
    n = n-e*G[k]; 
    } 
    return epsilon; 
} 

Nó liệt kê các yếu tố của sản phẩm Descartes theo thứ tự chống tự từ điển (bạn sẽ thấy toàn bộ liệt kê trong ví dụ doSomething):

~ var sizes = [2, 3, 5]; 
~ greedy_backward(sizes,0); 
0,0,0 
~ greedy_backward(sizes,1); 
1,0,0 
~ greedy_backward(sizes,2); 
0,1,0 
~ greedy_backward(sizes,3); 
1,1,0 
~ greedy_backward(sizes,4); 
0,2,0 
~ greedy_backward(sizes,5); 
1,2,0 

Đây là một sự tổng quát của các đại diện nhị phân (trường hợp khi sizes=[2,2,2,...]).

Ví dụ:

~ function doSomething(v){ 
    for (var message = v[0], i = 1; i<v.length; i++) message = message + '-' + v[i].toString(); 
    console.log(message); 
    } 
~ doSomething(["a","b","c"]) 
a-b-c 
~ for (var max = [1], i = 0; i<sizes.length; i++) max = max * sizes[i]; 
30 
~ for(i=0; i<max; i++){ 
    doSomething(greedy_backward(sizes,i)); 
    } 
0-0-0 
1-0-0 
0-1-0 
1-1-0 
0-2-0 
1-2-0 
0-0-1 
1-0-1 
0-1-1 
1-1-1 
0-2-1 
1-2-1 
0-0-2 
1-0-2 
0-1-2 
1-1-2 
0-2-2 
1-2-2 
0-0-3 
1-0-3 
0-1-3 
1-1-3 
0-2-3 
1-2-3 
0-0-4 
1-0-4 
0-1-4 
1-1-4 
0-2-4 
1-2-4 

Nếu cần thiết, quá trình ngược lại rất đơn giản:

function greedy_forward(sizes, epsilon) { 
    if (sizes.length!=epsilon.length) throw new Error("sizes and epsilon must have the same length"); 
    for (i = 0; i<sizes.length; i++) if (epsilon[i] <0 || epsilon[i] >= sizes[i]){ throw new Error("condition `0 <= epsilon[i] < sizes[i]` not fulfilled for all i"); }; 
    for (var G = [1], i = 0; i<sizes.length-1; i++) G[i+1] = G[i] * sizes[i]; 
    for (var n = 0, i = 0; i<sizes.length; i++) n += G[i] * epsilon[i]; 
    return n; 
} 

Ví dụ:

~ epsilon = greedy_backward(sizes, 29) 
1,2,4 
~ greedy_forward(sizes, epsilon) 
29 
0

Đây là nỗ lực của tôi tại đơn giản hóa phi đệ quy solution by Mike Samuel. Tôi cũng thêm khả năng thiết lập một phạm vi (không chỉ tối đa) cho mỗi đối số nguyên.

function everyPermutation(args, fn) { 
 
    var indices = args.map(a => a.min); 
 

 
    for (var j = args.length; j >= 0;) { 
 
     fn.apply(null, indices); 
 

 
     // go through indices from right to left setting them to 0 
 
     for (j = args.length; j--;) { 
 
      // until we find the last index not at max which we increment 
 
      if (indices[j] < args[j].max) { 
 
       ++indices[j]; 
 
       break; 
 
      } 
 
      indices[j] = args[j].min; 
 
     } 
 
    } 
 
} 
 

 
everyPermutation([ 
 
    {min:4, max:6}, 
 
    {min:2, max:3}, 
 
    {min:0, max:1} 
 
], function(a, b, c) { 
 
    console.log(a + ',' + b + ',' + c); 
 
});

Các vấn đề liên quan