Сократить массив, содержащий почти идентичные объекты - PullRequest
0 голосов
/ 08 января 2019

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

У меня есть массив объектов, который выглядит следующим образом:

    [{ 
        id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
        first_name: 'SomeName',
        email: 'some@email.com',
        rName: 'User',                                // 0,1
        rAuthority: 'ROLE_USER',                      // 0,1
        pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
        pMobile: '012 345 6789',
        atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', // 0,2
        atPlatform: 'web',
    },{ 
        id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
        first_name: 'SomeName',
        email: 'some@email.com',
        rName: 'User',                                // 0,1
        rAuthority: 'ROLE_USER',                      // 0,1
        pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
        pMobile: '012 345 6789',
        atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', // 1,3
        atPlatform: 'web',
    },{ 
        id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
        first_name: 'SomeName',
        email: 'some@email.com',
        rName: 'Admin',                               // 2,3
        rAuthority: 'ROLE_ADMIN',                     // 2,3
        pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
        pMobile: '012 345 6789',
        atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', // 0,2
        atPlatform: 'web',
    },{ 
        id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
        first_name: 'SomeName',
        email: 'some@email.com',
        rName: 'Admin',                               // 2,3
        rAuthority: 'ROLE_ADMIN',                     // 2,3
        pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
        pMobile: '012 345 6789',
        atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', // 1,3
        atPlatform: 'web',
    }]

// I point out which of the properties are not identical by adding 
// quotes showing which indices of the array contains unique values of 
// said property. If the there's not a quote to the right of the 
// property it's identical across all indices.

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

[{
    id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
    first_name: 'SomeName',
    email: 'some@email.com',
    roles: [{
            name: 'User',
            authority: 'ROLE_USER'
    },{
            name: 'Admin',
            authority: 'ROLE_ADMIN'
    }],
    profiles: [{
            id: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
            mobile: '012 345 6789',
    }],
    tokens: [{
            id: '90db0c5d-3030-44aa-9dc0-40242af0d5c5',
            platform: 'web',
    },{
            id: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c',
            platform: 'web',
    }]
}]

Как видите, все свойства предыдущего массива с префиксом r теперь имеют свои объекты в массиве в свойстве roles. p префиксные свойства находятся в profiles, а at префиксные свойства находятся в tokens. Объект считается «идентичным», если он имеет тот же id, что и другой объект.

Вот код, который я написал, который, кажется, успешно преобразует первый массив во второй массив:

...then(data => {

    let users = [];

    // gets the correct formatting
    data.forEach((d, i) => {
        let found = false;
        users.forEach((u, j) => {
            if(d.id === u.id) {
                u.roles.push({ name:d.rName, authority:d.rAuthority });
                u.tokens.push({ id:d.atId, platform:d.atPlatform });
                u.profiles.push({ id:d.pId, mobile:d.pMobile });
                found = true;
            }
        });
        if(!found) {
            users.push({
                id:d.id,
                first_name:d.first_name,
                email:d.email,
                roles: [{ name:d.rName, authority:d.rAuthority }],
                profiles: [{ id:d.pId, mobile:d.pMobile }],
                tokens: [{ id:d.atId, platform:d.atPlatform }]
            });
        }
    });

    // remove duplicates from sub-arrays
    users.forEach((user, i) => {
        user.roles = _.uniqBy(user.roles, 'name');
        user.profiles = _.uniqBy(user.profiles, 'id');
        user.tokens = _.uniqBy(user.tokens, 'id');
    });
});

У меня две проблемы с этим кодом. Во-первых, он очень длинный (на самом деле ради этого вопроса я удалил многие свойства из первого массива - в действительности каждый объект имеет более чем удвоенное количество свойств, которые вы видите здесь, что делает этот код намного длиннее), и во-вторых, у меня есть сильное подозрение, что это, вероятно, очень неэффективно.

Вопрос:

Может кто-нибудь помочь мне переписать код, который я использую для форматирования моего массива, во что-то более короткое и более эффективное. У меня действительно установлено lodash , поэтому я бы предпочел ответы, которые его использовали, но я с удовольствием также приму ответы vanilla.js.

Дополнительные примечания:

