Запутался в массивах на javascript

#javascript #arrays

#javascript #массивы

Вопрос:

Я хочу создать следующий массив на основе еще 2 массивов:

 array1 = ['a', 'b']
array2 = [1,2,3]
  

Я хочу создать следующий массив

 newArray = [['a',1], ['a',2], ['a',3], ['b',1], ['b',2], ['b',3]]
  

Вот мой код:

     var test1 = ['a', 'b'];
    var test2 = [1,2,3], arr1 = [], arr2 = [];

    for(var i = 0; i < test1.length; i  ){
       arr1 = [];
       arr1.push(test1[i]);
      for(var x = 0; x < test2.length; x  ){
        if(arr1.length > 1)
        	arr1.pop();
        arr1.push(test2[x])
        arr2.push(arr1);
        
      }
    }

console.log("arr1:",JSON.stringify(arr1), "arr2:" ,JSON.stringify(arr2));  

Но он возвращает последний элемент второго массива.

 [['a',3], ['a',3], ['a',3], ['b',3], ['b',3], ['b',3]]
  

Почему это происходит?

Ответ №1:

Любой другой ответ о длинах массивов и тому подобных вещах неверен. Единственная причина, по которой вы получаете 3 (или независимо от последнего значения / длины) повсюду, заключается в том, что массивы по ссылке, а functions не for-loops создают лексическую область. Это одна из причин, по которой вы слышите, что «функции являются первоклассными гражданами в javascript». Это классический пример, который часто используется в интервью, чтобы сбить с толку разработчиков, которые не привыкли к тому, как на самом деле работает scoping в javascript. Есть несколько способов исправить это, которые включают в себя обертывание внутренних элементов циклов в функциональных областях и передачу индекса, но я хотел бы предложить более «ориентированный на javascript» подход, который заключается в решении проблемы с функциями.

Посмотрите этот пример (который, кстати, также является четким способом реализации вашей цели.)

 var test1 = ['a', 'b'];
var test2 = [1,2,3];
    
// this will iterate over array 1 and return
// [[ [a,1],[a,2],[a,3] ],[ [b,1],[b,2],[b,3] ]]
var merged = test1.map(function(item1){
    return test2.map(function(item2){
        return [item1, item2];
    });	
  });

//this is a slick way to 'flatten' the result set
var answer = [].concat.apply([],merged )
    
console.log(answer) //this is it.  

Функции () создают область видимости, а не «скобки» {} . Обычно самое простое решение — использовать функции для решения ваших проблем, поскольку они создают лексическую область видимости. Например, самая надежная библиотека в npm, Lodash, основана на этом. Я думаю, что вы будете писать все меньше и меньше циклов изо дня в день js по мере продвижения и использовать больше функций.

Рабочий пример на скрипке js: https://jsfiddle.net/3z0hh12y/1 /

Вы можете прочитать больше о областях и замыканиях здесь https://github.com/getify/You-Dont-Know-JS/blob/master/scope & closures/ch1.md

И еще одна вещь: когда вы думаете, что хотите цикл в js, вы обычно хотите Array.map() , особенно если вы переназначаете значения.

Комментарии:

1. хороший! должно ли это быть return [item1, item2] для создания массива, как просил OP?

2. позвольте мне дважды проверить это, похоже, да, у меня это было в моей скрипке, редактируя. Теперь он обновлен. Похоже, я их перевернул. Рабочая демонстрация и ответ должны быть правильными.

3. @J.D.Pace интересно, что Lodash часто превосходит native .map, если вам интересно. Отличная библиотека и даже имеет функцию .flatten(), чтобы избежать необходимости использовать этот хитрый трюк с выравниванием — гораздо легче читать. Я рекомендую Lodash для такого рода операций

4. @the_5imian Я посмотрел на Lodash для других проектов. Возможно, пришло время вернуться к нему. Проверьте синтаксис в ответе, описывающем декартово произведение.

5. @J.D.Pace в основном, как Lodash справится с этим. Кроме того, функциональные манипуляции rx.js с , most.js , highland.js и многими другими библиотеками также делают это. Этот ответ похож на то, как это делают многие серьезные библиотеки — единственная причина, по которой я не написал именно это, заключается в том, что я хотел объяснить «почему» этого и ответить на родном JS (для всех, кто приходит). В настоящее время для наблюдаемых и функционального преобразования, most.js это самое быстрое, что мне известно на данный момент. Взгляните на motorcyle.js для примера в контексте FRP.

Ответ №2:

Ваша проблема заключается во втором цикле. поскольку у вас есть единственная ссылка на ary1, вы переписываете значение с каждым циклом.

итак, при первом цикле ваш массив будет иметь

 [['a',1]]
  

но поскольку у вас есть только одна ссылка на ary1, вы просто редактируете значение, которое вы только что ввели в ary2.

итак, тогда вы получаете это:

 [['a',2],['a',2]]
  

