【重学】数组扁平化,乱序

目录:
数组扁平化
数组乱序
reduce()的妙用
git复习:rebase,cherry-pick

(1)数组扁平化

1. flat()
- Array.prototype.flat()
- 参数是:要拉平的层数
- 参数是 Infinity 时表示不管嵌套多少层,都转成一元数组
- flat() 不会改变原数组,该方法会返回一个新数组
- 如果原数组有空位,flat()方法会跳过空位
- 代码:
const arr = [1, [2, 3, [4, 5, [6,7]]]] // [1,2,3,4,5,6,7]
const res = arr.flat(Infinity) // 参数表示展开的层数,如果是Infinity表示不管多少层都转成一元数组
console.log(res)


2. flatMap()
- flatMap()表示相对数组执行 map() 方法,在执行 flat()方法
- 参数:第一个参数是一个遍历函数,函数的参数一次是 value index array
- flatMap()方法还可以有第二个参数,用来绑定遍历函数里面的this
- flatMap()也不会改变原数组,会返回一个新的数组
- 注意:flatMap()默认之展开一层
- 代码:
const arr = [1, 2, 3]
const res = arr.flatMap(i => [i, i*2]) // [1, 2, 2, 4, 3, 6]
// 相当于:[[1, 2], [2, 4], [3, 6]]
console.log(res)
2. 递归
recursion:递归

const arr = [1, [2, 3, [4, [5]]]]
function flat(arr) {
    let result = []
    for(let i = 0, len = arr.length; i < len; i++) {
        if(Array.isArray(arr[i])) { // 如果还是数组,递归
            result = result.concat(flat(arr[i]))
        } 
        else {
            result.push(arr[i])
        }
    }
    return result
}
const res = flat(arr)
console.log(res)
3. toString
- 如果数组元素都是数值(注意所有的数组中的所有元素都要是数字),可以使用toString()
- 然而这种方法使用的场景却非常有限,如果数组是 [1, '1', 2, '2'] 的话,这种方法就会产生错误的结果。


const arr = [1, [2, 3, [4, [5]]]]
function flat(arr) {
    return arr.toString().split(',').map(item => +item)
    //  arr.toString()数组的字符串形式,会展平。      =>  1,2,3,4,5
    // arr.toString().split(',')以,为分隔符,将字符串转成数组  ["1", "2", "3", "4", "5"]
    // +item相当于Number(item)
}
const res = flat(arr)
console.log(res)
4. 使用reduce()

var arr = [1, [2, [3, 4]]];
function flatten(arr) {
    return arr.reduce(function(prev, next){
        return prev.concat(Array.isArray(next) ? flatten(next) : next)
    }, [])
  // 这里指定了初始值是 []
  // prev = []
  // next = 1,因为prev初始值是空数组,所以next的初始值是 1

  // reduce((accumulate, currentValue, index, arr) => {....}, [])
  // 第一个参数:是一个函数
    // 第一个参数:累积变量,默认是数组的第一个元素
    // 第二个参数:当前变量,默认是数组的第二个元素
    // 第三个参数:当前位置(当前变量在数组中的位置)
    // 第四个参数:原数组
  // 第二个参数:累积变量的初始值,注意如果指定了初始值,那么当前变量就从数组的第一个元素开始
}

console.log(flatten(arr))
4. 使用 ...展开运算符

let arr = [1, [2, [3, 4]]];
function flat(arr) {
  while (arr.some(item => Array.isArray(item))) { // 循环判断是不是数组,是数组就展开
    arr = [].concat(...arr); // 每次都扁平一层
  }
  return arr;
}
console.log(flat(arr))

https://github.com/mqyqingfeng/Blog/issues/36

(2)数组乱序

  • shuffle:洗牌
前置知识:
1. sort()
- sort()默认是按字典就行排序,数字会先被成字符串,改变原数组,没有返回值
- 如果想按照自定义的方式排序,参数可以是一个函数
- 参数函数有两个参数(两个比较的数组成员)
    - 返回值大于0,表示第一个成员排在第二个成员后面 (a-b>0,则a排在b后面)
    - 返回值小于0,表示第一个成员排在第二个成员前面
    - 返回值等于0,位置不变
- arr.sort((a, b) => a - b ) 升序
- arr.sort((a, b) => b - a ) 降序


2. js中不加分号的注意事项:
- 小括号开头的前一条语句(注意前面要是语句,表达式不受影响)
- 中括号开头的前一条语句
解决方法:在小括号或者中括号前面加上分号


哪些地方会用到乱序:
1. 换一批
2. 猜你喜欢
3. 中奖方案

方法1:
arr.sort(() => Math.random() - .5)


function a() {
  const arr = [1, 2, 3, 4, 5]
  arr.sort(() => Math.random() - .5)  // 因为Math.random的值区间是[0, 1) 所以 这里大于0和小于0的概率都是50%
  // sort自定义排序时接收一个函数作为参数
  // 函数返回值大于0,表示两个比较的成员,第一个排在第二个的后面
  // 函数返回值小于0,表示两个比较的成员,第一个排在第二个的前面
  // 函数返回值等于0,表示两个比较的成员,位置不变
  // 当小数是0点几时,可以省略前面的0
  console.log(arr)
}


