Декартово произведение множественных массивов в JavaScript

Как бы вы реализовали декартово произведение множественных массивов в JavaScript?

В качестве примера,

cartesian([1,2],[10,20],[100,200,300]) //should be
// [[1,10,100],[1,10,200],[1,10,300],[2,10,100],[2,10,200]...]
73 голоса | спросил viebel 6 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowThu, 06 Sep 2012 19:59:56 +0400 2012, 19:59:56

19 ответов


0

Обновление 2017 года: двухстрочный ответ с ванильным JS

Все ответы здесь чрезмерно сложные , большинство из них занимают 20 строк кода или даже больше.

В этом примере используются только две строки ванильного JavaScript , без символов lodash, подчеркивания или других библиотек:

let f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesian = (a, b, ...c) => b ? cartesian(f(a, b), ...c) : a;

Обновление:

Это то же самое, что и выше, но улучшено, чтобы строго следовать Руководству по стилю Airbnb JavaScript - проверено с помощью ESLint с eslint-конфигурации-Airbnb базы :

const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e))));
const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a);

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

Пример

Это точный пример из вашего вопроса:

let output = cartesian([1,2],[10,20],[100,200,300]);

Выход

Это вывод этой команды:

[ [ 1, 10, 100 ],
  [ 1, 10, 200 ],
  [ 1, 10, 300 ],
  [ 1, 20, 100 ],
  [ 1, 20, 200 ],
  [ 1, 20, 300 ],
  [ 2, 10, 100 ],
  [ 2, 10, 200 ],
  [ 2, 10, 300 ],
  [ 2, 20, 100 ],
  [ 2, 20, 200 ],
  [ 2, 20, 300 ] ]

Demo

Смотрите демоверсии на:

Синтаксис

Синтаксис, который я здесь использовал, не нов. В моем примере используется оператор распространения и остальные параметры - функции JavaScript, определенные в 6-й редакции стандарта ECMA-262, опубликованной в июне 2015 года и разработанной намного раньше, более известной как ES6 или ES2015. См:

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

Заключение

Нет необходимости писать сотни строк кода, которые сложно поддерживать, и нет необходимости использовать целые библиотеки для такой простой вещи, когда две строки ванильного JavaScript могут легко выполнить свою работу. Как вы можете видеть, использование современных функций языка действительно окупается, и в тех случаях, когда вам необходимо поддерживать архаичные платформы без встроенной поддержки современных функций, вы всегда можете использовать Babel или другие инструменты для переноса нового синтаксиса в старый. .

Не пишите так, как будто это 1995 год

JavaScript развивается, и это происходит по причине. TC39 делает потрясающую работу над языковым дизайном, добавляя новые функции, а производители браузеров делают потрясающую работу по реализации этих функций.

Чтобы узнать текущее состояние встроенной поддержки любой данной функции в браузерах, см .:

Чтобы увидеть поддержку в версиях Node, см .:

Чтобы использовать современный синтаксис на платформах, которые его не поддерживают изначально, используйтеБабель:

ответил rsp 27 MarpmMon, 27 Mar 2017 21:23:58 +03002017-03-27T21:23:58+03:0009 2017, 21:23:58
0

Вот модифицированная версия кода @ viebel в простом Javascript без использования какой-либо библиотеки:

function cartesianProduct(arr)
{
    return arr.reduce(function(a,b){
        return a.map(function(x){
            return b.map(function(y){
                return x.concat(y);
            })
        }).reduce(function(a,b){ return a.concat(b) },[])
    }, [[]])
}

var a = cartesianProduct([[1, 2,3], [4, 5,6], [7, 8], [9,10]]);
console.log(a);
ответил Danny 26 MarpmSat, 26 Mar 2016 13:31:02 +03002016-03-26T13:31:02+03:0001 2016, 13:31:02
0

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

