首页 > 编程语言 >去往js函数式编程(7)

去往js函数式编程(7)

时间:2023-06-19 10:11:06浏览次数:51  
标签:arr 调用 const 函数 编程 js length 去往 return

管道和组合

  管道和组合是一种技术,用于设置函数以便它们按顺序工作,使一个函数的输出称为下一个函数的输入。在 linux 中,执行一个命令并将其输出作为第二个命令的输入,而第二个命令的输出又成为第三个命令的输入,依此类推,这被称为管道。

const markers = [
  { name: 'AR', lat: -34.6, lon: -58.4 },
  { name: 'BO', lat: -16.5, lon: -68.1 },
  { name: 'BR', lat: -15.8, lon: -47.9 },
  { name: 'CL', lat: -33.4, lon: -70.7 },
  { name: 'CO', lat: 4.6, lon: -74.0 },
  { name: 'EC', lat: -0.3, lon: -78.6 },
  { name: 'PE', lat: -12.0, lon: -77.0 },
  { name: 'PY', lat: -25.2, lon: -57.5 },
  { name: 'UY', lat: -34.9, lon: -56.2 },
  { name: 'VE', lat: 10.5, lon: -66.9 }
]

  我们之前计算过的平均纬度和经度。我们的解决方案是:从每个点中提取纬度,使用函数创建一个纬度函数,将数组传递给我们的计算平均值函数。

const average = (arr) => arr.reduce(sum, 0) / arr.length
const getField = (attr) => (obj) => obj[attr]
const myMap = curry(flipTwo(demethodize(Array.prototype.map)))

const getLat = curry(getField)('lat')
const getAllLats = curry(myMap)(getLat)

let averageLat = pipeline(getAllLats, average)

  无参数风格(Pointfree style)。当你将函数连接在一起,你不需要任何中间变量来保存结果,这些结果将成为下一个函数的参数,它们是隐含的。你写出不提及参数的函数,这被称为 pointfree style.

链式调用

  当你处理对象或数组时,有另一种将多个调用链接起来的方法:使用链式调用。我们看一个常见的流畅,链式的 API 示例,然后考虑如何自己实现这种方法。

var node = svg
  .selectAll('.node')
  .data(pack(root).leaves())
  .enter()
  .append('g')
  .attr('class', 'node')
  .attr('transform', function (d) {
    return 'translate(' + d.x + ',' + d.y + ')'
  })

  每个放啊都在前一个对象上操作,并提供一个新对象的访问,后续的方法调用将应用于该对象。

递归

  递归是函数式编程的关键技术。计算机科学的一个基本事实是,无论你用递归做什么,都可以用循环来完成,反之亦然。

  什么是递归,简单的定义是函数一遍又一遍地调用自身,直到不再调用为止。递归可以解决多种问题:数学定义,斐波那契或数的阶乘。数据结构相关的算法。递归解决问题的关键是假设你已经拥有一个能够完成所需任务的函数,并正常调用它。另一方面,如果你试图在脑海中思考递归调用的工作原理并尝试跟踪思路的流程,你可能会迷失方向。所以你需要做以下几点:假设你已经有一个适合解决问题的函数。看看如何通过解决一个更小的问题来解决大问题。使用第一步中想象的函数来解决问题。确定你的基本情况是什么。确保它们足够简单,可以直接解决,而不需要进行更多的调用。

const search = (arr, key) => {
  if (arr.length == 0) {
    return false
  } else if (arr[0] == key) {
    return true
  } else {
    return search(arr.slice(1), key)
  }
}
// 我们可以稍微缩短搜索函数
const search2 = (arr, key) =>
  arr.length === 0 ? false : arr[0] === key || search2(arr.slice(1), key)

// 再进一步
// 但是并不真的建议使用这种方式编写函数,把它看作对某些开发者倾向的一种警告
// 他们试图追求最紧凑,最简短的解决方案,而不关心清晰度!
const search3 = (arr, key) =>
  arr.length && (arr[0] === key || search3(arr.slice(1), key))

  另一个经典的例子涉及高效的方式计算数字的幂。如果你想计算 2 的 13 次方,你可以通过进行 12 次乘法来实现。我们可以用几行代码来实现这个递归算法。

