数组的reduce方法的应用场景及相关面试题

简介: 数组的reduce方法的应用场景及相关面试题

数组的reduce方法的应用场景及相关面试题


reduce 最大的作用:从一个数组得到一个值,对数组中的每个元素执行 reducer 函数(升序执行)

reduce 打开相当于:

// 这里为了更加方便理解 统一加上initValue
[x1, x2, x3].reduce(f,initValue) 
// f起码是两个参数 
f(f(f(initValue,x1), x2), x3);

写reduce的核心其实很明了:

  • 找到initValue
  • 找到ff始终有两个参数 accitem

这样,能解决大部分的问题了,而且新手也有了线索,容易写reduce。

以下既是应用场景,也是很好练习的例子。

累加数组所有值

const sumFn = (acc, item) => acc + item;
const sum = [1, 2, 3, 4].reduce(sumFn, 0);
// 6
console.log(sum);

累加对象数组里的值

const sumFn = (acc, item) => acc + item.x;
const sum = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }].reduce(sumFn, 0);
// 6
console.log(sum);

降维数组,二维变成一维

// 之所以用concat不用push 是concat返回合并后的数组,而push返回数组长度
const flat = (acc, item) => acc.concat(item);
const res = [
  [0, 1],
  [2, 3],
  [4, 5]
].reduce(flat, []);
// [0, 1, 2, 3, 4, 5]
console.log(res);

扁平化任意维数组 --- 面试常考

const flat = (acc, item) => acc.concat(item);
const res = [ 0, 1, [2, 3, [4, 5]] ].reduce(flat, []);
// [0, 1, 2, 3, 4, 5]
console.log(res)
// 这里更新了,稍微抽象出一个函数,当前项是数组的话 flatten(item) 不是数组的话用concat合并下
const flatten = arr =>
  arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);

计算数组中每个元素出现的次数 --- 面试常考

const names = ["Alice", "Bob", "Tiff", "Bruce", "Alice"];
const getCount = (obj, key) => {
  key in obj || (obj[key] = 0);
  obj[key]++;
  return obj;
};
const res = names.reduce(getCount, {});
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }
console.log(res);

按属性对 object 分类 --- 面试常考

const people = [
  { id: 1, name: "Alice", age: 21 },
  { id: 2, name: "Max", age: 20 },
  { id: 3, name: "Jane", age: 20 }
];
const getType = (obj, item) => {
  const key = item.age;
  key in obj || (obj[key] = []);
  obj[key].push(item);
  return obj;
};
const res = people.reduce(getType, {});
// { 20: [ { name: 'Max', age: 20 }, { name: 'Jane', age: 20 } ], 21: [{ name: 'Alice', age: 21 }] }
console.log(res);

这里可以稍微升级下,可以对任意字段进行分类

const classifyArray = (arr, keyClassified) => {
  const getType = (obj, item) => {
    const key = item[keyClassified];
    key in obj || (obj[key] = []);
    obj[key].push(item);
    return obj;
  };
  return arr.reduce(getType, {});
};
const people = [
  { id: 1, name: "Alice", age: 21, gender: 0 },
  { id: 2, name: "Max", age: 20, gender: 1 },
  { id: 3, name: "Jane", age: 20, gender: 0 }
];
// { '0': [ { id: 1, name: 'Alice', age: 21, gender: 0 }, { id: 3, name: 'Jane', age: 20, gender: 0 } ], '1': [ { id: 2, name: 'Max', age: 20, gender: 1 } ] }
console.log(classifyArray(people, "gender"));

使用扩展运算符处理对象数组中的数组

// 合并所有的书
var friends = [
  {
    name: "Anna",
    books: ["Bible", "Harry Potter"],
    age: 21
  },
  {
    name: "Bob",
    books: ["War and peace", "Romeo and Juliet"],
    age: 26
  },
  {
    name: "Alice",
    books: ["The Lord of the Rings", "The Shining"],
    age: 18
  }
];
const sumArr = (oldArr, item) => {
  return [...oldArr, ...item.books];
};
const res = friends.reduce(sumArr, []);
// [ 'Bible', 'Harry Potter', 'War and peace', 'Romeo and Juliet', 'The Lord of the Rings', 'The Shining' ]
console.log(res);

