【问题标题】:Javascript multiple condition array filterJavascript多条件数组过滤器
【发布时间】:2017-05-28 08:02:28
【问题描述】:

我需要帮助来整理基于多个条件的数组搜索。此外,所有条件都是有条件的,这意味着我可能需要也可能不需要过滤这些条件。我有什么:

要过滤的对象数组:

var data = [{
    "_id" : ObjectId("583f6e6d14c8042dd7c979e6"),
    "transid" : 1,
    "acct" : "acct1",
    "transdate" : ISODate("2012-01-31T05:00:00.000Z"),
    "category" : "category1",
    "amount" : 103
},
{
    "_id" : ObjectId("583f6e6d14c8042dd7c2132t6"),
    "transid" : 2,
    "acct" : "acct2",
    "transdate" : ISODate("2012-01-31T05:00:00.000Z"),
    "category" : "category2",
    "amount" : 103
},
{
    "_id" : ObjectId("583f6e6d14c8042dd7c2132t6"),
    "transid" : 3,
    "acct" : "acct2",
    "transdate" : ISODate("2016-07-31T05:00:00.000Z"),
    "category" : "category1",
    "amount" : 103
},
{
    "_id" : ObjectId("583f6e6d14c8042dd7c2132t6"),
    "transid" : 4,
    "acct" : "acct2",
    "transdate" : ISODate("2012-01-31T05:00:00.000Z"),
    "category" : "category2",
    "amount" : 103
},
{
    "_id" : ObjectId("583f6e6d14c8042dd7c2132t6"),
    "transid" : 5,
    "acct" : "acct2",
    "transdate" : ISODate("2012-01-31T05:00:00.000Z"),
    "category" : "category3",
    "amount" : 103
},
{
    "_id" : ObjectId("583f6e6d14c8042dd7c152g2"),
    "transid" : 6,
    "acct" : "acct3",
    "transdate" : ISODate("2016-10-31T05:00:00.000Z"),
    "category" : "category3",
    "amount" : 103
}]

我正在根据另一个混合元素数组过滤上述对象数组。这些元素代表以下搜索字段:

  • “searchstring”:在数据数组中的所有字段中搜索任何 匹配的文本序列

  • 具有代表帐户类型和真假的键值的对象 用于指示是否应用于过滤的值

  • 过滤transdate的开始日期

  • 过滤transdate的结束日期

  • 要过滤类别的类别名称

具有搜索条件的数组看起来像这样(但如果某些字段不是必需的,它们将被设置为未定义或只是一个空字符串或数组):

var filtercondition = {
    "p",
    {acct1:true,acct2:false,acct3:true...}
    "2016-06-01",
    "2016-11-30",
    "category3"
}

实现这一目标的最佳方法是什么?我设计的是对过滤器数组中的每个元素进行单独搜索,但这似乎不是最优的并且非常乏味。我愿意重新设计我的设置...

【问题讨论】:

  • 数据的预期大小是多少?预计将在哪里运行?最近的电脑?移动的?旧电脑?强大的服务器? “最佳方式”实际上取决于多种因素。

标签: javascript filtering


【解决方案1】:
// You wrote that it's an array, so changed the braces 
var filtercondition = ["p",
{acct1:true,acct2:false,acct3:true...}
"2016-06-01",
"2016-11-30",
"category3"
];

var filtered = data.filter(o => {
    if(filtercondition[0] && !o.category.includes(filtercondition[o])) { // checking just the category, but you can check if any of more fields contains the conditions 
        return false;
    }
    if(filtercondition[1]) {
        for(var key in filtercondition[1]) {
        if(filtercondition[1][key] === true && o.acct != key) {
            return false;
        }
        }
    }
    if(filtercondition[2] && o.transdate < filtercondition[2]) {
        return false;
    }
    if(filtercondition[3] && o.transdate > filtercondition[3]) {
        return false;
    }
    if(filtercondition[4] && o.category !== filtercondition[4]) {
        return false;
    }

    return true;
});

两个注意事项: - 更改了filtercondition 的大括号,使其成为一个数组,但是我建议改用一个对象。 - 这个{acct1:true,acct2:false,acct3:true...} 示例对我来说没有意义,因为它表明acct 字段应该同时是acct1acct3

【讨论】:

  • 谢谢。这个解决方案对我有用。我对您建议的代码进行了轻微编辑,以“返回 true”,以防所有其他条件都失败,否则不会返回任何内容。
【解决方案2】:

创建一个函数数组,每个函数代表一个条件。

这是一些演示该方法的示例代码...

 var conditions = [];

 // Dynamically build the list of conditions
 if(startDateFilter) {
    conditions.push(function(item) { 
       return item.transdate >= startDateFilter.startDate;
    });
 };

 if(categoryFilter) {
     conditions.push(function(item) {
         return item.cateogry === categoryFilter.category;
     });
 };
 // etc etc

一旦有了条件数组,就可以使用Array.prototype.every 对项目运行每个条件。

 var itemsMatchingCondition = data.filter(function(d) {
     return conditions.every(function(c) {
         return c(d);
     });
 });

或者,使用更紧凑的箭头函数:

const itemsMatchingCondition = data.filter(d => conditions.every(c => c(d));

【讨论】:

    【解决方案3】:

    首先,您需要为数组使用方括号而不是花括号:

    var filtercondition = [
        "p",
        {acct1:true,acct2:false,acct3:true...},
        "2016-06-01",
        "2016-11-30",
        "category3"
    ];
    

    再一次,我不认为数组是最好的数据类型。试试这样的对象:

    var filtercondition = {
        query: "p",
        accounts: {acct1:true,acct2:false,acct3:true...},
        date1: "2016-06-01",
        date2: "2016-11-30",
        category: "category3"
    };
    

    然后,尝试使用 Array.prototype.filter:

    var filtered = data.filter(function(obj) {
        for (var key in filtercondition) {
            // if condition not met return false
        }
        return true;
    });
    

    【讨论】:

      【解决方案4】:

      我会使用一堆小的粒度函数并组合它们。

      //only some utilities, from the top of my mind
      var identity = v => v;
      
      //string-related
      var string = v => v == null? "": String(v);
      var startsWith = needle => haystack => string(haystack).startsWith(needle);
      var endsWith = needle => haystack => string(haystack).endsWith(needle);
      var contains = needle => haystack => string(haystack).contains(needle);
      
      //do sth with an object
      var prop = key => obj => obj != null && prop in obj? obj[prop]: undefined;
      var someProp = fn => obj => obj != null && Object.keys(obj).some(k => fn(k) );
      var someValue = fn => obj => obj != null && Object.keys(obj).some(k => fn(obj[k]) );
      
      //logic
      var eq = b => a => a === b;
      var not = fn => function(){ return !fn.apply(this, arguments) };
      var and = (...funcs) => funcs.reduce((a, b) => function(){
              return a.apply(this, arguments) && b.apply(this, arguments);
          });
      
      var or = (...funcs) => funcs.reduce((a, b) => function(){
              return a.apply(this, arguments) || b.apply(this, arguments);
          });
      
      //composition
      var compose = (...funcs) => funcs.reduce((a, b) => v => return a(b(v)));
      var chain = (...funcs) => funcs.reduceRight((a, b) => v => return a(b(v)));
      
      //and whatever else you want/need
      //but stay granular, don't put too much logic into a single function
      

      和一个示例组合:

      var filterFn = and(
          //some value contains "p"
          someValue(contains("p")),
      
          //and
          chain(
              //property "foo"
              prop("foo"), 
              or(
                  //either contains "asdf"
                  contains("asdf"),
      
                  //or startsWith "123"
                  startsWith("123")
              )
          ),
      )
      

      由于我不知道您是如何构建过滤条件的,因此我无法准确告诉您如何将它们解析成这样的组合,但您可以像这样组合它们:

      //start with something basic, so we don't ever have to check wether filterFn is null
      var filterFn = identity;
      
      //and extend/compose it depending on some conditions
      if(/*hasQuery*/){
          filterFn = and(
              // previous filterFn(obj) && some value on obj contains `query`
              filterFn,
              someValue(contains(query)))
          )
      }
      
      if(/*condition*/){
          //extend filterFn
          filterFn = or(
              // (obj.foo === null) || previous filterFn(obj)
              chain(prop("foo"), eq(null)),
              filterFn
          );
      }
      

      等等

      【讨论】:

      • 最干净的解决方案!太棒了?
      • 只是var eq =&gt; a =&gt; b =&gt; a === b; 中的一个小错字,应该是var eq = a =&gt; b =&gt; a === b;。与not 相同;)
      • @bflemi3 这取决于。是的,ramda 提供了这些方法,但它还提供了更多。可能有用,也可能只是臃肿。 API 试图变得灵活,比如柯里化……,但这是有代价的。现在看看上面的方法。有些基本上是变相的“运营商”;我不确定您是否想要基本的“成本”。因此,如果您需要 ramda 提供的大部分功能(包括灵活性),它可能会很有用。如果你不这样做,那可能是矫枉过正。使您的应用膨胀,或者在最坏的情况下甚至减慢它的速度。
      【解决方案5】:

      首先,几点:

      • 如果您要在浏览器中使用 data 对象,则它是无效的。可能数据来自MongoDB,对吧?您的后端(数据源)应该有一种方法可以对其进行正确编码并删除 ObjectIDISODate 引用。

      • 您的 filtercondition 不是有效的 JavaScript 对象/JSON。查看我的示例。

      因此,您可以使用Array#filter 方法过滤您的数据数组。

      类似的东西:

      let data = [{
          "_id" : "583f6e6d14c8042dd7c979e6",
          "transid" : 1,
          "acct" : "acct1",
          "transdate" : "2012-01-31T05:00:00.000Z",
          "category" : "category1",
          "amount" : 103
      },
      {
          "_id" : "583f6e6d14c8042dd7c2132t6",
          "transid" : 2,
          "acct" : "acct2",
          "transdate" : "2012-01-31T05:00:00.000Z",
          "category" : "category2",
          "amount" : 103
      },
      {
          "_id" : "583f6e6d14c8042dd7c2132t6",
          "transid" : 5,
          "acct" : "acct2",
          "transdate" : "2012-01-31T05:00:00.000Z",
          "category" : "category3",
          "amount" : 103
      }];
      
      
      let filterToApply = {
          acct: {
              acct1: true,
              acct2: false,
              acct3: true
          },
          initialDate: "2016-06-01",
          finalDate: "2016-11-30",
          category: "category3"
      }
      
      
      let filterData = (array, filter) => {
      
          return array.filter( (item) => {
      
              /* here, you iterate each item and compare with your filter,
                 if the item pass, you must return true. Otherwise, false */
      
      
              /* e.g.: category check (if present only) */
              if (filter.category && filter.category !== item.category) 
                  return false;
              }
      
              /* add other criterias check... */ 
      
              return true;
      });
      
      }
      
      let dataFiltered = filterData(data, filterToApply);
      console.log(dataFiltered);
      

      【讨论】:

      • So, you can sort your data array with Array#filter method. Array#filter 不是 Array#sort ;)
      猜你喜欢
      • 2015-10-28
      • 1970-01-01
      • 2017-08-29
      • 2023-03-08
      • 1970-01-01
      • 2019-03-02
      • 2020-08-05
      相关资源
      最近更新 更多