JS 高阶函数reduce 用法

reduce 是 JavaScript 数组的高阶函数之一,用于对数组中的元素进行累积操作,最终返回一个累积结果。reduce 接受一个回调函数作为参数,该回调函数在每次迭代中执行,并接受四个参数:累积值(也称为累加器),当前元素值,当前索引和原始数组。

语法:

array.reduce(callback(accumulator, currentValue, currentIndex, array), initialValue)

其中:

  • callback:表示回调函数,它执行数组元素的累积逻辑。
  • accumulator:表示累积值,它是在每次回调函数执行时累积计算的结果。如果提供了 initialValue,那么累积值将从 initialValue 开始;否则,它将从数组的第一个元素开始。
  • currentValue:表示当前元素的值。
  • currentIndex:表示当前元素的索引(可选)。
  • array:表示原始数组(可选)。
  • initialValue:表示累积值的初始值(可选)。

常见解决问题的场景例子:

  1. 数组求和
const numbers = [1, 2, 3, 4, 5];

const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);

console.log(sum); // Output: 15
  1. 数组求平均值
const numbers = [10, 20, 30, 40, 50];
const average = numbers.reduce((accumulator, currentValue, currentIndex, array) => {
  accumulator += currentValue;
  if (currentIndex === array.length - 1) {
    return accumulator / array.length;
  } else {
    return accumulator;
  }
}, 0);
console.log(average); // Output: 30
  1. 数组中的最大值和最小值
const numbers = [8, 3, 11, 6, 21, 4];
const max = numbers.reduce((accumulator, currentValue) => Math.max(accumulator, currentValue), numbers[0]);
const min = numbers.reduce((accumulator, currentValue) => Math.min(accumulator, currentValue), numbers[0]);

console.log(max); // Output: 21
console.log(min); // Output: 3
  1. 数组元素计数
const fruits = ['apple', 'banana', 'orange', 'apple', 'banana', 'apple'];
const count = fruits.reduce((accumulator, currentValue) => {
  accumulator[currentValue] = (accumulator[currentValue] || 0) + 1;
  return accumulator;
}, {});

console.log(count); // Output: { apple: 3, banana: 2, orange: 1 }
  1. 字符串中字符出现的次数
const str = "hello world";
const charCount = str.split('').reduce((accumulator, currentValue) => {
  accumulator[currentValue] = (accumulator[currentValue] || 0) + 1;
  return accumulator;
}, {});

console.log(charCount); // Output: { h: 1, e: 1, l: 3, o: 2, ' ': 1, w: 1, r: 1, d: 1 }
  1. 对象属性求和

假设有一个包含商品信息的数组,每个商品对象都有 price 属性。现在我们想要计算所有商品价格的总和:

const products = [
  { name: 'Product A', price: 100 },
  { name: 'Product B', price: 200 },
  { name: 'Product C', price: 150 },
];

const total = products.reduce((accumulator, product) => accumulator + product.price, 0);
console.log(total); // Output: 450
  1. 数组扁平化

假设有一个多维数组,我们想要将它扁平化为一个一维数组:

const nestedArray = [[1, 2], [3, 4], [5, 6]];
const flattenedArray = nestedArray.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
console.log(flattenedArray); // Output: [1, 2, 3, 4, 5, 6]
  1. 数据分类

假设有一个包含人员信息的数组,每个人员对象都有 age 属性,现在我们想要根据人员年龄将其分类:

const people = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 25 },
  { name: 'Dave', age: 35 },
];

const peopleByAge = people.reduce((accumulator, person) => {
  const age = person.age;
  if (!accumulator[age]) {
    accumulator[age] = [];
  }
  accumulator[age].push(person);
  return accumulator;
}, {});

console.log(peopleByAge);
/* Output:
{
  25: [
    { name: 'Alice', age: 25 },
    { name: 'Charlie', age: 25 }
  ],
  30: [
    { name: 'Bob', age: 30 }
  ],
  35: [
    { name: 'Dave', age: 35 }
  ]
}
*/
  1. 数组去重

通过 reduce 将数组去重:

const numbers = [1, 2, 3, 2, 4, 1, 5, 3, 6];
const uniqueNumbers = numbers.reduce((accumulator, currentValue) => {
  if (!accumulator.includes(currentValue)) {
    accumulator.push(currentValue);
  }
  return accumulator;
}, []);

