正确使用函数式编程:如何有效地使用.map()?

时间:2017-01-05 15:59:19

标签: javascript functional-programming ecmascript-6

我有两个数组:hobbieList和hobbieTypes。我想在hobbieTypes数组中分离hobbieList中的元素,具体取决于它们的索引值。像这样:

hobbieList = ["Rock", "Pop", "Surf", "Blues", "Soccer"];
hobbieTypes= ["music", "music", "sport", "music", "sport"];

...所以使用命令式编程和使用fors的典型应用程序将是:

let musicHobbies = [];
for(let i=0;i<hobbieList.length;i++){
   if(hobbieTypes[i] === "music"){
      musicHobbies.push(hobbieList[i]);
   }
}
// Now "musicHobbies" has only the hobbies which type is "music".

但我希望通过函数式编程,使用 map()函数来做到这一点,并尽量保持代码尽可能简短有效。像这样:

let musicHobbies = 
     hobbieList.map( (elem, ind) => (hobbieTypes[ind] === "music") ? elem : null;

我觉得它看起来不错,但是有一个问题:空位置没有被删除,因此数组显示“null”,其中条件不满足

我可以进入的第二个apporach,它工作得很好,但我不知道它是否是以功能方式编码的正确方法,是这样的:

 let musicHobbies = [];
 rawHobbies.map( (elem, ind) => (hobbiesTypes[ind] === "music") ? musicHobbies.push(elem) : elem);

这是第二个解决方案,设计得很好吗?或者我应该改变什么?而且,我应该使用map()函数返回的数组(如案例1),或者如果我只是将结果放在外部数组中(如情况2),那么它是否正常?

感谢您的帮助!

编辑:

这种其他方法呢?

      rawHobbies.map( (elem, ind) => {
        switch(hobbiesTypes[ind]){
           case "music":    this.hobbies.music.push(elem); break;
           case "plan":    this.hobbies.plans.push(elem); break;
           case "hobbie":  this.hobbies.hobbies.push(elem); break;
        }
      });

我想我会用forEach做得更好。你认为我应该使用它,使用forEach,或做这样的事情......?

this.hobbies.music = rawHobbies.filter( (x,i) => hobbieTypes[i] == "music");
this.hobbies.plans = rawHobbies.filter( (x,i) => hobbieTypes[i] == "plan");
this.hobbies.hobbies = rawHobbies.filter( (x,i) => hobbieTypes[i] == "hobbie");

我不确定这些选项中的哪一个会发挥最佳效果。意见?

编辑2:

我最终使用命令式编程来实现性能,但是感谢大家对函数式编程可以使用的不同方法。我的最终解决方案只是一个简单的foor循环:

     for(let i=0;i<rawHobbies.length;i++){
          switch(hobbiesTypes[i]){
           case "music":   this.hobbies.music.push(rawHobbies[i]); break;
           case "plan":    this.hobbies.plans.push(rawHobbies[i]); break;
           case "hobbie":  this.hobbies.hobbies.push(rawHobbies[i]); break;
        }
     }

5 个答案:

答案 0 :(得分:4)

编辑后,看起来你想要更像groupBy的东西。

您可以使用.reduce()实现这一目标。这对你有用:

&#13;
&#13;
const hobbieList = ["Rock", "Pop", "Surf", "Blues", "Soccer"];
const hobbieTypes= ["music", "music", "sport", "music", "sport"];
var groupedHobbies = hobbieTypes.reduce( (acc, item, index) => { 
        acc[item] = [...(acc[item] || []), hobbieList[index]];
        return acc;
 }, {});

console.log(groupedHobbies);
&#13;
&#13;
&#13;

通常,您希望尽可能减少副作用,并且不要在地图功能上同时执行大量操作(应将每个元素映射/转换/转换为新的元素,而不更改原始元素)。通过使用小函数,您可以在以后将它们组合起来(如果您继续遵循功能方法,您可能会阅读更多相关内容)

我建议您使用.filter()方法而不是.map()

类似的东西:

hobbieList.filter((elem, ind) => hobbieTypes[ind] === "music")

将返回一个如下数组:

[
  "Rock",
  "Pop",
  "Blues"
]

答案 1 :(得分:3)

我只想使用filter()方法:

const hobbieList = ["Rock", "Pop", "Surf", "Blues", "Soccer"];
const hobbieTypes = ["music", "music", "sport", "music", "sport"];
console.log(hobbieList.filter((x, i) => hobbieTypes[i] === "music"));

至于您的修改:我认为使用filter()map()(您实际使用forEach()更清晰)更清晰。这可能不是最快的方式,但它肯定更有用。

答案 2 :(得分:2)

您似乎想要做的是按类型分组爱好。这个分组过程的输出应该是什么?为每种类型创建业余爱好列表的单独变量的可扩展性不是很高。创建一个对象,其键是类型,并且其值是该类型的爱好列表,这样会更好:

hobbiesByType = {
  music: ["Rock", "Pop", "Blues"],
  sport: ["Surf", "Soccer"]
};

现在我可以将所有音乐爱好称为hobbiesByType.music

此分组流程的输入应该是什么?目前,您有两个并行数组:

hobbieList = ["Rock", "Pop", "Surf", "Blues", "Soccer"];
hobbieTypes= ["music", "music", "sport", "music", "sport"];

但这不是表示此信息的最佳方式。任何时候我想检查一下,我必须引用两个数组。我可能不得不并行迭代它们。拥有单个表示会更好,而通常的方法是对象数组

hobbies = [
  {name: "Rock", type: "music"},
  ...
];

现在让我们考虑如何进行分组。你会在这里找到很多关于SO的例子和方法。这是一个非常基本的使用for循环:

var hobbiesByType = {};

for (var i = 0; i < hobbies.length; i++) {
  var hobby = hobbies[i];
  var hobbyName = hobby.name;
  var hobbyType = hobby.type;
  if (!(hobbyType in hobbiesByType)) hobbiesByType[hobbyType] = [];
  hobbiesByType[hobbyType].push(hobby.name);
}

但你想使用&#34; functional&#34;数组方法。以上最紧凑的形式将使用reduce,并且如下所示:

hobbiesByType = hobbies.reduce((result, hobby) => {
  if (!(hobby.type in result)) result[hobby.type] = [];
  result[hobby.type].push(hobby.name);
  return result;
}, {});

那是所有ES5,而不是ES6。您可以使用ES6参数解构来简化它:

hobbiesByType = hobbies.reduce((result, type, name}) => {
  (result[type] = result[type] || []).push(name);
  return result;
}, {});