function cartProd(paramArray) {

  function addTo(curr, args) {

    var i, copy, 
        rest = args.slice(1),
        last = !rest.length,
        result = [];

    for (i = 0; i < args[0].length; i++) {

      copy = curr.slice();
      copy.push(args[0][i]);

      if (last) {
        result.push(copy);

      } else {
        result = result.concat(addTo(copy, rest));
      }
    }

    return result;
  }


  return addTo([], Array.prototype.slice.call(arguments));
}


>> console.log(cartProd([1,2], [10,20], [100,200,300]));
>> [
     [1, 10, 100], [1, 10, 200], [1, 10, 300], [1, 20, 100], 
     [1, 20, 200], [1, 20, 300], [2, 10, 100], [2, 10, 200], 
     [2, 10, 300], [2, 20, 100], [2, 20, 200], [2, 20, 300]
   ]

полная справочная реализация, которая относительно эффективна ... :-D

об эффективности: вы могли бы получить некоторые из них, вынув if из цикла и имея 2 отдельных цикла, поскольку он технически постоянен, и вы бы помогли с предсказанием ветвлений и всем этим беспорядком, но этот момент является своего рода спорным в Javascript

кто угодно, наслаждайся -ck

ответил ckozl 6 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowThu, 06 Sep 2012 21:16:36 +0400 2012, 21:16:36
0

Вот необычное, простое рекурсивное решение:

function cartesianProduct(a) { // a = array of array
    var i, j, l, m, a1, o = [];
    if (!a || a.length == 0) return a;

    a1 = a.splice(0, 1)[0]; // the first array of a
    a = cartesianProduct(a);
    for (i = 0, l = a1.length; i < l; i++) {
        if (a && a.length) for (j = 0, m = a.length; j < m; j++)
            o.push([a1[i]].concat(a[j]));
        else
            o.push([a1[i]]);
    }
    return o;
}

console.log(cartesianProduct([[1,2], [10,20], [100,200,300]]));
// [[1,10,100],[1,10,200],[1,10,300],[1,20,100],[1,20,200],[1,20,300],[2,10,100],[2,10,200],[2,10,300],[2,20,100],[2,20,200],[2,20,300]]
ответил sebnukem 12 AMpSun, 12 Apr 2015 06:40:34 +030040Sunday 2015, 06:40:34
0

Следующая эффективная функция генератора возвращает декартово произведение всех указанных итераций :

 // Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
  const remainder = tail.length > 0 ? cartesian(...tail) : [[]];
  for (let r of remainder) for (let h of head) yield [h, ...r];
}

// Example:
console.log(...cartesian([1, 2], [10, 20], [100, 200, 300]));

Он принимает массивы, строки, наборы и все другие объекты, реализующие итеративный протокол .

В соответствии со спецификацией n-арного декартового продукта он дает р>

  • [], если одна или несколько заданных итераций пусты, например, [] или ''
  • [[a]] если одна итерация содержит одно значение a дается.

Все остальные случаи обрабатываются, как и ожидалось, как продемонстрировали следующие тестовые примеры:

 // Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
  const remainder = tail.length > 0 ? cartesian(...tail) : [[]];
  for (let r of remainder) for (let h of head) yield [h, ...r];
}

// Test cases:
console.log([...cartesian([])]);              // []
console.log([...cartesian([1])]);             // [[1]]
console.log([...cartesian([1, 2])]);          // [[1], [2]]

console.log([...cartesian([1], [])]);         // []
console.log([...cartesian([1, 2], [])]);      // []

console.log([...cartesian([1], [2])]);        // [[1, 2]]
console.log([...cartesian([1], [2], [3])]);   // [[1, 2, 3]]
console.log([...cartesian([1, 2], [3, 4])]);  // [[1, 3], [2, 3], [1, 4], [2, 4]]

console.log([...cartesian('')]);              // []
console.log([...cartesian('ab', 'c')]);       // [['a','c'], ['b', 'c']]
console.log([...cartesian([1, 2], 'ab')]);    // [[1, 'a'], [2, 'a'], [1, 'b'], [2, 'b']]

