Em uma matriz de objetos, a maneira mais rápida de encontrar o índice de um objeto cujos atributos correspondem a uma pesquisa

135

Estive navegando um pouco tentando encontrar uma maneira eficiente de fazer isso, mas não chegamos a lugar algum. Eu tenho uma matriz de objetos que se parece com isso:

array[i].id = some number;
array[i].name = some name;

O que eu quero fazer é encontrar os INDEXES dos objetos em que id é igual a, por exemplo, um de 0,1,2,3 ou 4. Suponho que eu poderia fazer algo como:

var indexes = [];
for(i=0; i<array.length; i++) {
  (array[i].id === 0) ? { indexes[0] = i }
  (array[i].id === 1) ? { indexes[1] = i }
  (array[i].id === 2) ? { indexes[2] = i }
  (array[i].id === 3) ? { indexes[3] = i }
  (array[i].id === 4) ? { indexes[4] = i }
}

Enquanto isso funcionaria, parece muito caro e lento (para não mencionar feio), especialmente se o array.length puder ser grande. Alguma idéia de como melhorar isso um pouco? Eu pensei em usar array.indexOf de alguma forma, mas não vejo como forçar a sintaxe. este

array.indexOf(this.id === 0);

por exemplo, retorna indefinido, como provavelmente deveria. Desde já, obrigado!

Petrov
fonte
1
Se você tem uma matriz antiga simples, tudo o que você pode fazer é iterar. Isso é o que são matrizes, um monte de objetos ordenados pelo índice de matrizes.
Dave Newton
2
Basta encontrar este post hoje, para todos os retardatários, existe um novo método de matriz Array.prototype.findIndex()no ECMAScript 2015. A resposta aceita foi impressionante.
Conrad Lo
Sou fã da sintaxe do ES6 (use polyfills, se for necessário suporte em navegadores herdados). ES7 + ES8 serão futuros
Fr0zenFyr 22/02/19

Respostas:

391

Talvez você queira usar funções de ordem superior, como "mapa". Supondo que você deseja pesquisar pelo atributo 'campo':

var elementPos = array.map(function(x) {return x.id; }).indexOf(idYourAreLookingFor);
var objectFound = array[elementPos];
Pablo Francisco Pérez Hidalgo
fonte
9
Essa resposta é ótimo, porque ele realmente responde à pergunta, fornecendo o índice :)
counterbeing
3
@ZeroAbsolute Sua função aplicada (passada para o mapa) pode retornar uma string de hash que deve fornecer uma chave exclusiva para cada combinação possível fornecida por seus critérios. Por exemplo: function hashf(el) { return String(el.id) + "_" + String(el.name); }. Esta é apenas uma dica: elementPos = array.map(hashf(x)).indexOf(hash({id:3, name:'Pablo'}));obviamente, a função hash que forneço não é válida para todos os casos, pois '_'pode fazer parte de seus valores, mas é apenas um exemplo rápido de que você pode descobrir diferentes métodos de hash.
Pablo Francisco Pérez Hidalgo
1
O que isso retorna se não for encontrado? Presumo -1, apenas curioso. Eu vou experimentar.
Nathan C. Tresch 12/02
1
@ NathanC.Tresch Retorna -1 porque esse é o indexOfvalor de retorno quando não é possível localizar um determinado valor.
Pablo Francisco Pérez Hidalgo
2
Oi pessoal, em vez de usar dois métodos, map, indexOfvocê pode usar apenas um chamado findIndex....... Ex:[{id:1},{id:2},{id:3},{id:4}].findIndex(function(obj){return obj.id == 3}) OR [{id:1},{id:2},{id:3},{id:4}].findIndex(obj => obj.id == 3)
Umair Ahmed
64

A maneira mais simples e fácil de encontrar o índice de elementos na matriz.

Sintaxe ES5: [{id:1},{id:2},{id:3},{id:4}].findIndex(function(obj){return obj.id == 3})

Sintaxe ES6: [{id:1},{id:2},{id:3},{id:4}].findIndex(obj => obj.id == 3)

