2016-09-26 2 views
3

Есть ли функция, которая позволяет мне конкатрировать несколько массивов с разделителями между ними (разделители также являются массивами), подобно тому, как работает join, но не ограничивается строками?Присоединить последовательность массивов с разделителями массивов («intersperse»)

Эта функция может быть стандартной JS или частью основной библиотеки, такой как lodash (вот почему она упоминается в тегах).

Вот пример использования:

let numbers = [[1], [2], [3]]; 
let result = _.joinArrays(numbers, [0]); 
console.log(result); 
//printed: [1, 0, 2, 0, 3] 

Это аналогично:

let strings = ["a", "b", "c"]; 
let result = strings.join(","); 
console.log(result); 
//printed: "a,b,c"; 

Однако join не может быть использована, поскольку она превращает значения в строки, которые я не хочу случиться.

Но он работает для любого типа.

+1

добавьте еще несколько примеров. –

+1

Функциональное имя для того, чтобы вы хотели [interperse] (http://hackage.haskell.org/package/base-4.9.0.0/docs/Data-List.html#v:intersperse) массив с другим элементом. В настоящее время это [функция-запрос] (https://github.com/lodash/lodash/issues/2339) для Lodash, поэтому перейдите на него, если хотите, чтобы он был добавлен в библиотеку! – 4castle

+0

@ 4castle О, круто. Может быть, я отправлю запрос на тяну. Спасибо!! – GregRos

ответ

8

Вы могли бы просто использовать array.reduce для Concat массивы, и нажать, что вы хотите использовать в качестве разделителя.

let numbers = [[1], [2], [3]]; 
 

 
let n = numbers.reduce((a, b) => a.concat(0, b)) 
 

 
console.log(n)

+0

Проще говоря: 'return a.concat (0, b)' – 4castle

+0

@ 4castle - Хороший, очевидно. Спасибо. ;) – DavidDomain

+0

Это не ответит на вопрос, к сожалению. Это была моя первая мысль. Хотя было бы лучше применить concat ко всем подмассивам сразу ('Array.prototype.concat.apply'). – TylerY86

3

Matrix Interspersion

Вот полный Monty. Сходить с ума.

var numbers = [ 
 
    [1, 2, 3], 
 
    [4, 5, 6], 
 
    [7, 8, 9] 
 
], 
 
    delimiters = [ 
 
    ',', '-', 'x' 
 
]; 
 

 
// matrix interspersion, delimiters into numbers's children 
 
// the rank/order/whatevs of the matrix can be arbitrary and variable 
 
numbers.forEach((x, i) => { 
 
    for (var j = 1, l = x.length; j <= l; j+=2) 
 
    x.splice(j, 0, delimiters[i]); 
 
}) 
 

 
alert("Matrix interspersed: " + JSON.stringify(numbers)); 
 

 
// normal interspersion, a static delimiter into numbers 
 
for (var j = 1, l = numbers.length; j <= l; j+=2) 
 
    numbers.splice(j, 0, ' AND '); 
 

 
alert("Outer array interspersed: " + JSON.stringify(numbers)); 
 

 
// flattening a 2 rank array into a single array 
 
var flattened = Array.prototype.concat.apply([], numbers); 
 

 
alert("Flattened: " + JSON.stringify(flattened));

+0

Я не могу использовать join, потому что он превращает материал в строки. – GregRos

+0

О, так вы хотите, чтобы результат был массивом?Вам просто нужно, чтобы он был сплющен и чередовался с разделителями? Можете ли вы дать мне рабочий пример массива массивов для присоединения, а массив разделителей для пересечения? – TylerY86

+0

А я вижу. Другой пример использования Lodash - правильный. – TylerY86

0
var result = [].concat.apply([], numbers); 

console.log(result) 
0

Вы можете использовать Array#reduce и возвращает массив с элементами и клей, если это необходимо.

const join = (array, glue) => array.reduce((a, b) => a.concat(glue, b)); 
 

 
var numbers = [[1], [2], [3]]; 
 

 
console.log(join(numbers, [0])); 
 
console.log(join(numbers, [42, 43])); 
 
console.log(join([[1]], [0]));
.as-console-wrapper { max-height: 100% !important; top: 0; }

+2

Вы можете удалить термальный оператор и начальное значение из сокращения: 'array.reduce ((r, a) => r.concat (glue, a))' – 4castle

0

Вот реализация функции, которая делает это, с некоторой дополнительной логикой, в случае, если кто-то хочет, чтобы это сделать. Я действительно спрашивал, существует ли это уже.

Требуется lodash.

export function intersperse(arrs, delimeter) { 
    let joined = []; 
    for (var i = 0; i < arrs.length; i++) { 
     let arr = arrs[i]; 
     if (!arr) continue; //handle sparse arrays 
     joined.push(...arr); 
     if (i === arrs.length - 1) break; 
     if (_.isFunction(delimeter)) { 
      joined.push(...delimeter()); 
     } else if (_.isArray(delimeter)) { 
      joined.push(...delimeter); 
     } else { 
      throw new Error("unknown type"); 
     } 
    } 
    return joined; 
} 
0

Много хороших ответов здесь, включая комментарии 4Castle. Что касается изменения, я хотел бы разработать общий Array.prototype.intersperse() для этой работы.

