Конвертировать массив в объект с фильтрацией и расстановкой приоритетов и минимальными циклами - PullRequest
3 голосов
/ 24 февраля 2020

Учитывая массив

const array = [{
  typeName: 'welcome', orientation: 'landscape', languageId: 1, value: 'Welcome'
}, {
  typeName: 'welcome', orientation: 'landscape', languageId: 2, value: 'Bonjour'
}, {
  typeName: 'welcome', orientation: 'portrait', languageId: 2, value: 'Bonjour bonjour'
}]

Моя конечная цель состоит в том, чтобы получить это:

{
  welcome: {
    landscape: ['Welcome'],
    portrait: ['Bonjour bonjour']
  }
}

Для этого мне нужно преобразовать в объект типа {typeName: {orientation: value[]}}, например так:

// This is NOT what I want, it's just an intermediate form -- keep reading
{
  welcome: {
    landscape: ['Welcome', 'Bonjour'],
    portrait: ['Bonjour bonjour']
  }
}

Но включая расстановку приоритетов: если languageId = 1 присутствует в массиве, тогда игнорировать остальные значения для указанных c typeName, direction..В приведенном выше примере должно быть только ['Welcome'] , поскольку его languageId = 1, поэтому 'Bonjour' можно игнорировать, хотя, если languageId = 1 отсутствует, тогда может быть добавлено любое значение (welcome.portrait).

С конвертацией я не столкнулся с какими-либо проблемами. Думаю, .reduce () метод

array.reduce((prev, current) => ({
    ...prev,
    [current.typeName]: {
        ...prev[current.typeName],
        [current.orientation]: [
            ...(((prev[current.typeName] || {})[current.orientation]) || []),
            current.value
        ]
    }
  }), {});

, но приоритизацию я могу сделать только с фильтрацией, которая также l oop внутри него .. Пока проблем нет, но если массив будет довольно большим - производительность пострадает

    const array = [{
      typeName: 'welcome', orientation: 'landscape', languageId: 1, value: 'Welcome'
    }, {
      typeName: 'welcome', orientation: 'landscape', languageId: 2, value: 'Bonjour'
    }, {
      typeName: 'welcome', orientation: 'portrait', languageId: 2, value: 'Bonjour bonjour'
    }]

    const result =  array
      .filter((item) => {
          return item.languageId === 1 ||
          !array.some((innerItem) => ( //Inner loop that I want to avoid
              innerItem.typeName === item.typeName &&
              innerItem.orientation === item.orientation &&
              innerItem.languageId === 1
          ))
      })
      .reduce((prev, current) => ({
        ...prev,
        [current.typeName]: {
            ...prev[current.typeName],
            [current.orientation]: [
                ...(((prev[current.typeName] || {})[current.orientation]) || []),
                current.value
            ]
        }
      }), {});
      
console.log(result)

Итак, вопрос в том, как лучше всего избегать внутреннего l oop?

Ответы [ 3 ]

1 голос
/ 24 февраля 2020

Вы можете использовать Set, чтобы отслеживать, видели ли вы languageId === 1 для определенных typeName и orientation. Set требуется , чтобы иметь сублинейную производительность :

Заданные объекты должны быть реализованы с использованием таблиц ha sh или других механизмов, которые в среднем обеспечивают время доступа, которое сублинейный по количеству элементов в коллекции.

Таким образом, Set превзойдет внутренний l oop. (Вы также можете использовать объект, если ваши ключи являются строками, как в моем примере ниже. Если вы это сделаете, создайте его с помощью Object.create(null), чтобы он не наследовал от Object.prototype.)

Тогда только один простой l oop, а не reduce:

const array = [{
  typeName: 'welcome', orientation: 'landscape', languageId: 1, value: 'Welcome'
}, { typeName: 'welcome', orientation: 'landscape', languageId: 1, value: 'Bon dia'
}, {
  typeName: 'welcome', orientation: 'landscape', languageId: 2, value: 'Bonjour'
}, {
  typeName: 'welcome', orientation: 'portrait', languageId: 2, value: 'Bonjour bonjour'
}]

const sawPriority = new Set();
const result = {};
for (const {typeName, orientation, languageId, value} of array) {
    const key = `${typeName}-${orientation}`;
    const isPriority = languageId === 1;
    let entry = result[typeName];
    if (!entry) {
        // No entry yet, easy peasy
        entry = result[typeName] = {[orientation]: [value]};
        if (isPriority) {
            sawPriority.add(key);
        }
    } else {
        const hasPriority = sawPriority.has(key);
        if (hasPriority === isPriority) {
            // Either the first for a new orientation, or a subsequent entry that
            // matches the priority
            const inner = entry[orientation];
            if (inner) {
                // Subsequent, add
                inner.push(value);
            } else {
                // First for orientation, create
                entry[orientation] = [value];
            }
        } else if (isPriority) {
            // It's a new priority entry, overwrite
            entry[orientation] = [value];
            sawPriority.add(key);
        }
    }
}

console.log(result)

(В комментарии вы упомянули, что если есть несколько languageId === 1 записей, они должны быть собраны в массив, поэтому я включил вторую в выше, чтобы показать, что работает.)


В приведенном выше примере я сравниваю два логических значения, например:

if (hasPriority === isPriority) {

Это работает, потому что я знаю они оба на самом деле являются логическими значениями, а не просто значениями true или false, потому что isPriority является результатом сравнения === (гарантированно будет логическим), а hasPriority является результатом вызова has on Set (гарантированно будет логическим).

Если бы возник вопрос о том, может ли один из них быть просто истинным или ложным значением, а не определенно true или false, я бы использовал !, чтобы они были логическими значениями:

if (!hasPriority === !isPriority) {
1 голос
/ 24 февраля 2020

Вы можете взять Set для элементов prio и предотвратить добавление дополнительных элементов в массив.

const
    array = [{ typeName: 'welcome', orientation: 'landscape', languageId: 1, value: 'Welcome' }, { typeName: 'welcome', orientation: 'landscape', languageId: 2, value: 'Bonjour' }, { typeName: 'welcome', orientation: 'portrait', languageId: 2, value: 'Bonjour bonjour' }],
    hasPrio = new Set,
    result = array.reduce((r, { typeName, orientation, languageId, value }) => {
        var key = `${typeName}|${orientation}`;

        if (!r[typeName]) r[typeName] = {};
        if (languageId === 1) {
            r[typeName][orientation] = [value];
            hasPrio.add(key);
        } else if (!hasPrio.has(key)) {
            if (!r[typeName][orientation]) r[typeName][orientation] = [];
            r[typeName][orientation].push(value);
        }
        return r;
    }, {});

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
0 голосов
/ 24 февраля 2020

Смотрите демо-версию здесь

const array = [{
  typeName: 'welcome', orientation: 'landscape', languageId: 1, value: 'Welcome'
}, {
  typeName: 'welcome', orientation: 'landscape', languageId: 2, value: 'Bonjour'
}, {
  typeName: 'welcome', orientation: 'portrait', languageId: 2, value: 'Bonjour bonjour'
}];


let obj = array.reduce( (acc, {orientation, value})=>{   
      acc[orientation] = (acc[orientation] || []).concat(value);
      return acc;
   }, {});

output = {};
output[array[0].typeName] = obj;   
console.log(output);
...