Этот вопрос является продолжением другого вопроса Я отправил. Если посмотреть на этот вопрос, то смутно покажу, откуда берутся данные, которые я пытаюсь преобразовать в этом вопросе. Короче говоря, это происходит из базы данных, это моя идея ленивой загрузки с использованием Knex.js. Идея состоит в том, что каждый пользователь может иметь несколько ролей, профилей и AuthTokens.

Ответы [ 4 ]

0 голосов
/ 09 января 2019

Вы можете взять несколько массивов для желаемой группировки с их ключами и псевдонимами.

var data = [{ id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'User', rAuthority: 'ROLE_USER', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', atPlatform: 'web' }, { id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'User', rAuthority: 'ROLE_USER', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', atPlatform: 'web' }, { id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'Admin', rAuthority: 'ROLE_ADMIN', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', atPlatform: 'web' }, { id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'Admin', rAuthority: 'ROLE_ADMIN', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', atPlatform: 'web' }],
    head = [['id'], ['first_name'], ['email']],
    sub = [
        ['roles', [['name', 'rName'], ['authority', 'rAuthority']]],
        ['profiles', [['id', 'pId'], ['mobile', 'pMobile']]],
        ['tokens', [['id', 'atId'], ['platform', 'atPlatform']]]
    ],
    result = data.reduce((r, o) => {
        const mapData = ([key, alias = key]) => ({ [key]: o[alias] });
        var temp = r.find(q => q[head[0]] === o[head[0]]);
        if (!temp) {
            r.push(temp = Object.assign(...head.map(mapData)));
        }
        sub.forEach(([s, keys]) => {
            temp[s] = temp[s] || [];
            var inner = temp[s].find(q => q[keys[0][0]] === o[keys[0][1]]);
            if (!inner) {
                temp[s].push(Object.assign(...keys.map(mapData)));
            }
        });
        return r;
    }, []);

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

Немного отличается от ответа Кириммо, используя объекты в качестве промежуточных значений для сопоставления каждого элемента / подпункта с соответствующим идентификатором, а затем используя Object.values, чтобы вернуть их обратно:

