如何从任何对象键中过滤不区分大小写的对象数组

Posted

技术标签:

【中文标题】如何从任何对象键中过滤不区分大小写的对象数组【英文标题】:How to filter an array of objects for case insensitive matches from any object key 【发布时间】:2018-04-17 11:45:35 【问题描述】:

我这里有这个示例代码,我正在尝试过滤匹配的对象,而不会增加代码复杂性或性能:

这里的代码根据一个明确定义的键过滤匹配,它不区分大小写。

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)

目标:

    我希望它不区分大小写 我希望它从任意键返回匹配项 我希望它使用 contains 找到不完全匹配

类似这样的:

// 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' ,

我已经搜索了与Array.filter() 相关的文档,我绝对可以制定涉及Array.reduce() 和循环使用Object.keys(obj).forEach() 的解决方案,但我想知道是否有一种简洁、高效的方式来处理这种类型模糊搜索。

类似这样的:

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]这绝对有效,但可以接受吗?

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)

内存占用优化:

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

转换为函数:

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

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

这里有一些很好的答案;到目前为止,我选择了这个来满足我的过滤需求:

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)

我使它能够支持区分大小写并排除特定键。

【问题讨论】:

预期结果是整个匹配对象还是仅特定属性、值对? 匹配整个对象 【参考方案1】:

如果我们假设所有的属性都是字符串,那么你可以按照下面的方式来做

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)

更新:使用 RegExp 和更老派的替代解决方案 :) 但速度快 2 倍

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))

【讨论】:

这肯定比字符串化要好,非常肯定。 我刚刚意识到我也应该添加排除键,所以它可以是通用的。你的看起来应该很快就能支持。 我喜欢你对.some() 的使用。我不知道那存在。我会在Array.some(more time has passed) 之后将这个答案标记为正确,假设没有其他人通过疯狂的数据宝石。 用最后一个解决方案更新了你的测试:jsperf.com/fuzzy-match-objects-2 我将你的 for in 替换为 for (const key of Object.keys(obj)) ;)【参考方案2】:

如果整个匹配对象都是预期结果,您可以使用for..of循环、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);

【讨论】:

谢谢,这很酷。不幸的是,由于某种原因,它的执行时间比 Dmitry 的解决方案长 2-3 倍。 @agm1984 你能创建一个jsperf来演示吗? 在 Firefox 57 "Array.some" 在第一次运行时更快,在 *nix " Array.some 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)) 99,771 ±4.53% 慢 5% For of loop + Regex 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) 101,249 ±0.82% 最快" 我在我的机器上看到的差不多。它似乎在 +/- 20% 之间有偏差 @agm1984 见***.com/questions/46516234/…【参考方案3】:

您需要过滤数组,然后过滤对象中的每个键以匹配正则表达式。此示例将问题分解为单一职责功能,并将它们与功能概念联系起来,例如。

包括性能测试,在 chrome 中,这始终比 Dmitry 的示例快。我没有测试任何其他浏览器。这可能是因为当代码表示为仅将一种类型的数据作为输入和一种类型的数据作为输出的小型单一职责函数时,chrome 进行了优化以允许 jit 更快地处理脚本。

由于测试,加载大约需要 4 秒。

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>

【讨论】:

这真的很棒。谢谢。明天我得仔细看看这个。我很好奇与 Guest 和 Dmitry 的更新解决方案相比,这在性能方面如何。今晚我懒得尝试做一个巨大的阵列。 运行代码sn -p查看示例之间的性能差异【参考方案4】:

你应该给 fusejs 一个机会。 http://fusejs.io/ 它有一些有趣的设置,例如阈值,允许一些拼写错误(0.0 = 完美,1.0 = 匹配任何内容)和keys 指定您要搜索的任何键。

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;

【讨论】:

【参考方案5】:

您也可以使用Regular expressions 和i 修饰符进行不区分大小写的匹配和方法RegExp.prototype.test()

当您想要评估多个对象属性时非常方便,例如:

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

代码:

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)

【讨论】:

【参考方案6】:

您可以使用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);

【讨论】:

以上是关于如何从任何对象键中过滤不区分大小写的对象数组的主要内容,如果未能解决你的问题,请参考以下文章

字符串包含数组中的任何项目(不区分大小写)

如何使用 Django 模型进行不区分大小写的查询

如何仅从一个键中过滤对象并将其作为 json 返回?

GSON:如何从 Json 中获取不区分大小写的元素?

如何使用 NSPredicate 过滤这些 NSArray?

对数组中的字符串不区分大小写的NSPredicate?