Umair Ahmed
fonte
4
Eu acredito que esta é a solução mais elegante. Para aqueles preocupados com a compatibilidade com versões anteriores, você pode encontrar o polyfill findIndexem developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
mrogers
2
Recebo um aviso na ferramenta ES6 lint de que o obj.id == 3operador usado aqui pode causar uma conversão inesperada de tipos; portanto, use o obj.id === 3operador, que testa o mesmo valor e tipo.
Thclark 3/07
1
Esta resposta é pelo menos 3,5 vezes mais rápida que a resposta aceita acima. O uso var elementPos = array.map(function(x) {return x.id; }).indexOf(idYourAreLookingFor);levou 0,03500000002532033 milissegundos O uso [{id:1},{id:2},{id:3},{id:4}].findIndex(obj => obj.id == 3)demorou 0,00999999747378752 milissegundos.
Ovidio Reyna
1
ESTA RESPOSTA é a MAIS EFICIENTE, pois não itera toda a matriz. A resposta selecionada irá mapear gama completa e, em seguida, FindIndex que é obrigado para percorrer matriz inteira uma vez
Karun
26

O novo método Array .filter () funcionaria bem para isso:

var filteredArray = array.filter(function (element) { 
    return element.id === 0;
});

O jQuery também pode fazer isso com .grep ()

edit: vale ressaltar que essas duas funções simplesmente interagem sob o capô, não haverá uma diferença perceptível de desempenho entre elas e a rolagem de sua própria função de filtro, mas por que reinventar a roda.

jbabey
fonte
+1, sempre esqueço funções incorporadas como essa em objetos.
Tejs
59
Isso não retorna um índice.
23813 Adam Grant
Isso não responde a essa pergunta específica, mas me ajude bastante! Obrigado!
rochasdv
Isso não retorna o índice.
Rich
10

Se você se preocupa com o desempenho, não procure ou filtre ou mapeie ou use qualquer um dos métodos discutidos acima

Aqui está um exemplo demonstrando o método mais rápido. AQUI está o link para o teste real

Bloco de instalação

var items = []

for(var i = 0; i < 1000; i++) {
    items.push({id: i + 1})
}

var find = 523

Método mais rápido

var index = -1
for(var i = 0; i < items.length; i++) {
    if(items[i].id === find) {
        index = i;
        break;
    }
}

Métodos mais lentos

items.findIndex(item => item.id === find)

Método mais lento

items.map(item => item.id).indexOf(find);
PirateApp
fonte
2
Obrigado por fornecer esta comparação! O que é super interessante é a quantidade de desempenho que varia - incluindo qual método varia mais rapidamente, dependendo do navegador / mecanismo JavaScript usado para executá-los.
Iain Collins
1
Eu acho que isso deve ser marcado como uma resposta. Isso mostra o caminho mais rápido e mais lento.
Painkiller
No seu benchmark, o bloco 2 (usando o findIndex) é realmente mais rápido para mim (no Microsoft Edge Chromium 83.0.474.0)
rezadru
O bloco 2 agora também é mais rápido no chrome
cody mikol 20/04
8
array.forEach(function (elem, i) {  // iterate over all elements of array
    indexes[elem.id] = i;           // take the found id as index for the
});                                 // indexes array and assign i

o resultado é uma lista de pesquisa para o ID. com o ID fornecido, obtemos o índice do registro.

Nina Scholz
fonte
6
var indices = [];
var IDs = [0, 1, 2, 3, 4];

for(var i = 0, len = array.length; i < len; i++) {
    for(var j = 0; j < IDs.length; j++) {
        if(array[i].id == ID) indices.push(i);
    }
}
Elliot Bonneville
fonte
6

Como não há resposta usando a matriz regular find:

var one = {id: 1, name: 'one'};
var two = {id: 2, name:'two'}
var arr = [one, two] 

var found = arr.find((a) => a.id === 2)

found === two // true

arr.indexOf(found) // 1
enapupe
fonte
3

Uma nova maneira de usar o ES6

let picked_element = array.filter(element => element.id === 0);
Silve2611
fonte
picked_elementé uma matriz neste caso ...
Heretic Monkey
3

const index = array.findIndex(item => item.id === 'your-id');

Isso deve fornecer o índice do item na matriz com id === your-id

array = [ {id:1}, {id:2} ];

const index = array.findIndex(item => item.id === 2);