数组去重 --- 面试常考

var myArray = ["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"];
const addArr = (acc, item) => (acc.includes(item) ? acc : [...acc, item]);
const res = myArray.reduce(addArr, []);
// [ 'a', 'b', 'c', 'e', 'd' ]
console.log(res);

当然还有更简单粗暴的法子

const unique = arr => Array.from(new Set(arr));
// [ 'a', 'b', 'c', 'e', 'd' ]
console.log(
  unique(["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"])
);

按顺序运行 promise --- 面试常考

// promise function 1
function p1(a) {
  return new Promise(resolve => {
    resolve(a * 5);
  });
}
// !!这个是普通函数哟
function f(a) {
  return a * 2;
}
// promise function 2
function p2(a) {
  return new Promise(resolve => {
    resolve(a - 2);
  });
}
const arr = [p1, f, p2];
const runOrderly = (acc, item) => acc.then(item);
const res = arr.reduce(runOrderly, Promise.resolve(10));
// 98
console.log(res);

再抽象下写个通用的顺序执行的函数

const runOrderly = (arr, initValue) =>
  arr.reduce((acc, item) => acc.then(item), Promise.resolve(initValue));

生成组合函数compose --- 面试常考

举个例子,就明白啥是组合函数了

const sum = (a, b) => a + b;
const len = str => str.length;
const addCurrency = str => "$" + str;
// 现在想要先 将两字符求和,然后求长度,再然后再长度前加个$,这就是一个新的函数,是已知函数的组合
const newFn = (a, b) => addCurrency(len(sum(a, b)));
console.log(newFn("xyz", "abc"));

但是不想像上面那样,地狱式嵌套的生成newFn,希望能 compose(addCurrency,len,sum),这里可以试试reduceRight

// compose(addCurrency,len,sum)
const compose = (...fns) => {
  return (...args) => {
    // 这里因为最后一个函数是两个参数,和别的不一样,所以这边单独把它扔出来,这样其他的都符合fn(acc)
    const lastFn = fns.pop();
    let initValue = lastFn(...args);
    const f = (acc, fn) => fn(acc);
    return fns.reduceRight(f, initValue);
  };
};
const newFn = compose(addCurrency, len, sum);
console.log(newFn("xyz", "abc"));
// 用箭头函数简化
const compose = (...fns) => (...args) =>
  fns.reduceRight((acc, fn) => fn(acc), fns.pop()(...args));

当然如果思维再厉害点,也可以用reduce

// compose(addCurrency,len,sum)
const compose = (...fns) =>
  fns.reduce((acc, cur) => (...args) => acc(cur(...args)));

这个法子很不容易想,但是假设现在只组合两个函数,len和sum

  • compose就相当于(...args) => len(sum(...args))
  • 而用reduce的话相当于(...args)=>[len,sum].reduce((acc,curFn)=>(...args) => acc(curFn(...args)))
  • [len,sum]换成别的数组也一样
  • 我这也是马后炮啦,下次我自己写估计又只会reduceRight

reduce 的坑

  • 空数组不能 reduce
  • reduce(f)没 initValue 的话,f 的第一次参数是arr[0] arr[1] 1 arr
  • reduce(f,initValue)有 initValue 的话,f 的第一次参数是initValue arr[0] 0 arr
  • f必须至少有两个参数,总共四个参数acc(累计器) cur(当前值) curIndex (当前索引) src (源数组)

可读性更高的话,可以传入 initValue,这样每次的操作都是一致的不容易出错。本文也是如此操作

引用