console.log([...cartesian(new Set())]);       // []
console.log([...cartesian(new Set([1]))]);    // [[1]]
console.log([...cartesian(new Set([1, 1]))]); // [[1]]
ответил le_m 17 Mayam17 2017, 00:49:42
0

Вот рекурсивный способ использования ECMAScript 2015 функция генератора , поэтому вам не нужно создавать все кортежи одновременно:

 function* cartesian() {
    let arrays = arguments;
    function* doCartesian(i, prod) {
        if (i == arrays.length) {
            yield prod;
        } else {
            for (let j = 0; j < arrays[i].length; j++) {
                yield* doCartesian(i + 1, prod.concat([arrays[i][j]]));
            }
        }
    }
    yield* doCartesian(0, []);
}

console.log(JSON.stringify(Array.from(cartesian([1,2],[10,20],[100,200,300]))));
console.log(JSON.stringify(Array.from(cartesian([[1],[2]],[10,20],[100,200,300]))));
ответил heenenee 24 AM00000030000005031 2016, 03:22:50
0

Используя типичный возврат с генераторов ES6,

 function cartesianProduct(...arrays) {
  let current = new Array(arrays.length);
  return (function* backtracking(index) {
    if(index == arrays.length) yield current.slice();
    else for(let num of arrays[index]) {
      current[index] = num;
      yield* backtracking(index+1);
    }
  })(0);
}
for (let item of cartesianProduct([1,2],[10,20],[100,200,300])) {
  console.log('[' + item.join(', ') + ']');
}
 div.as-console-wrapper { max-height: 100%; }

Ниже представлена ​​аналогичная версия, совместимая с более старыми браузерами.

 function cartesianProduct(arrays) {
  var result = [],
      current = new Array(arrays.length);
  (function backtracking(index) {
    if(index == arrays.length) return result.push(current.slice());
    for(var i=0; i<arrays[index].length; ++i) {
      current[index] = arrays[index][i];
      backtracking(index+1);
    }
  })(0);
  return result;
}
cartesianProduct([[1,2],[10,20],[100,200,300]]).forEach(function(item) {
  console.log('[' + item.join(', ') + ']');
});
 div.as-console-wrapper { max-height: 100%; }
ответил Oriol 12 AMpSun, 12 Apr 2015 06:50:32 +030050Sunday 2015, 06:50:32
0

Это чисто решение ES6 с использованием функций стрелок

 function cartesianProduct(arr) {
  return arr.reduce((a, b) =>
    a.map(x => b.map(y => x.concat(y)))
    .reduce((a, b) => a.concat(b), []), [[]]);
}

var arr = [[1, 2], [10, 20], [100, 200, 300]];
console.log(JSON.stringify(cartesianProduct(arr)));
ответил Christopher Moore 18 MarpmSat, 18 Mar 2017 13:26:06 +03002017-03-18T13:26:06+03:0001 2017, 13:26:06
0

Версия coffeescript с lodash:

_ = require("lodash")
cartesianProduct = ->
    return _.reduceRight(arguments, (a,b) ->
        _.flatten(_.map(a,(x) -> _.map b, (y) -> x.concat(y)), true)
    , [ [] ])
ответил dsummersl 3 MaramMon, 03 Mar 2014 03:38:50 +04002014-03-03T03:38:50+04:0003 2014, 03:38:50
0

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

В любом случае, нет необходимости подчеркивать, lodash вообще. Я считаю, что это следует делать с чистым JS ES6, настолько функциональным, насколько это возможно.

Этот фрагмент кода использует карту сокращения и вложенную карту просто для получения декартового произведения двух массивов, однако второй массив получается из рекурсивного вызова той же функции с одним меньшим массивом; следовательно .. a[0].cartesian(...a.slice(1))

 Array.prototype.cartesian = function(...a){
  return a.length ? this.reduce((p,c) => (p.push(...a[0].cartesian(...a.slice(1)).map(e => a.length > 1 ? [c,...e] : [c,e])),p),[])
                  : this;
};