вы можете подумать, что вводите новый массив, но на самом деле это тот же самый массив! итак, шаблон продолжается, и вы получаете результат, который вы видите.

Ответ №3:

Это происходит потому, что вы удаляете элементы из arr1, как только его длина превышает 1, поэтому сохраняется только последний элемент.

Попробуйте это:

 var test1 = ['a', 'b'];
var test2 = [1,2,3];
var arr1 = [], arr2 = [];

for(var i = 0; i < test1.length; i  ) {
    for(var x = 0; x < test2.length; x  ) {
        arr1[arr1.length] = [test1[i], test2[x]];
    }
}
console.log(JSON.stringify(arr1));  

Ответ №4:

Помимо решений для фиксированного стиля для двух массивов, вы можете использовать другой подход для получения результата. вы могли бы использовать итеративный и рекурсивный подход для переменной длины частей и их длины.

 function combine(array) {
    function c(part, index) {
        array[index].forEach(function (a) {
            var p = part.concat([a]);
            if (p.length === array.length) {
                r.push(p);
                return;
            }
            c(p, index   1);
        });
    }

    var r = [];

    c([], 0);
    return r;
}

console.log(combine([['a', 'b'], [1, 2, 3]]));
console.log(combine([['a', 'b', 'c'], ['1', '2', '3', '4'], ['A', 'B']]));
console.log(combine([['a', 'b', 'c'], ['1', '2', '3', '4'], [['A'], ['B']]]));  
 .as-console-wrapper { max-height: 100% !important; top: 0; }  

Комментарии:

1. Здесь одна небольшая проблема. Если ваши элементы массива являются массивами, то .concat() они будут разворачиваться. ie попробуйте [['a', 'b', 'c'], ['1', '2', '3', '4'], [['A'], ['B']]] . У меня такая же проблема в этом , не могли бы вы помочь ..?

2. @redu, в приведенном выше случае вы могли бы заключить элемент в квадратные скобки, прежде чем объединять их, например var p = part.concat([a]); , возможно, это также поможет в вашей проблеме (concar уменьшает только один уровень массива).

3. Вы упорядочиваете все элементы там? ДА.. Это то, о чем я думал, но мне показалось грязным взломом. Хм, нет .. если подумать, в вашем случае это выполнимо, я полагаю.

4. это просто обходной путь для автоматического оператора rest of concat . да, это выглядит немного банально.

Ответ №5:

Я думаю, что правильный функциональный подход был бы выполнен с помощью одного liner reduce и nested map duo.

 var arr = ['a', 'b'],
    brr = [1,2,3],
 result = arr.reduce((p,c) => p.concat(brr.map(e => [c,e])),[]);
console.log(JSON.stringify(result));  

Ответ №6:

ваш подход немного отличается, вам лучше воспользоваться этим подходом (который, я думаю, довольно прост): DEMO

 var array1 = ['a', 'b'],
    array2 = [1,2,3],
    nextArray=[];

for(var i=0, array1Length=array1.length; i < array1Length; i  ){
    for(var x=0, array2Length=array2.length; x < array2Length; x  ){
    nextArray.push([array1[i],array2[x]]);
  }
}

console.log(nextArray);
  

Комментарии:

1. Не могли бы вы хотя бы объяснить, почему это «выключено»? Вот что на самом деле задает вопрос.

Ответ №7:

Да, это намного сложнее, чем должно быть. Результат, который вы хотите, называется декартовым произведением и может быть сгенерирован следующим образом:

 const cartesianProduct = (a, b) => {
  const rv = [];
  a.map(ae => b.map(be => rv.push([ae, be])));
  return rv;
};

const array1 = ['a', 'b']
const array2 = [1,2,3]
console.log(JSON.stringify(cartesianProduct(array1, array2)));  

Если бы Javascript6 имел flatMap , это было бы еще проще:

  const cartesianProduct = (a, b) => 
    a.flatMap(ae => b.map(be => [ae, be]));
  

Комментарии:

1. Пожалуйста, объясните мне синтаксис =>.

2. @J.D.Pace Это функция со стрелкой в ES6.

3. Это просто использование map в качестве итератора по внешнему массиву. Не очень элегантно.

4. @Redu — как я уже сказал, было бы лучше, если бы в языке была функция flatMap() или если вы добавите ее (используя подчеркивание или тому подобное).

Ответ №8:

Я немного изменил ваш код (но не слишком сильно, я старался придерживаться того же подхода). Решением было создать новый array ( arr1 ) во внутреннем цикле. В противном случае первая правильная пара ['a', 1] будет переопределена сразу после того, как begin нажал arr2 на следующий проход цикла.

 var test1 = ['a', 'b'];
var test2 = [1,2,3];
var arr2 = [];

for(var i = 0; i < test1.length; i  ){
    for(var x = 0; x < test2.length; x  ){
        var arr1 = [];
        arr1.push(test1[i]);

        if(arr1.length > 1){
           arr1.pop();
        }
        arr1.push(test2[x]);
        arr2.push(arr1);
    }
}