Scala 第二部分 算术
最编程
2024-04-22 15:33:53
...
Scala 第二篇 算子篇
上接:Scala 第一篇 基础篇
-
一、数组方法
- 1、数组的遍历
- 2、数组获取元素
- 3、数组排序
- 4、交集,并集,补集
- 5、集合转换操作
- 6、合并,拆解,填充
- 7、分组,排列
-
二、算子
- 1、简单计算
- 2、高阶计算
一、数组方法
1、数组的遍历
val array = Array(1, 2, 3, 4, 5)
val indices: Range = array.indices // 提取下标区间
for (i <- indices) {
println(array(i)) // 根据下标提取元素,注意是小括号
}
for (i <- 0 until array.length) { // 使用 until 遍历数组下标
println(array(i))
}
array.foreach(println) // 使用 foreach 遍历数组元素
val iter = array.iterator // 正向迭代器
val reIter = array.reverseIterator // 逆向迭代器
while (iter.hasNext) {
println(iter.next())
}
2、数组获取元素
-
获取首尾元素
val buffer = ArrayBuffer(1, 2, 3, 4) val head = buffer.head // 第一个元素 val opt1 = buffer.headOption // 第一个元素 val tail = buffer.tail // 除第一个元素以外的其他元素 // 剩余元素(阶梯) 4(1,2,3,4),3(2,3,4),2(3,4),1(4),0() val tails: Iterator[ArrayBuffer[Int]] = buffer.tails val last = buffer.last // 最后一个元素 val opt2 = buffer.lastOption // 最后一个元素 val init = buffer.init // 除最后一个元素以外的其他元素 val inits = buffer.inits // 剩余元素(阶梯) 4(1,2,3,4),3(1,2,3),2(1,2),1(1),0()
-
获取满足条件连续长度和元素
val size1 = buffer.prefixLength(e => e < 3) // 从集合的开头满足条件元素数量 val size2 = buffer.segmentLength(e => e > 1, 1) //从 from 下标的连续满足条件元素数量 val buf1 = buffer.take(1) // 从集合的开头开始获取指定数量的元素。 val buf2 = buffer.takeRight(2) // 从集合的末尾开始获取指定数量的元素。 val buf3 = buffer.takeWhile(e => e < 3) // 从集合的开头开始获取连续满足条件的元素。
3、数组排序
-
sorted
,sortBy
,sortWith
val buffer = ArrayBuffer((5, 2), (2, 5), (3, 2), (2, 4)) val sort1 = buffer.sorted // 默认按第一个值升序,如果第一个值相等按第二个值升序,如果有第三个值同理 val sort2 = buffer.sortBy(_._2) // 指定排序值,按第二个值排序(默认升序) // sortWith 定义元素之间的比较规则 // 如果第一个值不相等按第一个值降序,否则按第二个值降序 val sort3 = buffer.sortWith((a, b) => ( if(a._1 != b._1) a._1 > b._1 else a._2 > b._2 ))
-
隐式函数 (不建议)
val buffer = ArrayBuffer((5, 2), (2, 5), (3, 2), (2, 4)) implicit val orderingTp2: Ordering[(Int, Int)] = Ordering.by(_._2) // 按第二个值 val sort5 = buffer.sorted // 按第二个值升序
4、交集,并集,补集
// 结果类型:前(左)置确定
val buffer = ArrayBuffer(1, 2, 3, 4, 5)
val seq = Seq(3, 4, 5, 6, 7)
// 交集
val intersect: ArrayBuffer[Int] = buffer.intersect(seq)
println("交集:" + intersect.mkString(", "))
// 并集
val union: ArrayBuffer[Int] = buffer.union(seq)
println("并集:" + union.mkString(", "))
// 差集
val diff: ArrayBuffer[Int] = buffer.diff(seq)
println("差集:" + diff.mkString(", "))
输出:
交集:3, 4, 5
并集:1, 2, 3, 4, 5, 3, 4, 5, 6, 7
差集:1, 2
补充:
// 连接字符串
val str0: String = buffer.mkString
val str1: String = buffer.mkString(sep:String) // 指定分割符
val str2: String = buffer.mkString(start:String,sep:String,end:String) // 指定,连接字符串的起始部分,分隔符,结束部分
5、集合转换操作
-
一个维度的转变
import scala.collection.mutable.ArrayBuffer val buffer = ArrayBuffer(1, 2, 3, 4, 5) // 正向差异类型转变,(将数字x映射为x*2并转化为字符串) val doubledBuffer: ArrayBuffer[String] = buffer.map(x => (x * 2).toString) // 正向同类型转变 val squaredBuffer: ArrayBuffer[Int] = buffer.transform(x => x * x) // 逆向转变 val reversedSquaredBuffer: ArrayBuffer[Int] = buffer.reverseMap(x => x * x)
-
二维变换
import scala.collection.mutable.ArrayBuffer val buffer2D = ArrayBuffer( ArrayBuffer(1, 2, 3), ArrayBuffer(4, 5, 6), ArrayBuffer(7, 8, 9) ) // 使用 flatMap 将二维转换为一维,并对值做映射 (*2) val flatBuffer: ArrayBuffer[Int] = buffer2D.flatMap(_.map(_ * 2)) // 使用 flatten 将二维转换为一维 val flattenedBuffer: ArrayBuffer[Int] = buffer2D.flatten // 使用 transpose 进行二维数组缓冲区转置 val transpose: ArrayBuffer[ArrayBuffer[Int]] = buffer2D.transpose
-
类型变换
import scala.collection.mutable import scala.collection.parallel.mutable.ParArray val buffer = mutable.Buffer(1, 2, 2, 3, 4, 5) val buffer_tp2 = mutable.Buffer((1, "a"), (2, "b"), (3, "c")) val mutableBuffer: mutable.Buffer[Int] = buffer.toBuffer // 转换为可变的,为了增删 val array: Array[Int] = buffer.toArray // 转换为数组,(下标,排序) val parArray: ParArray[Int] = buffer.toParArray // 转换为并行数组,分布式计算 val map: Map[Int, String] = buffer_tp2.toMap // 转换为 Map,键值映射 val set: Set[Int] = buffer.toSet // 转换为集合(去重)
6、合并,拆解,填充
import scala.collection.mutable.ArrayBuffer
val buffer1 = ArrayBuffer('a', 'b', 'c', 'd')
val buffer2 = ArrayBuffer(1, 2, 3)
// 最短原则合并,结果:(a,1), (b,2), (c,3)
val zippedShortest: ArrayBuffer[(Char, Int)] = buffer1.zip(buffer2)
// 最长原则合并,长度不够填充默认值,结果:(a,1), (b,2), (c,3), (d,0)
val zippedAll: ArrayBuffer[(Char, Int)] = buffer1.zipAll(buffer2, 'x', 0)
// 带索引合并
val zippedWithIndex: ArrayBuffer[(Char, Int)] = buffer1.zipWithIndex
// 拆解操作,拆解二元组,拆解三元组为unzip3
val (unzipped1, unzipped2): (ArrayBuffer[Char], ArrayBuffer[Int]) = zippedShortest.unzip
// 填充操作,不足6为填充: '_'
val paddedBuffer: ArrayBuffer[Char] = buffer1.padTo(6, '_')
// 输出结果
println("最短原则合并:" + zippedShortest)
println("最长原则合并:" + zippedAll)
println("带索引合并:" + zippedWithIndex)
println("拆解结果:" + unzipped1 + ", " + unzipped2)
println("填充后的数组:" + paddedBuffer)
输出:
最短原则合并:ArrayBuffer((a,1), (b,2), (c,3))
最长原则合并:ArrayBuffer((a,1), (b,2), (c,3), (d,0))
带索引合并:ArrayBuffer((a,0), (b,1), (c,2), (d,3))
拆解结果:ArrayBuffer(a, b, c), ArrayBuffer(1, 2, 3)
填充后的数组:ArrayBuffer(a, b, c, d, _, _)
7、分组,排列
import scala.collection.mutable.ArrayBuffer
val buffer = ArrayBuffer(1, 2, 3, 4, 5)
// 切片操作,提取下标为1开始4结束的元素(包头不包尾)
val bufferSlice: ArrayBuffer[Int] = buffer.slice(1, 4)
// 滑动窗口操作,固定大小为 2 的滑动窗口,并且每次滑动 1 个元素。
val itAB: Iterator[ArrayBuffer[Int]] = buffer.sliding(2, 1) //
// 分组操作
val itGrouped: Iterator[ArrayBuffer[Int]] = buffer.grouped(2) // 定长分组
val map: Map[Int, ArrayBuffer[Int]] = buffer.groupBy(_ % 2) // 按键分组
val tp2: (ArrayBuffer[Int], ArrayBuffer[Int]) = buffer.partition(_ < 4) // 2 分区,按要求(小于4)分为两类
val splits: (ArrayBuffer[Int], ArrayBuffer[Int]) = buffer.splitAt(2) // 2 分区,在索引位置2处分成两部分
// grouped(size) <=> sliding(size,size),两者等价
val itPermutations: Iterator[ArrayBuffer[Int]] = buffer.permutations // 全排列
val itCombinations: Iterator[ArrayBuffer[Int]] = buffer.combinations(2) // 组合排列
二、算子
1、简单计算
-
数组元素为数值类型可以直接计算
import scala.collection.mutable.ArrayBuffer val buffer = ArrayBuffer(1, 2, 3, 4, 5) val sum = buffer.sum val max = buffer.max val min = buffer.min val pro = buffer.product // 阶乘
-
非数值元素类型
val buffer = ArrayBuffer(("aaa", 3), ("bbb", 1), ("ccc", 2), ("aaa", 2)) val sum = buffer2.map(e => e._2).sum val max: (String, Int) = buffer2.maxBy(e => e._2) val min: (String, Int) = buffer2.minBy(e => e._2) val cnt = buffer2.count(e => e._2 < 2)
-
计算优化:并行序列,并行计算
并行数组
ParArray[T]
,它是一个并行版本的数组,允许在多个线程上并行地操作数组元素。这对于对大型数据集进行处理或利用多核处理器进行并行计算非常有用,可以提高处理效率。val par: ParArray[T] = buffer.par
2、高阶计算
-
简介
- 聚合操作:
-
aggregate
方法对数组进行聚合操作,指定一个初始值o: O
,一个用于映射元素的函数map: (O, T) => O
,和一个用于合并两个结果的函数red: (O, O) => O
。这个操作会将数组元素映射为结果。在并行计算中使用这个操作,直接调用并行数组的par.aggregate
方法。 - 折叠操作:
-
fold
方法对数组进行折叠,指定一个初始值init: O
,和一个用于合并两个结果的函数func: (O, O) => O
。这个操作会从数组的一端开始,逐个将元素与初始值进行合并,直到折叠到另一端。通过指定Left
或Right
来指定折叠的方向。 - 扫描操作:
-
scan
方法对数组进行扫描,指定一个初始值init: O
,和一个用于合并两个结果的函数f: (O, O) => O
。这个操作会从数组的一端开始,逐个将元素与初始值进行合并,并在每一步生成一个过程结果。最终结果是一个包含所有过程结果的数组缓冲区。 - 归约操作:
-
reduce
方法对数组进行归约操作,指定一个用于合并两个结果的函数f: (O, O) => O
。这个操作会从数组的一端开始,逐个将元素进行合并,直到最终只剩下一个结果为止。通过指定Left
或Right
来指定归约的方向。
// 聚合操作 val rst:O = buffer.aggregate(o:O)(map:(O,T)=>O,red:(O,O)=>O) val rst:O = par.aggregate(o:O)(map:(O,T)=>O,red:(O,O)=>O) // 并行 Map & Red // 双向折叠操作 val rst:O = buffer.fold[Left|Right](init:O)(func:(O,O)=>O) // 过程结果扫描操作 val rst:ArrayBuffer[O] = buffer.scan[Left|Right](init:O)(f:(O,O)=>O) // 归约操作 val rst:O = buffer.reduce[Left|Right](f:(O,O)=>O)
-
案例
1、
aggregate
案例val buffer = ArrayBuffer(1, 2, 3, 4) val map = (a:Int,b:Int) => { println(s"MAP $a + $b = ${a+b}") a + b } val reduce = (a:Int,b:Int) => { println(s"RED $a + $b = ${a+b}") a + b } val rst: Int = buffer.par.aggregate(0)(map,reduce)
输出:
MAP 0 + 2 = 2 MAP 0 + 1 = 1 MAP 0 + 4 = 4 MAP 0 + 3 = 3 RED 1 + 2 = 3 RED 3 + 4 = 7 RED 3 + 7 = 10
2、
fold
案例val buffer = ArrayBuffer(1, 2, 3) val add = (a:Int, b:Int) => { println(s"$a + $b = ${a+b}") a + b } val rst: Int = buffer.fold(0)(add) println("-------------------------") val rst: Int = buffer.foldRight(0)(add)
输出:
0 + 1 = 1 1 + 2 = 3 3 + 3 = 6 ------------------------- 3 + 0 = 3 2 + 3 = 5 1 + 5 = 6
3、
scan
案例val buffer = ArrayBuffer(1, 2, 3, 4) val add = (a: Int, b: Int) => { println(s"$a + $b = ${a + b}") a + b } val rst1: ArrayBuffer[Int] = buffer.scan(0)(add) println(rst1) val rst2: ArrayBuffer[Int] = buffer.scanRight(0)(add) println(rst2)
输出:
0 + 1 = 1 1 + 2 = 3 3 + 3 = 6 6 + 4 = 10 ArrayBuffer(0, 1, 3, 6, 10) 4 + 0 = 4 3 + 4 = 7 2 + 7 = 9 1 + 9 = 10 ArrayBuffer(10, 9, 7, 4, 0)
4、
reduce
案例val buffer = ArrayBuffer(1, 2, 3, 4) val add = (a: Int, b: Int) => { println(s"$a + $b = ${a + b}") a + b } val rst1: Int = buffer.reduce(add) println("-------------------------") val rst2: Int = buffer.reduceRight(add)
输出:
1 + 2 = 3 3 + 3 = 6 6 + 4 = 10 ------------------------- 3 + 4 = 7 2 + 7 = 9 1 + 9 = 10
上一篇: KK 与国际音标的比较
下一篇: 国际音标和 kk 音标有什么区别?
推荐阅读
-
超越感官,沉浸赛场--大型体育赛事云端实践精选 - 第 4 章:2020 年东京奥运会:数字智能与虚拟领域--50 亿人观看 "云上奥运",*媒体背后的数字智能力量(第二部分)
-
正负偏差变量 即 d2+、d2- 分别表示决策值中超出和未达到目标值的部分。而 di+、di- 均大于 0 刚性约束和目标约束(柔性目标约束有偏差) 在多目标规划中,>=/<= 在刚性约束中保持不变。当需要将约束条件转换为柔性约束条件时,需要将 >=/<= 更改为 =(因为已经有 d2+、d2- 用来表示正负偏差),并附加上 (+dii-di+) 注意这里是 +di、-di+!之所以是 +di,-di+,是因为需要将目标还原为最接近的原始刚性约束条件 优先级因素和权重因素 对多个目标进行优先排序和优先排序 目标规划的目标函数 是所有偏差变量的加权和。值得注意的是,这个加权和都取最小值。而 di+ 和 dii- 并不一定要出现在每个不同的需求层次中。具体分析需要具体问题具体分析 下面是一个例子: 题目中说设备 B 既要求充分利用,又要求尽可能不加班,那么列出的时间计量表达式即为:min z = P3 (d3- + d3 +) 使用 + 而不是 -d3 + 的原因是:正负偏差不可能同时存在,必须有 di+di=0 (因为判定值不可能同时大于目标值和小于目标值),而前面是 min,所以只要取 + 并让 di+ 和 dii- 都为正值即可。因此,得出以下规则: 最后,给出示例和相应的解法: 问题:某企业生产 A 和 B 两种产品,需要使用 A、B、C 三种设备。下表显示了与工时和设备使用限制有关的产品利润率。问该企业应如何组织生产以实现下列目标? (1) 力争利润目标不低于 1 500 美元; (2) 考虑到市场需求,A、B 两种产品的生产比例应尽量保持在 1:2; (3)设备 A 是贵重设备,严禁超时使用; (4)设备 C 可以适当加班,但要控制;设备 B 要求充分利用,但尽量不加班。 从重要性来看,设备 B 的重要性是设备 C 的三倍。 建立相应的目标规划模型并求解。 解:设企业生产 A、B 两种产品的件数分别为 x1、x2,并建立相应的目标计划模型: 以下为顺序求解法,利用 LINGO 求解: 1 级目标: 模型。 设置。 variable/1..2/:x;! s_con_num/1...4/:g,dplus,dminus;!所需软约束数量(g=dplus=dminus 数量)及相关参数; s_con(s_con_num);! s_con(s_con_num,variable):c;!软约束系数; 结束集 数据。 g=1500 0 16 15. c=200 300 2 -1 4 0 0 5; 结束数据 min=dminus(1);!第一个目标函数;!对应于 min=z 的第一小部分;! 2*x(1)+2*x(2)<12;!硬约束 @for(s_con_num(i):@sum(variable(j):c(i,j)*x(j))+dminus(i)-dplus(i)=g(i)); !使用设置完成的数据构建软约束表达式; ! !软约束表达式 @for(variable:@gin(x)); !将变量约束为整数; ! 结束 此时,第一级目标的最优值为 0,第一级偏差为 0: 第二级目标: !求 dminus(1)=0,然后求解第二级目标。 模型。 设置。 变量/1..2/:x;!设置:变量/1..2/:x; ! s_con_num/1...4/:g,dplus,dminus;!软约束数量及相关参数; s_con(s_con_num(s_con_num));! s_con(s_con_num,variable):c;! 软约束系数; s_con(s_con_num,variable):c;! 结束集 数据。 g=1500 0 16 15; c=200 300 2 -1 4 0 0 5; 结束数据 min=dminus(2)+dplus(2);!第二个目标函数 2*x(1)+2*x(2)<12;!硬约束 @for(s_con_num(i):@sum(variable(j):c(i,j)*x(j))+dminus(i)-dplus(i)=g(i)); ! 软约束表达式;! dminus(1)=0; !第一个目标结果 @for(variable:@gin(x)); ! 结束 此时,第二个目标的最优值为 0,偏差为 0: 第三目标 !求 dminus(2)=0,然后求解第三个目标。 模型。 设置。 变量/1..2/:x;!设置:变量/1..2/:x; ! s_con_num/1...4/:g,dplus,dminus;!软约束数量及相关参数; s_con(s_con_num(s_con_num));! s_con(s_con_num,variable):c;! 软约束系数; s_con(s_con_num,variable):c;! 结束集 数据。 g=1500 0 16 15; c=200 300 2 -1 4 0 0 5; 结束数据 min=3*dminus(3)+3*dplus(3)+dminus(4);!第三个目标函数。 2*x(1)+2*x(2)<12;!硬约束 @for(s_con_num(i):@sum(variable(j):c(i,j)*x(j))+dminus(i)-dplus(i)=g(i)); ! 软约束表达式;! dminus(1)=0; !第一个目标约束条件; ! dminus(2)+dplus(2)=0; !第二个目标约束条件 @for(variable:@gin(x));! 结束 最终结果为 x1=2,x2=4,dplus(1)=100,最优利润为
-
UNIX 之父肯和丹尼斯(第二部分)--也许是因为宣传的缘故,今天人们的注意力大多集中在 "野生 "黑客身上,更多关注的是他们造成的破坏,而不是他们给技术带来的突破。如果回到 50 年前,情况就完全不同了。那时的黑客更像是为了爱好而自愿加班的模范员工,他们根本不在自己家里工作。 当然,那时的电脑还远远买不起。如果你对计算机技术感兴趣,就必须投身于学术机构或巨型企业。比如通用电气或贝尔实验室。 肯尼斯-莱恩-汤普森(Kenneth Lane Thompson)就是这些老派黑客中的一员,黑客们亲切地称他为 "肯"。他出生于 1943 年的前婴儿潮时代,22 岁从大学毕业,一年后获得硕士学位--这两个学位都来自加州大学伯克利分校的电子工程和计算科学专业。随后,他进入贝尔实验室,开始了 Multics 的研发工作。 不过,他并不总是在工作。他利用大型计算机编写了一款名为 "星际迷航 "的游戏,他和同事丹尼斯-里奇(Dennis Ritchie)在办公室里玩这款游戏。因此,当贝尔实验室在 1969 年退出 Multics 计划时,他和丹尼斯都有点失望。 不过很快,他们就找到了一台闲置的 PDP-7 机器。这台机器在当时属于低端产品,售价只有 7.2 万美元,所以贝尔实验室并没有太在意。幸运的是肯重写了《星际迷航》程序,开发了基于 Multics 的新操作系统,以便在 PDP-7 上运行游戏,甚至还为操作系统开发了一种新的编程语言 "B"。 这位 Unix 之父没有得到应有的尊重。
-
如何分析事物--第二部分
-
Scala 第二部分 算术
-
[黑客翻译] 汽车黑客,第二部分:CAN-utils 或 SocketCAN
-
技术管理实用笔记--自我意识第二部分
-
教您使用 Python 定时捕获微博评论的文章--第二部分 - 上手操作
-
MySQL:使用MySQL进行查询(第二部分)--删除
-
纯干货分享 | 研发效能提升——敏捷需求篇-而敏捷需求是提升效能的方式中不可或缺的模块之一。 云智慧的敏捷教练——Iris Xu近期在公司做了一场分享,主题为「敏捷需求挖掘和组织方法,交付更高业务价值的产品」。Iris具有丰富的团队敏捷转型实施经验,完成了企业多个团队从传统模式到敏捷转型的落地和实施,积淀了很多的经验。 这次分享主要包含以下2个部分: 第一部分是用户影响地图 第二部分是事件驱动的业务分析Event driven business analysis(以下简称EDBA) 用户影响地图,是一种从业务目标到产品需求映射的需求挖掘和组织的方法。 在软件开发过程中可能会遇到一些问题,比如大家使用不同的业务语言、技术语言,造成角色间的沟通阻碍,还会导致一些问题,比如需求误解、需求传递错误等;这会直接导致产品的功能需求和要实现的业务目标不是映射关系。 但在交付期间,研发人员必须要将这些需求实现交付,他们实则并不清楚这些功能需求产生的原因是什么、要解决客户的哪些痛点。研发人员往往只是拿到了解决方案,需要把它实现,但没有和业务侧一起去思考解决方案是否正确,能否真正的帮助客户解决问题。而用户影响地图通常是能够连接业务目标和产品功能的一种手段。 我们在每次迭代里加入的假设,也就是功能需求。首先把它先实现,再逐步去验证我们每一个小目标是否已经实现,再看下一个目标要是什么。那影响地图就是在这个过程中帮我们不断地去梳理目标和功能之间的关系。 我们在软件开发中可能存在的一些问题 针对这些问题,我们如何避免?先简单介绍做敏捷转型的常规思路: 先做团队级的敏捷,首先把产品、开发、测试人员,还有一些更后端的人员比如交互运维的同学放在一起,组成一个特训团队做交付。这个团队要包含交付过程中所涉及的所有角色。 接着业务敏捷要打通整个业务环节和研发侧的一个交付。上图中可以看到在敏捷中需求是分层管理的,第一层是业务需求,在这个层级是以用户目标和业务目标作为输入进行规划,同时需要去考虑客户的诉求。业务人员通过获取到的业务需求,进一步的和团队一起将其分解为产品需求。所以业务需求其实是我们真正去发布和运营的单元,它可以被独立发布到我们的生产环境上。我们的产品需求其实就是产品的具体功能,它是我们集成和测试的对象,也就是我们最终去部署到系统上的一个基本单元。产品需求再到了我们的开发团队,映射到迭代计划会上要把它分解为相应的技术任务,包括我们平时所说的比如一些前端的开发、后端的开发、测试都是相应的技术任务。所以业务敏捷要达到的目标是需要去持续顺畅高质量的交付业务价值。 将这几个点串起来,形成金字塔结构。最上层我们会把业务目标放在整个金字塔的塔尖。这个业务目标是通过用户的目标以及北极星指标确立的。确认业务目标后再去梳理相应的业务流程,最后生产。另外产品需求包含了操作流程和业务规则,具需求交付时间、工程时间以及我们的一些质量标准的要求。 谈到用户影响的地图,在敏捷江湖上其实有一个传说,大家都有一个说法叫做敏捷需求的“任督二脉”。用户影响地图其实就是任脉,在黑客马拉松上用过的用户故事地图其实叫督脉。所以说用户影响地图是在用户故事地图之前,先帮我们去梳理出我们要做哪些东西。当我们真正识别出我们要实现的业务活动之后,用户故事地图才去梳理我们整个的业务工作流,以及每个工作流节点下所要包含的具体功能和用户故事。所以说用户影响地图需要解决的问题,我们包括以下这些: 首先是范围蔓延,我们在整张地图上,功能和对应的业务目标是要去有一个映射的。这就避免了一些在我们比如有很多干系人参与的会议上,那大家都有不同想法些立场,会提出很多需求(正确以及错误的需求)。这个时候我们会依据目标去看这些需求是否真的是会影响我们的目标。 这里提到的错误需求,比如是利益相关的人提出的、客户认为产品应该有的、某个产品经理需求分析师认为可以有的....但是这些功能在用户影响地图中匹配不到对应目标的话,就需要降低优先级或弃掉。另外,通常我们去制定解决方案的时候,会考虑较完美的实现,导致解决方案括很多的功能。这个时候关键目标至关重要,会帮助我们梳理筛选、确定优先级。 看一下用户影响到地图概貌 总共分为一个三层的结构: 第一层why,你的业务目标哪个是最重要的,为什么?涉及到的角色有哪些? 第二层how ,怎样产生影响?影响用户角色什么样的行为? (不需要去列出所有的影响,基于业务目标) 第三层what,最关键的是在梳理需求时不需一次把所有细节想全,这通常团队中经常遇到的问题。 我们用这个例子来看一下 这是一个客服中心的影响地图,业务目标是 3个月内不增加客服人数的前提下能支持1.5倍的用户数。此业务目标设定是符合 smart 原则的,specific非常的具体,miserable 是可以衡量的,action reoriented是面向活动的, real list 也是很实际的。 量化的目标会指引我们接下来的行动,梳理一个业务目标,尽量去量化,比如 :我们通过打造一条什么样的流水线,能够提高整个部署的效率,时间是原来的 1/2 。这样才是一个能量化的有意义的目标。 回到这幅图, how 层级识别出来的内容,客服角色:想要对它施加的影响,把客户引导到论坛上,帮助客户更容易的跟踪问题,更快速的去定位问题。初级用户:方论坛上找到问题。高级用户:在论坛上回答问题。通过我们这些用户角色,进行活动,完成在不增加客户客服人数的前提下支持更多的用户数量。 最后一个层级,才是我们日常接触比较多的真正的功能的特性和需求,比如引导到客户到论坛上,其实这个产品就需要有一个常见问题的论坛的链接。这个层次需要我们团队进一步地在交付,在每个迭代之前做进一步的梳理,细化成相应的用户故事。 这个是云智慧团队中,自己做的影响地图的范例,可以看下整个的层级结构。序号表示优先级。 那我们用户影响地图可以总结为:
-