В функциональном JS я мог бы предложить 3 варианта этой работы;

Array.prototype.intersperse_1 = function(s){ 
    return this.reduce((p,c,i) => (p[2*i]=c,p), new Array(2*this.length-1).fill(s)); 
}; 
Array.prototype.intersperse_2 = function(s){ 
    return this.reduce((p,c,i) => (i ? p.push(s,c) : p.push(c),p),[]); 
}; 
Array.prototype.intersperse_3 = function(s){ 
    return this.reduce((p,c,i) => i ? p.concat([s],[c]) : p.concat([c]),[]); 
}; 

Вы должны держаться подальше от 3-го один, так как .concat() является одним из самых дорогостоящих операций в функциональных JS. Он не сможет выполнить задание даже на 100 тыс. Позиций.

С другой стороны, хотя он всегда немного быстрее в массивах малых размеров, 1-й из них получается 2x или даже быстрее, чем 2-й в очень больших массивах как в FF, так и в Chrome. то есть 1-й перемещает массив элементов 10M менее чем за 1000 мс, а для той же работы второй занимает примерно 2000-2500 мс. Обработка этого размера курса была бы невозможна с 3-го на всех.

Так что в этом конкретном случае это, вероятно, будет более дорогостоящим по сравнению с индивидуальными решениями, поскольку мы должны сопоставить результат с примитивными значениями, но я думаю, что все же стоит отметить следующий код. Уверен, что .concat() будет принимать соответствующие решения, если длина массива превышает определенную цифру.

Array.prototype.intersperse = function(s){ 
 
    return this.reduce((p,c,i) => (p[2*i]=c,p), new Array(2*this.length-1).fill(s)); 
 
} 
 

 
var arr = [[1],[2],[3]], 
 
result = arr.intersperse([0]) 
 
      .map(e => e[0]); 
 
console.log(JSON.stringify(result));

+0

другая идея: 'Array.from ({length: 2 * this.length-1}, (v, i) => i & 1? s: this [i >>> 1]) '; Я предполагаю, что это повторное увеличение размера и, следовательно, перераспределение памяти, что делает вашу вторую версию медленной. – Thomas

+0

@Thomas Интересный подход еще при тестировании с массивами 1M + кажется, что он отстает от второго (в Chrome, но не в FF). Кажется, что он всегда работает лучше всего в FF и Chrome. Проверьте [this] (https://repl.it/Dh7N) тест производительности элемента 10M. Может быть, вы можете его изменить. – Redu

0

Как уже упоминалось здесь несколько раз, самый простой способ сделать это

function intercalate(glue, arr){ 
    return arr.reduce((acc, v) => acc.concat(glue, v)); 
} 

, но это не самый лучший способ, так как он создает с каждой итерации новый (промежуточный) массив, который затем отбрасывается. Это не имеет значения для этого небольшого массива значений, но если вы когда-либо собираетесь использовать его на более длинном массиве, вы можете заметить влияние.

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

function intercalate(glue, arr){ 
    const push = (acc, v) => (Array.isArray(v)? acc.push(...v): acc.push(v), acc); 
    return arr.reduce((acc, v, i) => push(i===0? acc: push(acc, glue), v), []); 
} 

Но так как этот массив постепенно увеличивая его, возможно, нужно выделить больший кусок памяти и копирование данные. Эти задачи очень эффективны, но все же не нужны (imo); мы можем сделать лучше.

Сначала создадим список, содержащий все массивы и разделитель между ними, и сгладьте это, используя concat.apply([], list). Поэтому мы создаем один промежуточный массив, размер которого мы можем вычислить загодя, а остальная часть - проблема Array.concat, и это базовая реализация.

function intersperse(delimiter, arr){ 
    if(!arr.length) return []; 
    let j = 0, push = (acc, v) => (acc[j++] = v, acc); 
    return arr.reduce((acc, v) => push(j===0? acc: push(delimiter, glue), v), Array(2*arr.length-1)); 
} 
//or 
function intersperse(delimiter, arr){ 
    if(!arr.length) return []; 
    var out = Array(2*arr.length-1); 
     out[0] = arr[0]; 
    for(var i=1, j=1; j<out.length;){ 
     out[j++] = delimiter; 
     out[j++] = arr[i++]; 
    } 
    return out; 
} 

//and 

function intercalate(glue, arr){ 
    var emptyArray = []; 
    return arr.length? 
     emptyArray.concat.apply(emptyArray, intersperse(glue, arr)): 
     emptyArray; 
} 

Wich версия будет лучшим/быстрым, в конце концов, не так легко сказать, так как это может зависеть от переданных значений, а кастрированный баран или не JIT компилятор оптимизирует S *** из из этого. Я сделал свои замечания, вам решать, какую версию/реализацию вы используете.

Для первой версии: вы можете предпочесть не ставить это в Lib вообще, но писать это в основном рядный (это коротко и просто достаточно для этого), поэтому JIT-компилятор не может попытаться найти некоторые общие типы между различными вызовами (#monomorphic function/code) и, следовательно, оптимизировать каждое возникновение отдельно. С другой стороны, это может быть преждевременная оптимизация. Тебе решать.

Смежные вопросы