var arr = ['a', 'b', 'c'],
    brr = [1,2,3],
    crr = [[9],[8],[7]];
console.log(JSON.stringify(arr.cartesian(brr,crr))); 
ответил Redu 2 +03002016-10-02T19:10:03+03:00312016bEurope/MoscowSun, 02 Oct 2016 19:10:03 +0300 2016, 19:10:03
0

Однострочный подход для лучшего чтения с отступами.

result = data.reduce(
    (a, b) => a.reduce(
        (r, v) => r.concat(b.map(w => [].concat(v, w))),
        []
    )
);

Требуется один массив с массивами разыскиваемых декартовых элементов.

 var data = [[1, 2], [10, 20], [100, 200, 300]],
    result = data.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));

console.log(result.map(a => a.join(' ')));
 .as-console-wrapper { max-height: 100% !important; top: 0; }
ответил Nina Scholz 31 Maypm18 2018, 22:27:49
0

В моем конкретном случае «старомодный» подход казался более эффективным, чем методы, основанные на более современных функциях. Ниже приведен код (включая небольшое сравнение с другими решениями, опубликованными в этой теме @rsp и @sebnukem), если он окажется полезным для кого-то еще.

Идея заключается в следующем. Допустим, мы создаем внешний продукт массивов N, a_1,...,a_N каждый из которых имеет компоненты m_i. Внешний продукт этих массивов имеет элементы M=m_1*m_2*...*m_N, и каждый из них можно отождествить с N- размерный вектор, компоненты которого являются натуральными числами, а i -ый компонент строго ограничен из выше m_i. Например, вектор (0, 0, ..., 0) будет соответствовать конкретной комбинации, в которой каждый берет первый элемент из каждого массива, а (m_1-1, m_2-1, ..., m_N-1) идентифицируется комбинацией, в которой каждый берет последний элемент из каждого массива. Таким образом, чтобы построить все комбинации M, функция ниже последовательно создает все такие векторы и для каждого из них идентифицирует соответствующую комбинацию элементов входных массивов.

function cartesianProduct(){
    const N = arguments.length;

    var arr_lengths = Array(N);
    var digits = Array(N);
    var num_tot = 1;
    for(var i = 0; i < N; ++i){
        const len = arguments[i].length;
        if(!len){
            num_tot = 0;
            break;
        }
        digits[i] = 0;
        num_tot *= (arr_lengths[i] = len);
    }

    var ret = Array(num_tot);
    for(var num = 0; num < num_tot; ++num){

        var item = Array(N);
        for(var j = 0; j < N; ++j){ item[j] = arguments[j][digits[j]]; }
        ret[num] = item;

        for(var idx = 0; idx < N; ++idx){
            if(digits[idx] == arr_lengths[idx]-1){
                digits[idx] = 0;
            }else{
                digits[idx] += 1;
                break;
            }
        }
    }
    return ret;
}
//------------------------------------------------------------------------------
let _f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesianProduct_rsp = (a, b, ...c) => b ? cartesianProduct_rsp(_f(a, b), ...c) : a;
//------------------------------------------------------------------------------
function cartesianProduct_sebnukem(a) {
    var i, j, l, m, a1, o = [];
    if (!a || a.length == 0) return a;

    a1 = a.splice(0, 1)[0];
    a = cartesianProduct_sebnukem(a);
    for (i = 0, l = a1.length; i < l; i++) {
        if (a && a.length) for (j = 0, m = a.length; j < m; j++)
            o.push([a1[i]].concat(a[j]));
        else
            o.push([a1[i]]);
    }
    return o;
}
//------------------------------------------------------------------------------
const L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const args = [L, L, L, L, L, L];

let fns = {
    'cartesianProduct': function(args){ return cartesianProduct(...args); },
    'cartesianProduct_rsp': function(args){ return cartesianProduct_rsp(...args); },
    'cartesianProduct_sebnukem': function(args){ return cartesianProduct_sebnukem(args); }
};