const powerN = (base, power) => {
  if (power === 0) {
    return 1
  } else if (power % 2) {
    // 奇数
    return base * powerN(base, power - 1)
  } else {
    return powerN(base * base, power / 2)
  }
}

  我们考虑一个经典的谜题,这个谜题涉及到一个印度的寺庙,有 3 根柱子和 64 个直径递减的金盘。僧侣们必须按照两个规则将盘子从第一根柱子移动到最后一根柱子;一次只能移动一个盘子,较大的盘子永远不能放在较小的盘子上面。根据传说,当这 64 个盘子移动完成时,世界将终结。这个谜题通常被称为汉诺塔。

function hanoi(n, source, target, auxiliary) {
  if (n > 0) {
    // 将 n-1 个盘子从源柱移动到辅助柱
    hanoi(n - 1, source, auxiliary, target)

    // 将第 n 个盘子从源柱移动到目标柱
    console.log(`Move disk ${n} from ${source} to ${target}`)

    // 将 n-1 个盘子从辅助柱移动到目标柱
    hanoi(n - 1, auxiliary, target, source)
  }
}

// 示例调用
hanoi(3, 'A', 'C', 'B')

  使用分而治之策略还可以解决其他类型的问题。例如,合并排序是一种经典的分而治之算法。它将一个大的排序问题分解为两个较小的排序问题,然后将这些子问题的解合并起来以获得最终的有序结果。

const mergeSort = (arr) => {
  if (arr.length <= 1) {
    return arr
  }

  const mid = Math.floor(arr.length / 2)
  const left = arr.slice(0, mid)
  const right = arr.slice(mid)

  const sortedLeft = mergeSort(left)
  const sortedRight = mergeSort(right)

  return merge(sortedLeft, sortedRight)
}

const merge = (arr1, arr2) => {
  let result = []
  let i = 0
  let j = 0
  while (i < arr1.length && j < arr2.length) {
    if (arr1[i] < arr2[j]) {
      result.push(arr1[i])
      i++
    } else {
      result.push(arr2[j])
      j++
    }
  }

  while (i < arr1.length) {
    result.push(arr1[i])
    i++
  }

  while (j < arr2.length) {
    result.push(arr2[j])
    j++
  }

  return result
}

  第三种通用策略,动态规划,假设你需要解决许多较小的问题,但不是每次都使用递归,而是依赖于你之前存储的已找到的解决方案..也就是记忆化。给一定数量的美元和现有票面值的列表,计算我们可以用不同的票面组合多少种不同的方式支付这笔金额的美元。假设你可以无限次使用每张钞票。

const makeChange = (n, bills) => {
  if (n < 0) {
    return 0
  } else if (n == 0) {
    return 1
  } else if (bills.length == 0) {
    // 在此情况下,n>0
    return 0
  } else {
    return makeChange(n, bills.slice(1)) + makeChange(n - bills[0], bills)
  }
}

console.log(makeChange(64, [100, 50, 20, 10, 5, 2, 1]))

Mapping 和 filtering

  在很大程度上,map 和 filter 相似,因为两者都意味着遍历数组中的所有元素,并对每个元素应用回调函数以生成输出。我们自己写一个 map 版本:

const mapR = (arr, cb) =>
  arr.length === 0 ? [] : [cb(arr[0])].concat(mapR(slice(1), cb))

const mapR2 = (arr, cb, i = 0, orig = arr) =>
  arr.length == 0
    ? []
    : [cb(arr[0], i, orig)].concat(mapR2(arr.slice(1), cb, i + 1, orig))

  当你使用递归而不是迭代时,你无法访问索引,因此,如果你需要它,你就不得不自己生成它。然而,函数中有额外的参数并不好;开发人员可能会不小心提供它们,那么结果将是不可预测的。

const mapR3 = (org, cb) => {
  const mapLoop = (arr, i) =>
    arr.length == 0
      ? []
      : [cb[(arr[0], i, orig)]].concat(mapR3(arr.slice(1), vb, i + 1, orig))
  return mapLoop(orig, 0)
}

  延续传递方式(Continuation Passing Style,CPS)中,我们可以通过使用一个延续来将递归调用转换为尾调用。在函数时编程中,延续是表示进程状态并允许处理继续的东西。我们从代码看起。

function getTime() {
  return new Date().toTimeString()
}

console.log(getTime())

function getTime2(cont) {
  return cont(new Date().toTimeString())
}

getTime2(console.log)

  有什么区别?关键在于我们可以应用这种机制,将递归调用转换为尾调用,因为之后的所有代码都会在递归调用本身中提供。为了清楚起见,让我们重新审视一下阶乘函数的一个版本,该版本明确表示我们没有使用尾调用。

