如何通过许多不同的类别列表对数据项列表进行分类,其中每个列表包含多个不同的类别值? 奖金有条件的

问题描述

我是JavaScript的新手,所以我甚至很难知道从哪里开始。请有人帮我。我已经尝试了如下所示的内容,但与下面显示的所需输出完全不同

我有此配料表,其中含有数量和有效值:

const Ingris = [
  {
    val: "onion,",amount: "1",},{
    val: "paprika",amount: "½ tsp",{
    val: "yogurt",amount: "1/2 Cup",{
    val: "fine sea salt",amount: "½ tsp  ",];

我想根据以下这些变量对它们进行分类

var spices = ["paprika","parsley","peppermint","poppy seed","rosemary"];
var meats = ["steak","ground beef","stewing beef","roast beef","ribs","chicken"];
var dairy = ["milk","egg","cheese","yogurt"];
var produce = ["peppers","radishes","onions","Tomato"];

这就是我想要获得的:

    // desired output:
    
    const ShoppingList = [
      {
        produceOutput: [
          {
            val: "garlic,minced",amount: "8 cloves ",],spicesOutput: [
          {
            val: "paprika",{
            val: "onion",//The ingredient only goes in here if the value is not in the categories

        NoCategoryOutput: [
          {
            val: "fine sea salt",];

我制作了一个正则表达式来检查该值,但是它不起作用并且不能识别Paprikapaprikagreek yogurtyogurt间的值,请有人帮我

const Categorize = (term) => {
  let data = []
  if (term) {
    const newData = Ingris.filter(({ Ingris }) => {
      if (RegExp(term,"gim").exec(Ingris))
        return ingridients.filter(({ amount }) => RegExp(term,"gim").exec(amount))
          .length;
    });
    data.push(newData)
  } else {
    data = []
  }
};

解决方法

您可以使用i标志将搜索数组更改为正则表达式,以进行不区分大小写的搜索,并将成分val转换为两侧均带有通配符的正则表达式(如果它们为复数形式)或有其他信息):

const Ingris = [
  {
val: "onion,",amount: "1",},{
val: "paprika",amount: "½ tsp",{
val: "yogurt",amount: "1/2 Cup",{
val: "fine sea salt",amount: "½ tsp  ",];
var spices = [/paprika/i,/parsley/i,/peppermint/i,/poppy seed/i,/rosemary/i];
var meats = [/steak/i,/ground beef/i,/stewing beef/i,/roast beef/i,/ribs/i,/chicken/i];
var dairy = [/milk/i,/egg/i,/cheese/i,/yogurt/i];
var produce = [/pepper/i,/radish/i,/onion/i,/Tomato/i];

function shoppingList(array,ingredient) {
  for (var i = 0; i < array.length; i++) {
    if (ingredient.match(array[i])) {
      return ingredient;
    }
  }
}


function Categorize() {
  let produceOutput = [];
  let NoCategoryOutput = [];

  for (const [key,value] of Object.entries(Ingris)) {
    var ingredient = '/\.*' + value.val + '\.*/';
    if (shoppingList(spices,ingredient) || shoppingList(meats,ingredient) || shoppingList(dairy,ingredient) || shoppingList(produce,ingredient)) {
    produceOutput.push(value);
    } else {
    NoCategoryOutput.push(value);
    }
  }
    var ShoppingList = new Object();
    ShoppingList.produceOutput = produceOutput;
    ShoppingList.NoCategoryOutput = NoCategoryOutput;
    console.log(ShoppingList);
}

   Categorize();

如果您想让它同时适用于复数和单数成分,则必须确保搜索数组的值都为单数(即不是"onions",而是{{1} }。

这能回答您的问题吗?

,

有关所选方法的非常详细的说明,可以在下面提供的示例代码下找到。

const ingredientList = [{
  "amount": "1","val": "packet pasta"
},{
  "val": "Chicken breast"
},{
  "val": "Ground ginger"
},{
  "amount": "8 cloves","val": "garlic,minced"
},{
  "amount": "1","val": "onion"
},{
  "amount": "½ tsp","val": "paprika"
},{
  "amount": "1 Chopped","val": "Tomato"
},{
  "amount": "1/2 Cup","val": "yogurt"
},{
  "amount": "1/2 teaspoon","val": "heavy cream"
},"val": "fine sea salt"
}];

const spiceList = ["paprika","parsley","peppermint","poppy seed","rosemary"];
const meatList = ["steak","ground beef","stewing beef","roast beef","ribs","chicken breast"];
const dairyList = ["milk","eggs","egg","cheese","yogurt","cream"];
const produceList = ["peppers","pepper","radishes","radish","onions","onion","Tomatos","Tomato","Garlic","Ginger"];


function groupItemByCategoryDescriptorAndSourceKey(collector,item) {
  const {
    descriptorList,uncategorizableKey,itemSourceKey,index
  } = collector;

  const isEqualCategoryValues = (
    ((typeof collector.isEqualCategoryValues === 'function') && collector.isEqualCategoryValues) ||
    ((itemValue,categoryValue) => {

      // this is the default implementation of how to determine equality
      // of two values in case no other function was provided via the
      // `collector`'s `isEqualCategoryValues` property.

      itemValue = itemValue.trim().replace((/\s+/g),' ').toLowerCase();
      categoryValue = categoryValue.trim().replace((/\s+/g),' ').toLowerCase();

      return (itemValue === categoryValue);
    })
  );
  let currentCategoryList;

  function doesBoundValueEqualCategoryValue(categoryValue) {
    return isEqualCategoryValues(this.value,categoryValue);
  }
  function doesBoundValueMatchCategoryAndWhichIsIt(descriptor) {
    const isMatchingValue = descriptor.valueList.some(
      doesBoundValueEqualCategoryValue,this
    );
    if (isMatchingValue) { // ... and which is it?
      const categoryKey = descriptor.targetKey;

      currentCategoryList = (
        index[categoryKey] ||
        (index[categoryKey] = [])
      );
      currentCategoryList.push(item);
    }
    return isMatchingValue;
  }

  const isCategorizable = descriptorList.some(
    doesBoundValueMatchCategoryAndWhichIsIt,{ value: item[itemSourceKey] }
  );
  if (!isCategorizable) {

    currentCategoryList = (
      index[uncategorizableKey] ||
      (index[uncategorizableKey] = [])
    );
    currentCategoryList.push(item);
  }
  return collector;
}


console.log(
  'Shopping List :',JSON.parse(JSON.stringify([ // in order to get rid of SO specific object reference logs.
  ingredientList.reduce(groupItemByCategoryDescriptorAndSourceKey,{

      descriptorList: [{
        targetKey: 'spicesOutput',valueList: spiceList
      },{
        targetKey: 'meatsOutput',valueList: meatList
      },{
        targetKey: 'dairyOutput',valueList: dairyList
      },{
        targetKey: 'produceOutput',valueList: produceList
      }],uncategorizableKey: 'noCategoryOutput',// isEqualCategoryValues: anyCustomImplementationWhichDeterminesEqualityOfTwoCategoryValues
      itemSourceKey: 'val',index: {}

  }).index]))
);


function isEqualCategoryValues(itemValue,categoryValue) {
  // this is a custom implementation of how
  // to determine equality of two category.

  itemValue = itemValue.trim().replace((/\s+/g),' ').toLowerCase();
  categoryValue = categoryValue.trim().replace((/\s+/g),' ').toLowerCase();

  return (
    (itemValue === categoryValue) ||
    RegExp('\\b' + categoryValue + '\\b').test(itemValue)
  );
}

console.log(
  'Shopping List (custom method for equality of category values) :',JSON.parse(JSON.stringify([
  ingredientList.reduce(groupItemByCategoryDescriptorAndSourceKey,isEqualCategoryValues,itemSourceKey: 'val',index: {}

  }).index]))
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

方法

OP所提供的问题几乎看起来像一个(相当复杂的)reduce任务,从配料项目列表到索引/映射,其中具有针对配料源列表项目的不同目标列表。

从我的角度来看,将这种减少结果作为唯一项推入数组是有问题的。

const shoppingListIndex = {
  produceOutput: [{
    val: "garlic,minced",amount: "8 cloves ",}],spicesOutput: [{
    // ...
  }],NoCategoryOutput: [{
    val: "fine sea salt",}]
};

// ... instead of ...

const ShoppingList = [{
  produceOutput: [{
    // ...
  }],NoCategoryOutput: [{
    // ...
  }]
}];

任何直接的方法都会以某种方式逐步选择一个成分项目,然后针对每个项目再次搜索每个给定的类别列表,直到成分项目的val值与当前类别中的第一个最佳类别项目匹配为止列表。

可以通过简化功能来概括此任务。为了更加通用,这种实现方式不应对环境以及所涉及列表的名称和数量等进行任何假设(也不应“知道” )。

因此,这种实现必须是抽象的且可配置的。这意味着应该清楚如何将OP的问题分解为这样的抽象和配置。

化简方法accumulator可用作configcollector对象。

因此,为了不依赖于类别列表的数量及其名称,确实向collector提供了类别描述符对象的列表。实现将知道/识别此配置项为descriptorList

此外,为了灵活地定义配料项目的类别目标列表的名称,此类描述符项目不仅包含可能匹配的类别值的列表,而且还具有目标列表名称的属性...

一个通用的reduce任务的可能用例可能类似于下一个代码示例...

ingredientList.reduce(groupItemByCategoryDescriptorAndSourceKey,{

  descriptorList: [{
    targetKey: 'spicesOutput',valueList: spiceList // the OP's category list example.
  },{
    targetKey: 'meatsOutput',valueList: meatList // the OP's category list example.
  },{
    targetKey: 'dairyOutput',valueList: dairyList // the OP's category list example.
  },{
    targetKey: 'produceOutput',valueList: produceList // the OP's category list example.
  }]
});

此外,完全正常工作的reduce任务的配置必须提供任何源列表项的属性名称(键),以便将其值与任何提供的类别值列表中的任何类别值进行比较。实现将知道/识别此配置项为itemSourceKey

另一个必要的配置项是uncategorizableKey。其值将用作无法分类的源列表项的特殊列表的键(意味着在所有提供的类别列表中均未找到匹配项)。

将有一个可选的isEqualCategoryValues配置键。如果提供,则此属性引用一个自定义函数,该函数确定两个类别值的相等性;即,第一个itemValue参数保存当前处理的源列表项的引用,第二个categoryValue参数保存对当前处理的类别列表的当前处理值的引用。

最后有index,它始终是一个空对象文字,并且reduce进程将其结果写入该引用。

因此,通用还原任务的完整用例可能类似于下一个代码示例...

const shoppingListIndex =
  ingredientList.reduce(groupItemByCategoryDescriptorAndSourceKey,{

    descriptorList: [{
      targetKey: 'spicesOutput',valueList: spiceList
    },{
      targetKey: 'meatsOutput',valueList: meatList
    },{
      targetKey: 'dairyOutput',valueList: dairyList
    },{
      targetKey: 'produceOutput',valueList: produceList
    }],index: {}

  }).index;

比较/确定平等

现在,将通用计算部分与案例特定的配置分开了,必须专注于如何确定两个值的相等性,对于给定的示例,一方面是配料项的val值, ,另一方面,在OP的类别数组之一中列出的许多值。

例如,{ ... "val": "onion" ... }甚至是{ ... "val": "Chicken breast" ... }都应该在"onion"的{​​{1}}和produceList的{​​{1}}中找到各自相等的对应对象"chicken breast"

对于meatList"Chicken breast",很明显比较过程必须将两个运算符都转换成各自的规范化变体。 toLowerCase足够了,但是为了安全起见,应该首先trim设置一个值,然后replace进行其他操作,以保护所有空白序列剩余的空格序列,带有单个空白字符。

因此,对于相等性而言已经足够好的标准比较可能看起来像...

"chicken breast"

...实际上,这是在没有将用于确定相等性的自定义函数提供给reducer的collector / config对象的情况下作为reducer函数的内部部分实现的后备。

对于任何写得较差的成分或类别值,这种对原因的幼稚值相等性检查确实会立即失败,例如示例代码中的那些{... function isEqualCategoryValues(itemValue,categoryValue) { itemValue = itemValue.trim().replace((/\s+/g),' ').toLowerCase(); return (itemValue === categoryValue); }); 与{{1}中的"Ground ginger" },... "Ginger"中的produceList"heavy cream",... "cream"中的dairyList vs "garlic,minced"

很明显,需要进行更好的定制化平等检查,以完全满足OP的需求/要求/接受标准。但是,解决问题现在归结为仅提供一种定制功能,该功能仅解决了如何确切确定值相等性的部分。

掌握"Garlic"produceList的已经标准化的变体,并考虑在由空格分隔和/或终止和/或终止的字符串值中出现了不止两个单词有效的方法可以基于正则表达式/(RegExp

"ground ginger"
"ginger"

因此,可靠地涵盖OP用例的自定义console.log( "(/\\bginger\\b/).test('ground ginger') ?",(/\bginger\b/).test('ground ginger') ); console.log( "RegExp('\\\\b' + 'ginger' + '\\\\b','i').test('ground ginger') ?",RegExp('\\b' + 'ginger' + '\\b').test('ground ginger') );函数的实现与内部使用的默认相等性检查几乎完全相同。此外,它还具有基于.as-console-wrapper { min-height: 100%!important; top: 0; }的检查功能,它可以及时构建和测试正确的正则表达式,就像本段上方的可执行示例代码所演示的那样。

完整的自定义实现可能看起来像这样...

isEqualCategoryValues

约简逻辑/实施

已经了解了为什么(通用的reduce任务但其配置灵活,因此能够处理各种用例)以及如何使用reduce函数收集器配置...

RegExp

...现在,只需按照上面»方法«部分的字面意思,就可以继续执行reduce逻辑的实际实现。

再次阅读本节,可能会形成一个完全由 堆叠的some任务 构建的解决方案。 function isEqualCategoryValues(itemValue,' ').toLowerCase(); return ( (itemValue === categoryValue) || RegExp('\\b' + categoryValue + '\\b').test(itemValue) ); } 的本质是在找到第一个匹配项(布尔值const shoppingListIndex = ingredientList.reduce(groupItemByCategoryDescriptorAndSourceKey,{ descriptorList: [{ /* ... */ },{ /* ... */ }/*,... */],index: {} }).index; 返回值)的情况下尽快离开搜索任务(中断迭代周期)。这正是解决OP问题所需要做的。并且堆叠是由于搜索的值应 查找 匹配范围内 类别值列表 列表

由于基于some的方法的检测功能不仅要确保“提前退出” ,还需要提供有关第二比较值的信息,因此必须使用callback function's this context作为数据载体。

基于true的检测方法中最外部的方法解决了编写/收集找到的类别的附加任务。因此,该方法可以命名为some,其用法很可能类似于下一个代码示例...

some

正如我们所看到的,整个doesBoundValueMatchCategoryAndWhichIsIt堆栈的最终返回值是否表明了(成分)值是否可以分类。

// iterate the (descriptor) list of category lists. const isCategorizable = descriptorList.some( doesBoundValueMatchCategoryAndWhichIsIt,{ value: item[itemSourceKey] } ); 的实现可能与此类似...

some

使用doesBoundValueMatchCategoryAndWhichIsIt,当前处理的(成分)项目值的传递几乎已经结束。此函数将其绑定的当前item-value和其第一个参数(当前category-value)转发给均等函数(后者作为自定义变体或内部默认值提供)...

function doesBoundValueMatchCategoryAndWhichIsIt(descriptor) {

  // iterate the current category list.
    
  // boolean return value
  const isMatchingValue = descriptor.valueList.some(
    doesBoundValueEqualCategoryValue,this
  );

  // act upon the return value.
  //
  // - push the item of the related value- match
  //   into the corresponding category list (create
  //   the latter in case it did not yet exist).

  if (isMatchingValue) { // ... and which is it?
    const categoryKey = descriptor.targetKey;

    currentCategoryList = (
      index[categoryKey] ||
      (index[categoryKey] = [])
    );
    currentCategoryList.push(item);
  }

  // forces "early exit" in case of being `true`.
  return isMatchingValue;
}

最后,如果无法对当前处理的(成分)项目值进行分类,则将该项目推入到由收集器doesBoundValueEqualCategoryValue属性标识的列表中。

就是这样。感谢阅读。

奖金(有条件的)

考虑到OP的另一个相关问题... How does one parse best each item of an ingredient list and does create a new object based on each parsing result? ...和one of the approaches……一个功能强大,类似于下一个基于function doesBoundValueEqualCategoryValue(categoryValue) { return isEqualCategoryValues(this.value,categoryValue); } 的可配置过程链...

uncategorizableKey
reduce
const ingredientList = [
  '1 packet pasta','Chicken breast','Ground ginger','8 cloves garlic,minced','1 onion','½ tsp paprika','1 Chopped Tomato','1/2 Cup yogurt','1/2 teaspoon heavy cream','½ tsp fine sea salt'
];
const measuringUnitList = [
  'tbsp','tablespoons','tablespoon','tsp','teaspoons','teaspoon','chopped','oz','ounces','ounce','fl. oz','fl. ounces','fl. ounce','fluid ounces','fluid ounce','cups','cup','qt','quarts','quart','pt','pints','pint','gal','gallons','gallon','ml','milliliter','l','liter','g','gram','kg','kilogram'
];
const spiceList = ["paprika","Ginger"];


function isEqualCategoryValues(itemValue,categoryValue) {
  itemValue = itemValue.trim().replace((/\s+/g),' ').toLowerCase();

  return (
    (itemValue === categoryValue) ||
    RegExp('\\b' + categoryValue + '\\b').test(itemValue)
  );
}


console.log('Ingredient List :',ingredientList);

console.log(
  'Shopping List Index :',JSON.parse(JSON.stringify( // in order to get rid of SO specific object reference logs.
  ingredientList.reduce(collectNamedCaptureGroupData,{

      regXPrimary: createUnitCentricCapturingRegX(measuringUnitList),regXSecondary: unitlessCapturingRegX,defaultKey: 'val',list: []

    }).list.reduce(groupItemByCategoryDescriptorAndSourceKey,index: {}

  }).index))
);