Cara memfilter array objek untuk kecocokan yang tidak peka huruf besar-kecil dari kunci objek apa pun

Saya memiliki kode contoh ini di sini, dan saya mencoba memfilter objek yang cocok tanpa merusak kompleksitas atau kinerja kode:

Kode di sini memfilter kecocokan berdasarkan pada satu kunci yang ditentukan secara eksplisit dan tidak peka huruf besar-kecil.

const people = [
  { firstName: 'Bob', lastName: 'Smith', status: 'single' },
  { firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
  { firstName: 'Jim', lastName: 'Johnson', status: 'complicated' },
  { firstName: 'Sally', lastName: 'Fields', status: 'relationship' },
  { firstName: 'Robert', lastName: 'Bobler', status: 'single' },
  { firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' },
  { firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship'
    rogueBonusKey: 'bob likes salmon' },
]

const searchString = 'Bob'

const found = people.filter((person) => {
  if (person.firstName === searchString) return true
})

console.log(found)

TUJUAN:

  1. Saya ingin itu cocok dengan huruf besar-kecil
  2. Saya ingin mengembalikan kecocokan dari kunci apa saja
  3. Saya ingin menemukannya menggunakan contains bukan pencocokan tepat

Sesuatu seperti ini:

// const people = [
//   { firstName: 'Bob', lastName: 'Smith', status: 'single' },
//   { firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
//   { firstName: 'Jim', lastName: 'Johnson', status: 'complicated' },
//   { firstName: 'Sally', lastName: 'Fields', status: 'relationship' },
//   { firstName: 'Robert', lastName: 'Bobler', status: 'single' },
//   { firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' },
//   { firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship'
//     rogueBonusKey: 'bob likes salmon' },
// ]

// const searchString = 'bob'

// ... magic

// console.log(found)

// { firstName: 'Bob', lastName: 'Smith', status: 'single' },
// { firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
// { firstName: 'Robert', lastName: 'Bobler', status: 'single' },
// { firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship'
//   rogueBonusKey: 'bob likes salmon' },

Saya telah menjelajahi dokumentasi yang terkait dengan Array.filter() dan saya pasti dapat membuat solusi yang melibatkan Array.reduce() dan mengulangi hal-hal dengan Object.keys(obj).forEach(), tetapi saya ingin tahu apakah ada cara yang ringkas dan berkinerja baik untuk menangani pencarian fuzzy semacam ini.

Sesuatu seperti ini:

const people = [
  { firstName: 'Bob', lastName: 'Smith', status: 'single' },
  { firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
  { firstName: 'Jim', lastName: 'Johnson', status: 'complicated' },
  { firstName: 'Sally', lastName: 'Fields', status: 'relationship' },
  { firstName: 'Robert', lastName: 'Bobler', status: 'single' },
  { firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' },
  { firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship' },
    rogueBonusKey: 'bob likes salmon' },
]

const searchString = 'Bob'

const found = people.filter((person) => {
  if (person.toString().indexOf(searchString).toLowerCase !== -1) return true
})

console.log(found)

[edit] Ini pasti berhasil, namun apakah dapat diterima?

const people = [
  { firstName: 'Bob', lastName: 'Smith', status: 'single' },
  { firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
  { firstName: 'Jim', lastName: 'Johnson', status: 'complicated' },
  { firstName: 'Sally', lastName: 'Fields', status: 'relationship' },
  { firstName: 'Robert', lastName: 'Bobler', status: 'single' },
  { firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' },
  { firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship',
    rogueBonusKey: 'bob likes salmon' },
]

const searchString = 'Bob'

const found = people.filter((person) => {
  const savageMatch = JSON.stringify(person)
    .toLowerCase()
    .indexOf(searchString.toLowerCase()) !== -1

  console.log(savageMatch)
  if (savageMatch) return true
})

console.log(found)

Jejak memori dioptimalkan:

const found = people.filter((person) => JSON.stringify(person)
    .toLowerCase()
    .indexOf(searchString.toLowerCase()) !== -1
)

Dikonversi menjadi suatu fungsi:

const fuzzyMatch = (collection, searchTerm) =>
  collection.filter((obj) => JSON.stringify(obj)
    .toLowerCase()
    .indexOf(searchTerm.toLowerCase()) !== -1
)

console.log(fuzzyMatch(people, 'bob'))

Ada beberapa jawaban bagus di sini; sejauh ini, saya telah memilih ini untuk kebutuhan pemfilteran saya:

const people = [
  { imageURL: 'http://www.alice.com/goat.jpeg', firstName: 'Bob', lastName: 'Smith', status: 'single' },
  { firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
  { firstName: 'Jim', lastName: 'Johnson', status: 'complicated' },
  { firstName: 'Sally', lastName: 'Fields', status: 'relationship' },
  { firstName: 'Robert', lastName: 'Bobler', status: 'single' },
  { firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' },
  {
    firstName: 'Ronald', lastName: 'McDonlad', status: 'relationship',
    rogueBonusKey: 'bob likes salmon'
  },
  {
    imageURL: 'http://www.bob.com/cats.jpeg', firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship',
    rogueBonusKey: 'bob hates salmon'
  },
]

const searchString = 'bob'

const options = {
  caseSensitive: false,
  excludedKeys: ['imageURL', 'firstName'],
}

const customFind = (collection, term, opts) => {
  const filterBy = () => {
    const searchTerms = (!opts.caseSensitive) ? new RegExp(term, 'i') : new RegExp(term)
    return (obj) => {
      for (const key of Object.keys(obj)) {
        if (searchTerms.test(obj[key]) &&
          !opts.excludedKeys.includes(key)) return true
      }
      return false
    }
  }
  return collection.filter(filterBy(term))
}

const found = customFind(people, searchString, options)

console.log(found)

Saya membuatnya dapat mendukung sensitivitas huruf besar-kecil dan mengecualikan kunci tertentu.


person agm1984    schedule 05.11.2017    source sumber
comment
Apakah hasil yang diharapkan adalah seluruh objek yang cocok atau hanya properti tertentu, pasangan nilai?   -  person guest271314    schedule 05.11.2017
comment
Cocokkan seluruh objek   -  person agm1984    schedule 05.11.2017


Jawaban (6)


Jika kami berasumsi bahwa semua properti adalah string, Anda dapat melakukan cara berikut

const people = [
  // ...
]

const searchString = 'Bob'

const filterBy = (term) => {
  const termLowerCase = term.toLowerCase()
  return (person) =>
    Object.keys(person)
      .some(prop => person[prop].toLowerCase().indexOf(termLowerCase) !== -1)
}

const found = people.filter(filterBy(searchString))

console.log(found)

Pembaruan: solusi alternatif dengan RegExp dan lebih kuno :) tetapi 2x lebih cepat

const people = [
  // ...
]

const searchString = 'Bob'

const escapeRegExp = (str) => // or better use 'escape-string-regexp' package
  str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&")


const filterBy = (term) => {
  const re = new RegExp(escapeRegExp(term), 'i')
  return person => {
    for (let prop in person) {
      if (!person.hasOwnProperty(prop)) {
        continue;
      }
      if (re.test(person[prop])) {
        return true;
      }
    }
    return false;        
  }
}

const found = people.filter(filterBy(searchString))
person Dmitry Druganov    schedule 05.11.2017
comment
Ini jelas lebih baik daripada merangkainya, tentu saja. - person agm1984; 05.11.2017
comment
Saya baru sadar bahwa saya harus menambahkan kunci yang dikecualikan juga, sehingga bisa untuk tujuan umum. Sepertinya Anda harus mendukungnya dengan cukup cepat. - person agm1984; 05.11.2017
comment
Saya suka penggunaan .some() Anda. Saya tidak tahu itu ada. Saya akan menandai jawaban ini sebagai benar setelah Array.some(more time has passed) dengan asumsi tidak ada orang lain yang mendapatkan permata data gila. - person agm1984; 05.11.2017
comment
Memperbarui pengujian Anda dengan solusi terakhir: jsperf.com/fuzzy-match-objects-2 - person Dmitry Druganov; 05.11.2017
comment
Jika Anda menghapus pelolosan, ini akan sedikit lebih cepat daripada solusi @ guest271314. Sebenarnya solusinya harusnya ada jalan keluarnya juga. - person Dmitry Druganov; 05.11.2017
comment
Selain itu, memeriksa properti sendiri tidak diperlukan dalam kasus Anda, jadi optimasi mikro ini juga akan sedikit meningkatkan kinerja. :) - person Dmitry Druganov; 05.11.2017
comment
Jawaban Anda yang diperbarui cukup indah. Saya mengalami masalah saat mencoba menambahkan excludedKeys ke jawaban guest271314. Saya tidak akan menyebut sekolah Anda lebih kuno. Loop for in dan for of mendukung async/menunggu. Tampaknya mudah bagi Anda untuk menambahkan lebih banyak pengaturan konfigurasi opsional. Saya rasa saya bisa segera menyelesaikannya sekarang dengan pembaruan Anda. Saya akan memposting jawaban akhir setelah saya selesai. Jawaban tamu bagus tetapi sangat kaku. Tapi itu sangat puitis. Milik Anda menyediakan kait tambahan dengan for in. - person agm1984; 05.11.2017
comment
Saya mengganti for in Anda dengan for (const key of Object.keys(obj)) ;) - person agm1984; 05.11.2017
comment
@DmitryDruganov apakah ada cara untuk melakukan hal yang sama jika array objek saya juga berisi angka? - person legacy; 25.07.2021

Anda harus mencoba Fusejs. http://fusejs.io/ Ini memiliki beberapa pengaturan menarik seperti ambang batas, yang memungkinkan beberapa kesalahan ketik (0,0 = sempurna, 1.0 = cocok dengan apa pun) dan keys untuk menentukan kunci apa pun yang ingin Anda cari.

const people = [
  { firstName: 'Bob', lastName: 'Smith', status: 'single' },
  { firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
  { firstName: 'Jim', lastName: 'Johnson', status: 'complicated' },
  { firstName: 'Sally', lastName: 'Fields', status: 'relationship' },
  { firstName: 'Robert', lastName: 'Bobler', status: 'single' },
  { firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' },
  { firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship'
    rogueBonusKey: 'bob likes salmon' },
]

const fuseOptions = {
  caseSensitive: false,
  shouldSort: true,
  threshold: 0.2,
  location: 0,
  distance: 100,
  maxPatternLength: 32,
  minMatchCharLength: 1,
  keys: [
    "firstName",
    "lastName",
    "rogueBonusKey",
  ]
};


const search = (txt) => {
  const fuse = new Fuse(people, fuseOptions);
  const result = fuse.search(txt);
  return result;
}
person Doppio    schedule 06.11.2017

Anda perlu memfilter array, lalu memfilter setiap kunci di objek agar cocok dengan ekspresi reguler. Contoh ini memecah masalah menjadi fungsi tanggung jawab tunggal dan menghubungkannya dengan konsep fungsional misalnya.

Tes kinerja disertakan, di chrome ini secara konsisten lebih cepat daripada contoh Dmitry. Saya belum menguji browser lain. Hal ini mungkin terjadi karena optimasi yang dilakukan chrome untuk memungkinkan jit memproses skrip lebih cepat ketika kode dinyatakan sebagai fungsi tanggung jawab tunggal kecil yang hanya mengambil satu jenis data sebagai masukan dan satu jenis data sebagai keluaran.

Karena pengujian ini memerlukan waktu sekitar 4 detik untuk memuat.

const people = [
  { firstName: 'Bob', lastName: 'Smith', status: 'single' },
  { firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
  { firstName: 'Jim', lastName: 'Johnson', status: 'complicated' },
  { firstName: 'Sally', lastName: 'Fields', status: 'relationship' },
  { firstName: 'Robert', lastName: 'Bobler', status: 'single' },
  { firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' },
  { firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship', rogueBonusKey: 'bob likes salmon' },
]

// run a predicate function over each key of an object
// const hasValue = f => o => 
//  Object.keys(o).some(x => f(o[x]))
const hasValue = f => o => {
  let key
  for (key in o) {
    if (f(o[key])) return true
  }
  return false
}

// convert string to regular expression
const toReg = str => 
  new RegExp(str.replace(/\//g, '//'), 'gi')

// test a string with a regular expression
const match = reg => x => 
  reg.test(x)

// filter an array by a predicate
// const filter = f => a => a.filter(a)
const filter = f => a => {
  const ret = []
  let ii = 0
  let ll = a.length
  for (;ii < ll; ii++) {
    if (f(a[ii])) ret.push(a[ii])
  }
  return ret
}

// **magic**
const filterArrByValue = value => {
  // create a regular expression based on your search value
  // cache it for all filter iterations
  const reg = toReg(value)
  // filter your array of people
  return filter(
    // only return the results that match the regex
    hasValue(match(reg))
  )
}

// create a function to filter by the value 'bob'
const filterBob = filterArrByValue('Bob')

// ########################## UNIT TESTS ########################## //

console.assert('hasValue finds a matching value', !!hasValue(x => x === 'one')({ one: 'one' }))
console.assert('toReg is a regular expression', toReg('reg') instanceof RegExp)
console.assert('match finds a regular expression in a string', !!match(/test/)('this is a test'))
console.assert('filter filters an array', filter(x => x === true)([true, false]).length === 1)

// ##########################   RESULTS   ########################## //

console.log(
  // run your function passing in your people array
  'find bob',
  filterBob(people)
)

console.log(
  // or you could call both of them at the same time
  'find salmon',
  filterArrByValue('salmon')(people)
)

// ########################## BENCHMARKS ########################## //

// dmitry's first function
const filterBy = (term) => {
  const termLowerCase = term.toLowerCase()
  return (person) =>
    Object.keys(person)
      .some(prop => person[prop].toLowerCase().indexOf(termLowerCase) !== -1)
}

// dmitry's updated function
const escapeRegExp = (str) => // or better use 'escape-string-regexp' package
  str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&")


const filterBy2 = (term) => {
  const re = new RegExp(escapeRegExp(term), 'i')
  return person => {
    for (let prop in person) {
      if (!person.hasOwnProperty(prop)) {
        continue;
      }
      if (re.test(person[prop])) {
        return true;
      }
    }
    return false;        
  }
}

// test stringify - incredibly slow
const fuzzyMatch = (collection, searchTerm) =>
  collection.filter((obj) => JSON.stringify(obj)
    .toLowerCase()
    .indexOf(searchTerm.toLowerCase()) !== -1
)

new Benchmark({ iterations: 1000000 })
  // test my function - fastest
  .add('synthet1c', function() {
    filterBob(people)
  })
  .add('dmitry', function() {
    people.filter(filterBy('Bob'))
  })
  .add('dmitry2', function() {
    people.filter(filterBy2('Bob'))
  })
  .add('guest', function() {
    fuzzyMatch(people, 'Bob')
  })
  .run()
<link rel="stylesheet" type="text/css" href="https://codepen.io/synthet1c/pen/WrQapG.css">
<script src="https://codepen.io/synthet1c/pen/WrQapG.js"></script>

person synthet1c    schedule 05.11.2017
comment
Ini sebenarnya sangat bagus. Terima kasih. Saya harus melihatnya lebih dekat besok. Saya ingin tahu bagaimana kinerjanya dibandingkan dengan solusi terbaru dari Guest dan Dmitry. Saya terlalu malas malam ini untuk mencoba membuat susunan raksasa. - person agm1984; 05.11.2017
comment
Jalankan cuplikan kode untuk melihat perbedaan kinerja di antara contoh-contoh tersebut - person synthet1c; 05.11.2017

Anda juga dapat menggunakan Ekspresi reguler dengan pengubah i menjadi melakukan pencocokan peka huruf besar-kecil dan metode RegExp. prototipe.test()

  • Ini sangat berguna ketika Anda ingin mengevaluasi beberapa properti objek seperti:

    new RegExp(searchString, 'i').test( 
      person.email || person.firstName || person.lastName
    )
    

Kode:

const people = [{ firstName: 'Bob', lastName: 'Smith', status: 'single' }, { firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' }, { firstName: 'Jim', lastName: 'Johnson', status: 'complicated' }, { firstName: 'Sally', lastName: 'Fields', status: 'relationship' }, { firstName: 'Robert', lastName: 'Bobler', status: 'single' }, { firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' }, { firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship', rogueBonusKey: 'bob likes salmon' }]

const searchString = 'Bob'

const found = people.filter(({ firstName }) => 
  new RegExp(searchString, 'i').test(firstName))

console.log(found)

person Yosvel Quintero Arguelles    schedule 10.08.2020

Jika seluruh objek yang cocok adalah hasil yang diharapkan, Anda dapat menggunakan for..of loop, Object.values(), Array.prototype.find()

consy searchString = "Bob";
const re = new RegExp(searchString, "i");
let res = [];
for (const props of Object.values(people))
  Object.values(props).find(prop => re.test(prop)) && res.push(props);
person guest271314    schedule 05.11.2017
comment
Terima kasih, ini keren. Sayangnya, karena alasan tertentu, diperlukan waktu 2-3x lebih lama untuk mengeksekusi dibandingkan solusi Dmitry. - person agm1984; 05.11.2017
comment
@ agm1984 Bisakah Anda membuat jsperf untuk didemonstrasikan? - person guest271314; 05.11.2017
comment
Tes yang lebih baik adalah dengan array yang terdiri dari ribuan objek. Di browser saya, yang pertama 2-3x lebih cepat menggunakan performance.now() - person agm1984; 05.11.2017
comment
@ agm1984 Di browser manakah Anda mencoba kode? Di Chromium 61 for..of tercepat Array.some const filterBy = (term) =› { const termLowerCase = term.toLowerCase() return (person) =› Object.keys(person) .some(prop =› person[prop ].toLowerCase().indexOf(termLowerCase) !== -1) } const ditemukan = people.filter(filterBy(searchString)) 115,822 ±3,10% 6% lebih lambat Untuk loop + Regex const re = RegExp baru(searchString, i ) let res = [] for (const props of Object.values(people)) Object.values(props).find(prop =› re.test(prop)) && res.push(props) 122,775 ±3,10% tercepat< /i> - person guest271314; 05.11.2017
comment
Pengujian di Chrome 61.0.3163 / Windows 10 0.0.0 - person agm1984; 05.11.2017
comment
Di Firefox 57 Array.some lebih cepat saat pertama kali dijalankan, lebih lambat saat dijalankan kedua di *nix Array.some const filterBy = (term) =› { const termLowerCase = term.toLowerCase() return (person) =› Object. kunci(orang) .some(prop =› person[prop].toLowerCase().indexOf(termLowerCase) !== -1) } const ditemukan = people.filter(filterBy(searchString)) 99,771 ±4,53% 5% lebih lambat Untuk dari loop + Regex const re = new RegExp(searchString, i) let res = [] for (const props dari Object.values(people)) Object.values(props).find(prop =› re.test(prop)) && res.push(props) 101.249 ±0,82% tercepat - person guest271314; 05.11.2017
comment
Saya melihat hal yang sama di mesin saya. Tampaknya menyimpang antara keduanya +/- 20% - person agm1984; 05.11.2017
comment
@ agm1984 Lihat stackoverflow.com/questions/46516234/ - person guest271314; 05.11.2017
comment
Terima kasih. For loop umumnya selalu lebih cepat, menurut pengalaman saya. Saya sedang mengerjakan kode Anda saat ini. Caranya cukup mudah untuk membedakan huruf besar-kecil atau tidak. - person agm1984; 05.11.2017

Anda dapat menggunakan Array.prototype.find().

const people = [
  { firstName: 'Bob', lastName: 'Smith', status: 'single' },
  { firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
  { firstName: 'Jim', lastName: 'Johnson', status: 'complicated' },
  { firstName: 'Sally', lastName: 'Fields', status: 'relationship' },
  { firstName: 'Robert', lastName: 'Bobler', status: 'single' },
  { firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' },
  { firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship',
    'rogueBonusKey': 'bob likes salmon' },
]

const searchString = 'Bob';

const person = people.find(({ firstName }) => firstName.toLowerCase() === searchString.toLowerCase());

console.log(person);

person xinthose    schedule 14.01.2021