function fact2(n) {
  if (n === 0) {
    return 1
  } else {
    const aux = fact2(n - 1)
    return n * aux
  }
}

function factC(n, cont) {
  if (n == 0) {
    return cont(1)
  } else {
    return factC(n - 1, (x) => cont(n * x))
  }
}

标签:arr,调用,const,函数,编程,js,length,去往,return
From: https://www.cnblogs.com/wlxll/p/17490416.html

相关文章

  • JSP 最佳实践: 使用JSTL来更新JSP页面
    developerWorks中国  >  Javatechnology | Webdevelopment  >JSP最佳实践:使用JSTL来更新JSP页面http://www.ibm.com/developerworks/cn/java/j-jsp05273/index.html标准化JSTL标记为您的Web页面带来更多的功能级别:初级BrettMcLaughlin([email protected]),作者,......
  • 使用nwjs打包VUE生成桌面应用
    摘抄自:https://blog.csdn.net/weixin_40521770/article/details/126907614目前已知把Vue项目打包成桌面应用有两种方式:(1)使用nwjs生成桌面应用;(2)使用Electron生成桌面应用。本文采用的是nwjs生成桌面应用,也是我认为最简单、最快捷的一种。一、打包Vue应用程序npmrunbuild二、添......
  • js数组常用的方法
    在JavaScript中,数组是一种非常重要的数据类型。数组提供了一系列常用的方法,可以方便地对数组进行操作和处理。本文将介绍JavaScript中几种常用的数组方法的含义、返回值以及是否改变原数组。一、push()push()方法可以将一个或多个元素添加到数组的末尾,并返回数组的新长度。例如:......
  • js数据类型
    *字符串String:‘’,“”,“123”,‘都是字符串’leta=1//是赋值号,是动态的,把右边的赋值给左边*数字Number:所有数字,整数,小数都是数字。varb=2.3*布尔Boolean:true,falsevarc=true*空值Null:表示空或者没有。vard="字符串"*未定义:undefined。表示声明了,但......
  • 关于前后端JSON解析差异问题与思考
    本文主要总结了作者在一次涉及流程表单的需求发布中遇到的问题及思考总结。 一、问题回顾在一次涉及流程表单的需求发布时,由于表单设计的改动,需要在历史工单中的一个json字段增加一个属性,效果示意如下:[{"key1":"value1"}]->[{"key1":"value1","key2":"value2"}]......
  • 关于前后端JSON解析差异问题与思考
    本文主要总结了作者在一次涉及流程表单的需求发布中遇到的问题及思考总结。 一、问题回顾在一次涉及流程表单的需求发布时,由于表单设计的改动,需要在历史工单中的一个json字段增加一个属性,效果示意如下:[{"key1":"value1"}]->[{"key1":"value1","key2":"value2"}]......
  • Linux网络编程
    查看端口占用情况netstat-tunlp-t(tcp)仅显示tcp相关选项-u(udp)仅显示udp相关选项-n拒绝显示列名,能显示数字的全部转化为数字-l仅显示出在listen(监听)的服务状态-p显示潜力相关链接的程序名linux查看端口被哪个进程占用的方法本机地址127.0.0.1:这个地......
  • JS(简单数据类型、数据类型转换)
    一.数据类型简介1.1为什么需要数据类型在计算机中,不同的数据所需占用的存储空间是不同的,为了便于把数据分成所需内存大小不同的数据,充分利用存储空间,于是定义了不同的数据类型。简单来说,数据类型就是数据的类别型号。比如姓名“张三”,年龄18,这些数据的类型是不一样的。1.2变......
  • Vue.js 插槽
    学习目录:Vue.js简介Vue.js实例与数据绑定Vue.js计算属性和侦听器Vue.js条件渲染和列表渲染Vue.js事件处理Vue.js表单输入绑定Vue.js组件基础Vue.js组件通信Vue.js插槽Vue.js动态组件和异步组件Vue.js自定义指令Vue.js过渡和动画Vue.js混入Vue.js自定义事件和v-model......
  • 关于Linux系统下Lua编程运行环境的部署安装
    这里以操作系统:RedHatEnterpriseLinuxrelease8.7(Ootpa)为例,讲解如下部署Lua编程脚本的运行环境首先对于Lua脚本,需要保证系统中有lua二进制程序文件,即/usr/bin/lua但最小化安装的Linux- RedHatEnterpriseLinuxrelease8.7(Ootpa) 笔者试了一下,没有lua命令了 ......