console.log(uniqueNumbers); // Output: [1, 2, 3, 4, 5, 6]
  1. 函数管道(Function Piping)
const data = [1, 2, 3, 4, 5];

const addOne = num => num + 1;
const double = num => num * 2;
const subtractFive = num => num - 5;

const result = data.reduce((accumulator, currentValue) => {
  return [addOne, double, subtractFive].reduce((acc, fn) => fn(acc), currentValue);
}, 0);

console.log(result); // Output: 3 (先加一,再乘以2,再减去5)
  1. 多条件数据分类

假设有一个包含人员信息的数组,每个人员对象有 agegender 属性。现在我们想要根据人员的年龄和性别进行多条件分类:

const people = [
  { name: 'Alice', age: 25, gender: 'female' },
  { name: 'Bob', age: 30, gender: 'male' },
  { name: 'Charlie', age: 25, gender: 'male' },
  { name: 'Dave', age: 35, gender: 'male' },
  { name: 'Eve', age: 25, gender: 'female' },
];

const peopleByAgeAndGender = people.reduce((accumulator, person) => {
  const { age, gender } = person;
  if (!accumulator[age]) {
    accumulator[age] = {};
  }
  if (!accumulator[age][gender]) {
    accumulator[age][gender] = [];
  }
  accumulator[age][gender].push(person);
  return accumulator;
}, {});

console.log(peopleByAgeAndGender);
/* Output:
{
  25: {
    female: [
      { name: 'Alice', age: 25, gender: 'female' },
      { name: 'Eve', age: 25, gender: 'female' }
    ],
    male: [
      { name: 'Charlie', age: 25, gender: 'male' }
    ]
  },
  30: {
    male: [
      { name: 'Bob', age: 30, gender: 'male' }
    ]
  },
  35: {
    male: [
      { name: 'Dave', age: 35, gender: 'male' }
    ]
  }
}
*/
  1. 数组分块

假设有一个排序好的数组,我们想要将其分块成指定大小的子数组:

const sortedArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const chunkSize = 3;
const chunkedArray = sortedArray.reduce((accumulator, currentValue, currentIndex) => {
  const chunkIndex = Math.floor(currentIndex / chunkSize);
  if (!accumulator[chunkIndex]) {
    accumulator[chunkIndex] = [];
  }
  accumulator[chunkIndex].push(currentValue);
  return accumulator;
}, []);

console.log(chunkedArray);
/* Output:
[
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
  [10]
]
*/
  1. 数组分组

假设有一个数组,我们想要根据某个条件对数组进行分组:

const numbers = [10, 20, 35, 45, 55, 60, 70];

const groups = numbers.reduce((accumulator, currentValue) => {
  const groupKey = currentValue >= 50 ? 'greaterThan50' : 'lessThan50';
  if (!accumulator[groupKey]) {
    accumulator[groupKey] = [];
  }
  accumulator[groupKey].push(currentValue);
  return accumulator;
}, {});

console.log(groups);
/* Output:
{
  greaterThan50: [55, 60, 70],
  lessThan50: [10, 20, 35, 45]
}
*/

当结合其他高阶用法时,reduce 可以应用于非常复杂的场景。其中一个非常复杂的场景是实现函数组合或管道。

函数组合是一种将多个函数合并成一个新函数的技术,其中每个函数的输出都是下一个函数的输入。我们可以使用 reduce 和函数组合技术来实现函数管道,将一系列函数应用于数据并依次传递结果。

函数管道示例

假设有一个字符串数组,我们想要将其转换为大写、去除空格并拼接为一个单词:

const words = ['  hello', 'world ', 'JavaScript '];

const compose = (...functions) => input => functions.reduceRight((acc, fn) => fn(acc), input);

const toUpperCase = str => str.toUpperCase();
const trim = str => str.trim();
const concatenate = (str1, str2) => str1 + str2;

const result = words.map(compose(trim, toUpperCase)).reduce(concatenate);
console.log(result); // Output: "HELLOWORLDJAVASCRIPT"

在上面的例子中,我们首先定义了三个简单的字符串处理函数:toUpperCase(将字符串转换为大写)、trim(去除字符串两端的空格)和 concatenate(将两个字符串拼接在一起)。然后我们定义了一个 compose 函数,该函数接受任意数量的函数,并返回一个新函数,该新函数依次将传入的函数应用于输入数据。在 compose 函数中,我们使用 reduceRight 来依次应用函数,这样可以确保函数按照从右到左的顺序执行。