但是,不是在每种类型下创建业余爱好的名称的数组,而是自己创建业余爱好对象的数组可能更好。然后,如果爱好曾经包含其他信息 - 或许skillLevel - 那么您可以轻松地从分组表示中进行访问。

顺便说一句,单数形式是&#34;业余爱好&#34;而不是&#34; hobbie&#34;。

答案 3 :(得分:2)

使用变量数据作为对象键将是对象的可怕滥用。相反,Map可以为您想要的数据做出更好的选择。

zip功能与Array.prototype.reduce组合将产生非常实用的结果

let hobbyList = ["Rock", "Pop", "Surf", "Blues", "Soccer"]
let hobbyTypes= ["music", "music", "sport", "music", "sport"]

const zip = ([x,...xs], [y,...ys]) => {
  if (x === undefined || y === undefined)
    return []
  else
    return [[x,y], ...zip(xs, ys)]
}

let result = zip(hobbyTypes, hobbyList).reduce((acc, [type, hobby]) => {
  if (acc.has(type))
    return acc.set(type, [...acc.get(type), hobby])
  else
    return acc.set(type, [hobby])
}, new Map())

console.log(result.get('music'))
// => [ 'Rock', 'Pop', 'Blues' ]

console.log(result.get('sport'))
// => [ 'Surf', 'Soccer' ]

如果确实必须使用某个对象,但实际上不应该使用,则可以使用map.entriesArray.from来执行此操作。

Array.from(result.entries())
  .reduce((acc, [type, hobbies]) =>
    Object.assign(acc, { [type]: hobbies }), {})

// => { music: [ 'Rock', 'Pop', 'Blues' ], sport: [ 'Surf', 'Soccer' ] }

答案 4 :(得分:1)

Map用于转换其他内容,而不是获取符合条件的元素。在这种情况下,您必须使用过滤功能。