目录
相关文章
|
SQL 缓存 Java
MyBatis场景面试题
MyBatis与MyBatisPlus均属ORM框架,前者擅长复杂SQL及动态查询,后者封装API简化单表操作。常用XML标签如if、foreach提升SQL灵活性。MyBatis支持一级(SqlSession级)与二级(NameSpace级)缓存,提升查询效率。#{}防SQL注入,${}用于动态表名等场景。
550 62
|
消息中间件 测试技术 数据库
吊打面试官!应用间交互如何设计?
【10月更文挑战第18天】设计应用间交互需从明确需求、选择合适方式、设计协议与数据格式、考虑安全性和权限管理、进行性能优化和测试五个方面入手。明确功能和用户需求,选择接口调用、消息队列、数据库共享或文件交换等方式,确保交互高效、安全、可靠。展示这些能力将在面试中脱颖而出。
317 1
|
人工智能 前端开发 Java
Java 面试资料中相关代码使用方法与组件封装方法解析
这是一份详尽的Java面试资料代码指南,涵盖使用方法与组件封装技巧。内容包括环境准备(JDK 8+、Maven/Gradle)、核心类示例(问题管理、学习进度跟踪)、Web应用部署(Spring Boot、前端框架)、单元测试及API封装。通过问题库管理、数据访问组件、学习进度服务和REST接口等模块化设计,帮助开发者高效组织与复用功能,同时支持扩展如用户认证、AI推荐等功能。适用于Java核心技术学习与面试备考,提升编程与设计能力。资源链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
257 6
Java 面试资料中相关代码使用方法与组件封装方法解析
|
算法
面试场景题:如何设计一个抢红包随机算法
本文详细解析了抢红包随机算法的设计与实现,涵盖三种解法:随机分配法、二倍均值法和线段切割法。随机分配法通过逐次随机分配金额确保总额不变,但易导致两极分化;二倍均值法优化了金额分布,使每次抢到的金额更均衡;线段切割法则将总金额视为线段,通过随机切割点生成子金额,手气最佳金额可能更高。代码示例清晰,结果对比直观,为面试中类似算法题提供了全面思路。
2280 16
|
人工智能 算法 数据库
美团面试:LLM大模型存在哪些问题?RAG 优化有哪些方法?_
美团面试:LLM大模型存在哪些问题?RAG 优化有哪些方法?_
|
消息中间件 存储 Java
招行面试:10Wqps场景,RocketMQ 顺序消费 的性能 如何提升 ?
45岁资深架构师尼恩在其读者群中分享了关于如何提升RocketMQ顺序消费性能的高并发面试题解析。面对10W QPS的高并发场景,尼恩详细讲解了RocketMQ的调优策略,包括专用方案如增加ConsumeQueue数量、优化Topic设计等,以及通用方案如硬件配置(CPU、内存、磁盘、网络)、操作系统调优、Broker配置调整、客户端配置优化、JVM调优和监控与日志分析等方面。通过系统化的梳理,帮助读者在面试中充分展示技术实力,获得面试官的认可。相关真题及答案将收录于《尼恩Java面试宝典PDF》V175版本中,助力求职者提高架构、设计和开发水平。
招行面试:10Wqps场景,RocketMQ 顺序消费 的性能 如何提升 ?
|
Java 程序员 调度
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
719 9
|
安全 Java 程序员
Java面试必问!run() 和 start() 方法到底有啥区别?
在多线程编程中,run和 start方法常常让开发者感到困惑。为什么调用 start 才能启动线程,而直接调用 run只是普通方法调用?这篇文章将通过一个简单的例子,详细解析这两者的区别,帮助你在面试中脱颖而出,理解多线程背后的机制和原理。
1139 12
|
架构师 数据库
大厂面试高频:数据库乐观锁的实现原理、以及应用场景
数据库乐观锁是必知必会的技术栈,也是大厂面试高频,十分重要,本文解析数据库乐观锁。关注【mikechen的互联网架构】,10年+BAT架构经验分享。
大厂面试高频:数据库乐观锁的实现原理、以及应用场景
|
Java 关系型数据库 数据库
京东面试:聊聊Spring事务?Spring事务的10种失效场景?加入型传播和嵌套型传播有什么区别?
45岁老架构师尼恩分享了Spring事务的核心知识点,包括事务的两种管理方式(编程式和声明式)、@Transactional注解的五大属性(transactionManager、propagation、isolation、timeout、readOnly、rollbackFor)、事务的七种传播行为、事务隔离级别及其与数据库隔离级别的关系,以及Spring事务的10种失效场景。尼恩还强调了面试中如何给出高质量答案,推荐阅读《尼恩Java面试宝典PDF》以提升面试表现。更多技术资料可在公众号【技术自由圈】获取。