然后,我们使用 map 方法将 words 数组中的每个字符串依次传递给 compose 函数,然后使用 reduce 方法将转换后的结果拼接在一起,得到最终的结果:"HELLOWORLDJAVASCRIPT"。

这个例子展示了如何结合函数组合和 reduce 实现一个复杂的函数管道,通过这种方式,你可以在实际开发中处理更加复杂的数据转换和处理任务。

还有一种复杂的场景是使用 reduce 和递归实现树形结构的操作。这样的场景在处理层次结构数据,比如树形结构的 JSON 数据或嵌套对象数组时非常有用。

树形结构操作示例

假设有一个包含树形结构的 JSON 数据,每个节点都有 idchildren 属性。我们想要通过 reduce 实现根据 id 查找节点的功能:

const treeData = {
  id: 1,
  children: [
    {
      id: 2,
      children: [
        {
          id: 3,
          children: []
        },
        {
          id: 4,
          children: []
        }
      ]
    },
    {
      id: 5,
      children: []
    }
  ]
};

const findNodeById = (tree, id) => {
  return tree.id === id
    ? tree
    : tree.children.reduce((acc, child) => acc || findNodeById(child, id), null);
};

const foundNode = findNodeById(treeData, 3);
console.log(foundNode); // Output: { id: 3, children: [] }

在上面的例子中,我们首先定义了一个 findNodeById 函数,该函数使用 reduce 递归地遍历树形结构。它将传入的 id 与当前节点的 id 进行比较,如果匹配,就返回当前节点;否则,它会继续递归遍历节点的子节点,直到找到匹配的节点或遍历完整个树。

通过这种方式,我们可以使用 reduce 和递归实现复杂的树形结构操作,比如根据 id 查找节点、计算节点的深度或广度优先遍历等。

reduce 在处理树形结构的例子中可以非常有用。在树形结构中,每个节点可能会有子节点,形成层次结构。使用 reduce 可以在树形结构中实现递归遍历、搜索、转换等操作。下面是一个在树形结构中使用 reduce 的例子:

假设有以下树形结构数据表示文件系统的目录结构:

const fileSystem = {
  name: 'root',
  type: 'folder',
  children: [
    {
      name: 'folder1',
      type: 'folder',
      children: [
        {
          name: 'file1.txt',
          type: 'file',
        },
        {
          name: 'file2.txt',
          type: 'file',
        },
      ],
    },
    {
      name: 'folder2',
      type: 'folder',
      children: [
        {
          name: 'file3.txt',
          type: 'file',
        },
      ],
    },
  ],
};
  1. 计算文件数量

我们可以使用 reduce 在树形结构中递归计算文件的数量:

const countFiles = (node) => {
  if (node.type === 'file') {
    return 1;
  } else if (node.type === 'folder') {
    return node.children.reduce((acc, child) => acc + countFiles(child), 0);
  } else {
    return 0;
  }
};

const totalFiles = countFiles(fileSystem);
console.log(totalFiles); // Output: 3
  1. 获取指定文件路径

我们可以使用 reduce 在树形结构中递归查找指定文件路径:

const findFileByPath = (node, path) => {
  if (node.name === path) {
    return node;
  } else if (node.type === 'folder') {
    for (const child of node.children) {
      const foundFile = findFileByPath(child, path);
      if (foundFile) {
        return foundFile;
      }
    }
  }
  return null;
};

const filePath = 'file3.txt';
const foundFile = findFileByPath(fileSystem, filePath);
console.log(foundFile); // Output: { name: 'file3.txt', type: 'file' }
  1. 转换树形结构

我们可以使用 reduce 在树形结构中递归转换节点数据:

const transformTree = (node) => {
  if (node.type === 'file') {
    return { file: node.name };
  } else if (node.type === 'folder') {
    return {
      folder: node.name,
      children: node.children.reduce((acc, child) => [...acc, transformTree(child)], []),
    };
  } else {
    return null;
  }
};

const transformedTree = transformTree(fileSystem);
console.log(transformedTree);
/* Output:
{
  folder: 'root',
  children: [
    {
      folder: 'folder1',
      children: [
        { file: 'file1.txt' },
        { file: 'file2.txt' }
      ]
    },
    {
      folder: 'folder2',
      children: [
        { file: 'file3.txt' }
      ]
    }
  ]
}
*/

猜你喜欢

转载自blog.csdn.net/qq_20173195/article/details/131828794