Object.keys(fns).forEach(fname => {
    console.time(fname);
    const ret = fns[fname](args);
    console.timeEnd(fname);
});

с помощью node v6.12.2 я получаю следующее время:

cartesianProduct: 427.378ms
cartesianProduct_rsp: 1710.829ms
cartesianProduct_sebnukem: 593.351ms
ответил ewcz 12 TueEurope/Moscow2017-12-12T17:12:53+03:00Europe/Moscow12bEurope/MoscowTue, 12 Dec 2017 17:12:53 +0300 2017, 17:12:53
0

Нерекурсивный подход, который добавляет возможность фильтровать и изменять продукты перед тем, как фактически добавлять их в набор результатов. Обратите внимание на использование .map, а не .forEach. В некоторых браузерах .map работает быстрее.

function crossproduct(arrays,rowtest,rowaction) {
      // Calculate the number of elements needed in the result
      var result_elems = 1, row_size = arrays.length;
      arrays.map(function(array) {
            result_elems *= array.length;
      });
      var temp = new Array(result_elems), result = [];

      // Go through each array and add the appropriate element to each element of the temp
      var scale_factor = result_elems;
      arrays.map(function(array)
      {
        var set_elems = array.length;
        scale_factor /= set_elems;
        for(var i=result_elems-1;i>=0;i--) {
            temp[i] = (temp[i] ? temp[i] : []);
            var pos = i / scale_factor % set_elems;
            // deal with floating point results for indexes, this took a little experimenting
            if(pos < 1 || pos % 1 <= .5) {
                pos = Math.floor(pos);
            } else {
                pos = Math.min(array.length-1,Math.ceil(pos));
            }
            temp[i].push(array[pos]);
            if(temp[i].length===row_size) {
                var pass = (rowtest ? rowtest(temp[i]) : true);
                if(pass) {
                    if(rowaction) {
                        result.push(rowaction(temp[i]));
                    } else {
                        result.push(temp[i]);
                    }
                }
            }
        }
      });
      return result;
    }
ответил AnyWhichWay 17 MarpmThu, 17 Mar 2016 12:13:51 +03002016-03-17T12:13:51+03:0012 2016, 12:13:51
0

Просто на выбор очень простая реализация, использующая reduce:

const array1 = ["day", "month", "year", "time"];
const array2 = ["from", "to"];
const process = (one, two) => [one, two].join(" ");

const product = array1.reduce((result, one) => result.concat(array2.map(two => process(one, two))), []);
ответил Simple.Js 31 Jpm1000000pmWed, 31 Jan 2018 17:21:28 +030018 2018, 17:21:28
0

Я заметил, что никто не опубликовал решение, позволяющее передавать функции для обработки каждой комбинации, поэтому вот мое решение:

const _ = require('lodash')

function combinations(arr, f, xArr = []) {
    return arr.length>1 
    ? _.flatMap(arr[0], x => combinations(arr.slice(1), f, xArr.concat(x)))
    : arr[0].map(x => f(...xArr.concat(x)))
}

// use case
const greetings = ["Hello", "Goodbye"]
const places = ["World", "Planet"]
const punctuationMarks = ["!", "?"]
combinations([greetings,places,punctuationMarks], (greeting, place, punctuationMark) => `${greeting} ${place}${punctuationMark}`)
  .forEach(row => console.log(row))

Вывод:

Hello World!
Hello World?
Hello Planet!
Hello Planet?
Goodbye World!
Goodbye World?
Goodbye Planet!
Goodbye Planet?
ответил Lezorte 21 stEurope/Moscowp30Europe/Moscow09bEurope/MoscowThu, 21 Sep 2017 01:19:05 +0300 2017, 01:19:05
0

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