console.log(index);

PulpDood
fonte
2

Parece-me que você poderia criar um iterador simples com um retorno de chamada para teste. Igual a:

function findElements(array, predicate)
{
    var matchingIndices = [];

    for(var j = 0; j < array.length; j++)
    {
        if(predicate(array[j]))
           matchingIndices.push(j);
    }

    return matchingIndices;
}

Então você poderia invocar assim:

var someArray = [
     { id: 1, text: "Hello" },
     { id: 2, text: "World" },
     { id: 3, text: "Sup" },
     { id: 4, text: "Dawg" }
  ];

var matchingIndices = findElements(someArray, function(item)
   {
        return item.id % 2 == 0;
   });

// Should have an array of [1, 3] as the indexes that matched
Tejs
fonte
2

Adaptando a resposta de Tejs para mongoDB e Robomongo I mudou

matchingIndices.push(j);

para

matchingIndices.push(NumberInt(j+1));
user2584621
fonte
2

Usando a mapfunção ES6 :

let idToFind = 3;
let index = someArray.map(obj => obj.id).indexOf(idToFind);
JoeTidee
fonte
2

Para resumir toda a grande resposta acima e adicional da minha resposta em relação a encontrar todos os índices ocorridos em alguns dos comentários.

  1. Para retornar o índice da primeira ocorrência.

const array = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 2 }];
const idYourAreLookingFor = 2;

//ES5 
//Output: 1
array.map(function (x) { return x.id; }).indexOf(idYourAreLookingFor);

//ES6 
//Output: 1
array.findIndex(obj => obj.id === idYourAreLookingFor);

  1. Para retornar a matriz de índice de todas as ocorrências, usando reduzir.

const array = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 2 }]
const idYourAreLookingFor = 2;

//ES5
//Output: [1, 4]
array.reduce(function (acc, obj, i) {
  if (obj.id === idYourAreLookingFor)
    acc.push(i);
  return acc;
}, []);

//ES6
//Output: [1, 4]
array.reduce((acc, obj, i) => (obj.id === idYourAreLookingFor) ? acc.concat(i) : acc, [])

trungk18
fonte
0

Como ainda não posso comentar, quero mostrar a solução que usei com base no método que Umair Ahmed postou, mas quando você deseja procurar uma chave em vez de um valor:

[{"a":true}, {"f":true}, {"g":false}]
.findIndex(function(element){return Object.keys(element)[0] == "g"});

Entendo que ele não responde à pergunta expandida, mas o título não especifica o que era desejado de cada objeto. Por isso, quero compartilhar humildemente isso para economizar dores de cabeça para outras pessoas no futuro, enquanto eu o desinformar, talvez não seja o solução mais rápida.

Xander N
fonte
0

Eu criei um pequeno utilitário chamado super-array, onde você pode acessar itens em um array por um identificador exclusivo com complexidade O (1). Exemplo:

const SuperArray = require('super-array');

const myArray = new SuperArray([
  {id: 'ab1', name: 'John'},
  {id: 'ab2', name: 'Peter'},
]);

console.log(myArray.get('ab1')); // {id: 'ab1', name: 'John'}
console.log(myArray.get('ab2')); // {id: 'ab2', name: 'Peter'}
patotoma
fonte
Você pode ler Como oferecer bibliotecas pessoais de código aberto? antes de postar isso em qualquer lugar.
Martijn Pieters
@MartijnPieters Publiquei apenas algumas perguntas relevantes e o projeto é gratuito para o MIT, então qual é o problema? Talvez você possa ser um pouco mais tolerante.
patotoma 24/09
0
var test = [
  {id:1, test: 1},
  {id:2, test: 2},
  {id:2, test: 2}
];

var result = test.findIndex(findIndex, '2');

console.log(result);

function findIndex(object) {
  return object.id == this;
}

retornará o índice 1 (funciona apenas no ES 2016)

extremo
fonte
0

Eu gosto desse método porque é fácil comparar com qualquer valor no objeto, independentemente da profundidade do aninhamento.

 while(i<myArray.length && myArray[i].data.value!==value){
  i++; 
}
// i now hows the index value for the match. 
 console.log("Index ->",i );
Daniel Lefebvre
fonte