-----------
存在问题:
1. 概率不均等
2. 造成概率不均等的原因是,sort底层实现是用了插入排序
3. 具体就是当无序部分插入到有序部分的元素,一旦找到位置,剩下的元素就没有在和缓存的值就行比较了
4. 没有机会比较所有元素,所以得不到完全随机的结果
如下:
const times = [0, 0, 0, 0, 0, 0]
for(i = 0; i < 100000; i++) {
    const arr = [1, 2, 3, 4, 5, 6]
    arr.sort(() => Math.random() - 0.5)
    times[arr[5] - 1] ++  
    // times[arr[5] - 1] ++ 表示arr数组最后一个位置上,各数字出现的次数
    // 当arr[5]是6时,times[5]位置的值+1,就是arr[5]是6的次数+1
}
console.log(times)
// [19480, 5174, 15619, 14188, 18880, 26659] 概率分配不均,arr[5]是2的概率明显小了很多



-----------
sort排序源码:
- 各个浏览器的sort()方法的实现方法不同
- v8中
  - 当数组长度小于10时,用的是插入排序
  - 否则,使用的是插入排序和快速排序的混合排序



-----------
插入排序
- 分类:
  - 直接插入排序:顺序发定位插入位置
  - 二分插入排序:二分法定位插入位置
  -   希尔排序  :缩小增量,多遍插入排序



-----------
直接插入排序:
- 插入排序的思想:从无序数组中取出一个值,插入到有序数组中,插入后有序数组仍然有序(打牌)
- 原理:
  1. 将数组分成两部分,左边是有序数组(最初只有一个元素),右边是无序数组(所以循环是从1开始,因为有序部分初始有一个元素)
  2. 从右边的无序部分依次取出一个值,插入到有序部分,直到取完无序部分
  3. 如果找有序部分的插入位置?:
    1. 先缓存需要插入的无序部分的值,用一个变量来缓存 let temp = arr[i]
    2. 从有序部分的最后位置找(arr[i - 1]),如果arr[i - 1] > arr[i] 则该元素往后移动一位
    3. 如果有序该位置仍然比需要插入的值大,有序中该位置的值,也后移动一位,直到 j>=0
- 代码:
// 直接插入排序
const arr = [1, 4, 3, 2]
const insert_sort = (arr) => {
    for(let i = 1, len = arr.length; i < len; i++) { // 循环数组的无序部分,从1开始,因为假设初始化时有序部分有一个元素
        let temp = arr[i] // 缓存需要插入有序部分的这个无序部分的值,因为有序部分可能会往后移动位置,将其覆盖
        let j = i - 1 // 有序部分的最后一个元素的位置,有序部分从最后的位置依次往前查找需要插入的位置
        while(j >= 0 && arr[j] > temp) { // 有序部分循环条件
            arr[j+1] = arr[j] // 有序该位置值大于temp,则往后移动一位
            j-- // 依次往前查找
        }
        arr[j+1] = temp // 循环完后,j+1就是需要插入的位置,因为条件是大于temp,不满足时,j+1就是要插入的位置
    }
    return arr // 最后返回数组
}
console.log(insert_sort(arr))

乱序排序改进

Fisher–Yates

1. 使用sort(() =>  Math.random() - .5)存在的问题就是不能完全实现全部元素的比较,造成概率不均等
2. 为什么叫 Fisher–Yates 呢?
  - 因为这个算法是由 Ronald Fisher 和 Frank Yates 首次提出的。
3.shuffle:是洗牌的意思

4.Fisher–Yates的原理:
- 首先选取(数组最后一个位置)的元素和(数组长度随机位置)上的元素交换
- 接着选取(数组倒数第二个位置)的元素和(除去最后一个位置剩下的数组位置)上的元素交换
- 重复以上步骤,直到length >= 1

5.代码
// 数组乱序
const arr = [1, 2, 3, 4, 5, 6]
function shuffle(arr) {
    let length = arr.length
    while(length > 1) {
        const pivot = Math.floor(Math.random() * length--) 
        // 1. 注意这里是先赋值,再减 1,即Math.floor(Math.random() * 6) 
        // (Math.random() * 6的区间[0, 6)
        // Math.floor(Math.random() * 6) 区间是 [0-5]

        // 2. 语句以分号结尾,一个分号就表示一个语句结束。所以这里赋值语句后面记得加分号
        // 3. ;[arr[pivot], arr[length]] = [arr[length], arr[pivot]]这里条语句前加上分号,因为前面也是语句
        // 在小括号开头,或者中括号开头的语句,前面的语句末尾需要加分号,或者加到本条语句前面
        //4. const pivot = Math.floor(Math.random() * length--); 这样也行
        ;[arr[pivot], arr[length]] = [arr[length], arr[pivot]] // 这里的length是减1之后的length
        // const temp = arr[length]
        // arr[length] = arr[pivot]
        // arr[pivot] = temp
    }
    return arr
}
console.log(shuffle(arr))