var cartesian = function(arrays) {
    var product = [];
    var precals = [];
    var length = arrays.reduce(function(acc, curr) {
        return acc * curr.length
    }, 1);
    for (var i = 0; i < arrays.length; i++) {
        var array = arrays[i];
        var mod = array.length;
        var div = i > 0 ? precals[i - 1].div * precals[i - 1].mod : 1;
        precals.push({
            div: div,
            mod: mod
        });
    }
    for (var j = 0; j < length; j++) {
        var item = [];
        for (var i = 0; i < arrays.length; i++) {
            var array = arrays[i];
            var precal = precals[i];
            var k = (~~(j / precal.div)) % precal.mod;
            item.push(array[k]);
        }
        product.push(item);
    }
    return product;
};

cartesian([
    [1],
    [2, 3]
]);

cartesian([
    [1],
    [2, 3],
    [4, 5, 6]
]);
ответил Samuel Ventura 22 FebruaryEurope/MoscowbThu, 22 Feb 2018 08:05:48 +0300000000amThu, 22 Feb 2018 08:05:48 +030018 2018, 08:05:48
0

Простое «разумное и визуально дружественное» решение.

 введите описание изображения здесь


// t = [i, length]

const moveThreadForwardAt = (t, tCursor) => {
  if (tCursor < 0)
    return true; // reached end of first array

  const newIndex = (t[tCursor][0] + 1) % t[tCursor][1];
  t[tCursor][0] = newIndex;

  if (newIndex == 0)
    return moveThreadForwardAt(t, tCursor - 1);

  return false;
}

const cartesianMult = (...args) => {
  let result = [];
  const t = Array.from(Array(args.length)).map((x, i) => [0, args[i].length]);
  let reachedEndOfFirstArray = false;

  while (false == reachedEndOfFirstArray) {
    result.push(t.map((v, i) => args[i][v[0]]));

    reachedEndOfFirstArray = moveThreadForwardAt(t, args.length - 1);
  }

  return result;
}

// cartesianMult(
//   ['a1', 'b1', 'c1'],
//   ['a2', 'b2'],
//   ['a3', 'b3', 'c3'],
//   ['a4', 'b4']
// );

console.log(cartesianMult(
  ['a1'],
  ['a2', 'b2'],
  ['a3', 'b3']
));
ответил zero.zero.seven 17 MarpmSat, 17 Mar 2018 15:07:52 +03002018-03-17T15:07:52+03:0003 2018, 15:07:52
0

 var chars = ['A', 'B', 'C']
var nums = [1, 2, 3]

var cartesianProduct = function() {
  return _.reduce(arguments, function(a, b) {
    return _.flatten(_.map(a, function(x) {
      return _.map(b, function(y) {
        return x.concat(y);
      });
    }), true);
  }, [
    []
  ]);
};

console.log(cartesianProduct(chars, nums))
 <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

Только что преобразовал ответ @ dummersl из CoffeScript в JavaScript. Это просто работает.

var chars = ['A', 'B', 'C']
var nums = [1, 2, 3]

var cartesianProduct = function() {
  return _.reduce(arguments, function(a, b) {
    return _.flatten(_.map(a, function(x) {
      return _.map(b, function(y) {
        return x.concat(y);
      });
    }), true);
  }, [[]]);
};

console.log( cartesianProduct(chars, nums) )
ответил guneysus 26 MaramMon, 26 Mar 2018 11:15:17 +03002018-03-26T11:15:17+03:0011 2018, 11:15:17
0

Еще одна реализация. Не самый короткий или причудливый, но быстрый:

function cartesianProduct() {
    var arr = [].slice.call(arguments),
        intLength = arr.length,
        arrHelper = [1],
        arrToReturn = [];

    for (var i = arr.length - 1; i >= 0; i--) {
        arrHelper.unshift(arrHelper[0] * arr[i].length);
    }

    for (var i = 0, l = arrHelper[0]; i < l; i++) {
        arrToReturn.push([]);
        for (var j = 0; j < intLength; j++) {
            arrToReturn[i].push(arr[j][(i / arrHelper[j + 1] | 0) % arr[j].length]);
        }
    }

    return arrToReturn;
}
ответил flare256 5 Jpm1000000pmSat, 05 Jan 2019 21:44:38 +030019 2019, 21:44:38

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132