const input = [
  {id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'User', rAuthority: 'ROLE_USER', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', atPlatform: 'web'},
  {id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'User', rAuthority: 'ROLE_USER', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', atPlatform: 'web'},
  {id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'Admin', rAuthority: 'ROLE_ADMIN', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', atPlatform: 'web'},
  {id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'Admin', rAuthority: 'ROLE_ADMIN', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', atPlatform: 'web'}
];

const result = Object.values(input.reduce((result, item) => {
  if (!(item.id in result)) {
    result[item.id] = {id: item.id, first_name: item.first_name, email: item.email, roles: {}, profiles: {}, tokens: {}};
  }
  result[item.id].roles[item.rName] = {name: item.rName, authority: item.rAuthority};
  result[item.id].profiles[item.pId] = {id: item.pId, mobile: item.pMobile};
  result[item.id].tokens[item.atId] = {id: item.atId, platform: item.atPlatform};
  return result;
}, {})).map(item => {
  ['roles', 'profiles', 'tokens'].forEach(prop => item[prop] = Object.values(item[prop]));
  return item;
});

console.log(result);

В основном:

  • Array.reduce преобразует исходный массив в объект, проиндексированный по идентификаторам элементов и содержащий объекты ролей / профилей / токенов, также проиндексированные по соответствующим им идентификаторам,
  • Object.values преобразует полученный объект обратно в массив,
  • Array.map вызывает Object.values для ролей / профилей / токенов каждого элемента, чтобы преобразовать их в нужные массивы.
0 голосов
/ 08 января 2019

Используя lodash, вы можете создать частично примененную функцию, которая получает свойства от объекта, используя _.pickBy() и регулярное выражение со строкой, с которой должно начинаться свойство. Затем вы можете создать создатель атрибута, используя соответствующую строку (at для tokens).

Теперь _.groupBy() id предметов, и сопоставьте каждую группу, чтобы создать объект, используя создателей атрибутов.

const data = [{"id":"6b6574cf-d77a-4ed8-852f-cb60a0d377cd","first_name":"SomeName","email":"some@email.com","rName":"User","rAuthority":"ROLE_USER","pId":"e7da65a9-ea2d-4c77-82f6-e1addc78fb6e","pMobile":"012 345 6789","atId":"90db0c5d-3030-44aa-9dc0-40242af0d5c5","atPlatform":"web"},{"id":"6b6574cf-d77a-4ed8-852f-cb60a0d377cd","first_name":"SomeName","email":"some@email.com","rName":"User","rAuthority":"ROLE_USER","pId":"e7da65a9-ea2d-4c77-82f6-e1addc78fb6e","pMobile":"012 345 6789","atId":"e7d53cab-a9b9-40ae-9271-11d79c2f269c","atPlatform":"web"},{"id":"6b6574cf-d77a-4ed8-852f-cb60a0d377cd","first_name":"SomeName","email":"some@email.com","rName":"Admin","rAuthority":"ROLE_ADMIN","pId":"e7da65a9-ea2d-4c77-82f6-e1addc78fb6e","pMobile":"012 345 6789","atId":"90db0c5d-3030-44aa-9dc0-40242af0d5c5","atPlatform":"web"},{"id":"6b6574cf-d77a-4ed8-852f-cb60a0d377cd","first_name":"SomeName","email":"some@email.com","rName":"Admin","rAuthority":"ROLE_ADMIN","pId":"e7da65a9-ea2d-4c77-82f6-e1addc78fb6e","pMobile":"012 345 6789","atId":"e7d53cab-a9b9-40ae-9271-11d79c2f269c","atPlatform":"web"}];
    
const createAttribute = startStr => {
  const pattern = new RegExp(`^${startStr}[A-Z]`);
  
  return arr => _.uniqWith(_.map(arr, _.flow([
    obj => _.pickBy(obj, (v, k) => pattern.test(k)),
    obj => _.mapKeys(obj, (v, k) => 
      k.replace(/(^[a-z]+)([A-Z].+$)/, '$2') // key name without the prefix
      .toLowerCase()
    )
  ])), _.isEqual);
};

const createRoles = createAttribute('r');
const createProfiles = createAttribute('p');
const createTokens = createAttribute('at');
    
const fn = _.flow([
  arr => _.groupBy(arr, 'id'),
  groups => _.map(groups, group => {
    const { id, first_name, email } = _.first(group);
    
    return {
      id,
      first_name,
      email,
      roles: createRoles(group),
      profiles: createProfiles(group),
      tokens: createTokens(group)
    };
  })
]);

const result = fn(data);

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
0 голосов
/ 08 января 2019

Вы можете использовать редуктор и создать новый массив объектов:

const input = [{
  id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
  first_name: 'SomeName',
  email: 'some@email.com',
  rName: 'User', // 0,1
  rAuthority: 'ROLE_USER', // 0,1
  pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
  pMobile: '012 345 6789',
  atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', // 0,2
  atPlatform: 'web',
}, {
  id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
  first_name: 'SomeName',
  email: 'some@email.com',
  rName: 'User', // 0,1
  rAuthority: 'ROLE_USER', // 0,1
  pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
  pMobile: '012 345 6789',
  atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', // 1,3
  atPlatform: 'web',
}, {
  id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
  first_name: 'SomeName',
  email: 'some@email.com',
  rName: 'Admin', // 2,3
  rAuthority: 'ROLE_ADMIN', // 2,3
  pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
  pMobile: '012 345 6789',
  atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', // 0,2
  atPlatform: 'web',
}, {
  id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
  first_name: 'SomeName',
  email: 'some@email.com',
  rName: 'Admin', // 2,3
  rAuthority: 'ROLE_ADMIN', // 2,3
  pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
  pMobile: '012 345 6789',
  atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', // 1,3
  atPlatform: 'web',
}];

console.log(input.reduce((acc, val, ind) => {
  if (!acc.find(el => el.id === val.id)) {
    acc.push({
      id: val.id,
      first_name: val.first_name,
      email: val.email,
      roles: [],
      profiles: [],
      tokens: []
    });
  }
  else {
    const el = acc.find(el => el.id === val.id);
    if (!el.roles.find(a => a.authority === val.rAuthority)) {
      el.roles.push({
        authority: val.rAuthority,
        name: val.rName
      });
    }
    if (!el.profiles.find(a => a.id === val.pId)) {
      el.profiles.push({
        id: val.pId,
        mobile: val.pMobile
      });
    }
    if (!el.tokens.find(a => a.id === val.atId)) {
      el.tokens.push({
        id: val.atId,
        platform: val.atPlatform
      });
    }
  }
  return acc;
}, []));
...