https://juejin.im/post/5d004ad95188257c6b518056#heading-2

(3)reduce()使用技巧

reduce()

(1) 概念
- reduce()默认按照字典排序
- reduce(function(reduce, currentValue, currentIndex, arr), initial)
- 第一个参数是函数:
  - 函数第一个参数:累计变量,默认是数组的第一项
  - 函数第二个参数:当前变量,默认是数组的第二项(如给定reduce第二个参数(累积变量初始值),则当前变量从第一项开始)
  - 函数第三个参数:当前位置,(第二个参数的下标)
  - 函数第四个参数:原数组
- 第二个参数是(累计变量初始值),如果给定累计变量初始值,当前变量就从数组第一项开始

(2) 用法
1. 累加

2. 将数组转化成对象
const arr = [{id: 1, name: 'wang'}, {id: 2, name: 'zhang'}]
const res = arr.reduce((pre, cur, i, arr) => {
  return {...pre, [cur.id]: cur}
}, {})
console.log(res)


(3) 实现数组扁平化
const arr = [1, [2, [3, [4, 5]], 6], 7]
function flat(arr) {
  return arr.reduce((prev, current, index, arr) => {
    return prev.concat(Array.isArray(current) ? flat(current) : current)
  }, [])
}
const res = flat(arr)
console.log(res)

https://juejin.im/post/5cbc23def265da037b610ec6#heading-18

https://juejin.im/post/5d073414f265da1b6029037f

(4)git

(一)初次运行git前的配置

(1) 用户信息
- 第一步就是设置(用户名)和(邮件地址)
- 如果使用了--global,该命令只需运行一次,之后无论在什么系统上做任何事情,git都会使用那些信息
- 当你想针对特定项目使用不同的用户名称与邮件地址时,可以在那个项目目录下运行没有 --global 选项的命令来配置。
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com


(2) 文本编辑器
- 当用户信息配置完后,可以配置文本编辑器,用户使用git输入信息时,会调用文本编辑器
- 默认调用的是 (vim),如果想自定义可以输入以下命令:
$ git config --global core.editor emacs  // 使用Emacs编辑器


(3) 检查配置信息
- 如果想要检查你的配置,可以使用 git config --list 命令来列出所有 Git 当时能找到的配置
$ git config --list
(1) git log
- git log:显示从最近到最远的提交日志
- git log --pretty=oneline --graph
- // --pretty=oneline 将commit 信息简化成一行显示
- // --graph命令可以看到分支合并图。 -------------( graph是图表的意思 )


(2) git reflog
- gitt reflog:记录几乎所有本地仓库的改变
- 可以用来重返未来(git reset --hard comit_id之后后悔)


(3) git reset
- git reset --hard commit_id
- git reset --hard HEAD^ // 回退到上一个commit
- 当前版本   ( HEAD )
- 上一个版本 ( HEAD^ )或者  ( HEAD~1 )
- 上上个版本 ( HEAD^^ ) 或者 ( HEAD~2 )
(4) git rebase
- 不同分支:变基
- 同一分支:合并多个提交
git rebase变基
- 不同分支的处理是变基
- git rebase master  // 把当前分支变基到master分支
git rebase进入交互模式 
- 同一分支是进入交互模式

- git rebase -i  [startpoint]  [endpoint] 
- git rebase -i HEAD~3  表示将最新的三个提交进入交互式界面
// [startpoint]  [endpoint]这个是一个 (前开后闭区间)
// 如果不指定[endpoint],则该区间的终点默认是当前分支HEAD所指向的commit(
// i:是 (interactive)交互的意思

pick:保留该commit(缩写:p)
reword:保留该commit,但我需要修改该commit的注释(缩写:r)
edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e)
squash:将该commit和前一个commit合并(缩写:s)
fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f)
exec:执行shell命令(缩写:x)
drop:我要丢弃该commit(缩写:d)

- squash:是粉碎的意思
- reword:改写,修改措辞的意思

https://www.jianshu.com/p/4a8f4af4e803

https://git-scm.com/book/zh/v2/%E8%B5%B7%E6%AD%A5-%E5%88%9D%E6%AC%A1%E8%BF%90%E8%A1%8C-Git-%E5%89%8D%E7%9A%84%E9%85%8D%E7%BD%AE

(5) git cherry-pick 摘樱桃
- git cherry-pick:把其他分支的commit,移到当前分支
- git cherry-pick <commit id>

- 如果有冲突,首先需要解决冲突,解决冲突后需要git commit手动进行提交
- git add .后直接使用git cherry-pick --continue继续 // 继续后,可以修改提交的commit的命名

https://www.cnblogs.com/ludashi/p/8213550.html

推荐阅读更多精彩内容