-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
第 6 题:请分别用深度优先思想和广度优先思想实现一个拷贝函数? #10
Comments
@atheist1 老哥你代码最好添加代码高亮一下,这样阅读感很差。 |
笔误: |
DFSdeepClone 方法有问题吧 字符串 clone 出来不久变数组了 |
数值赋值本身就是复制,clone的时候需要复制的是array, object和function,这个代码里并没有针对function成员的复制。 |
DFS没有对环状结构进行处理~ |
顺便说明下,这里我对es6的symbol数据以及构造函数的拷贝都没做处理,想要了解的可以看大佬的 |
|
我只深拷贝了 Object, Array,其他的非基本类型都是浅拷贝(如果处理Set什么的就太复杂了,题目用意应该是考察遍历树和重复引用吧) DFS用常规的递归问题不大,需要注意下重复引用的问题,不用递归的话就用栈 BFS就用队列,整体代码倒是差不多 // 如果是对象/数组,返回一个空的对象/数组,
// 都不是的话直接返回原对象
// 判断返回的对象和原有对象是否相同就可以知道是否需要继续深拷贝
// 处理其他的数据类型的话就在这里加判断
function getEmpty(o){
if(Object.prototype.toString.call(o) === '[object Object]'){
return {};
}
if(Object.prototype.toString.call(o) === '[object Array]'){
return [];
}
return o;
}
function deepCopyBFS(origin){
let queue = [];
let map = new Map(); // 记录出现过的对象,用于处理环
let target = getEmpty(origin);
if(target !== origin){
queue.push([origin, target]);
map.set(origin, target);
}
while(queue.length){
let [ori, tar] = queue.shift();
for(let key in ori){
// 处理环状
if(map.get(ori[key])){
tar[key] = map.get(ori[key]);
continue;
}
tar[key] = getEmpty(ori[key]);
if(tar[key] !== ori[key]){
queue.push([ori[key], tar[key]]);
map.set(ori[key], tar[key]);
}
}
}
return target;
}
function deepCopyDFS(origin){
let stack = [];
let map = new Map(); // 记录出现过的对象,用于处理环
let target = getEmpty(origin);
if(target !== origin){
stack.push([origin, target]);
map.set(origin, target);
}
while(stack.length){
let [ori, tar] = stack.pop();
for(let key in ori){
// 处理环状
if(map.get(ori[key])){
tar[key] = map.get(ori[key]);
continue;
}
tar[key] = getEmpty(ori[key]);
if(tar[key] !== ori[key]){
stack.push([ori[key], tar[key]]);
map.set(ori[key], tar[key]);
}
}
}
return target;
}
// test
[deepCopyBFS, deepCopyDFS].forEach(deepCopy=>{
console.log(deepCopy({a:1}));
console.log(deepCopy([1,2,{a:[3,4]}]))
console.log(deepCopy(function(){return 1;}))
console.log(deepCopy({
x:function(){
return "x";
},
val:3,
arr: [
1,
{test:1}
]
}))
let circle = {};
circle.child = circle;
console.log(deepCopy(circle));
}) |
var o = {m: 2} |
公司gayhub怎么老是炸,感谢提醒,已更新,在处理环状数据时我将 |
笔试时会不会纸上手写深度拷贝 |
代码有点难读 |
|
|
/**
* @Author nzq
* @Date 2019/5/28
* @Description:
* @Param:
* @Return:
*/
const type = Object.prototype.toString;
function getType(obj) {
const map = {
'[object Boolean]' : 'boolean',
'[object Number]' : 'number',
'[object String]' : 'string',
'[object Null]' : 'null',
'[object Undefined]' : 'undefined',
'[object Symbol]' : 'symbol',
'[object Object]' : 'object',
'[object Array]' : 'array',
'[object Function]' : 'function',
'[object Date]' : 'date',
'[object RegExp]' : 'regExp',
'[object HTMLDivElement]' : 'dom',
};
return map[type.call(obj)];
}
/**
* @Author nzq
* @Date 2019/5/28
* @Description: 递归
* 注意 visitedQueue.push(target); createQueue.push(res); 的时机
* @Param:
* @Return:
*/
function deepClone(obj) {
// visitedQueue 和 createQueue 的值一定要相互对应
let visitedQueue = [],
createQueue = [],
visitedIdx = 0;
return (function _clone (target) {
let res = null,
type = getType(target);
visitedIdx = visitedQueue.indexOf(target);
// 访问过
if ( visitedIdx !== -1) {
res = createQueue[visitedIdx]; // 获取对应的 createQueue 中的值
}
// 没访问过
else {
switch(type) {
case 'object': {
res = {};
visitedQueue.push(target);
createQueue.push(res);
// 遍历
for (let key in target) {
res[key] = _clone(target[key]);
}
break;
}
case 'array': {
res = [];
visitedQueue.push(target);
createQueue.push(res);
// 遍历
for (let idx in target) {
res[idx] = _clone(target[key]);
}
break;
}
case 'dom': {
res = target.cloneNode(true);
break;
}
default : {
res = target;
}
}
}
return res;
})(obj)
} |
请问能不能解释一下环状结构数据呢? |
|
@atheist1 您可以将下面这段代码 添加到函数执行之前,来复现该问题 let a = [1,2,3,4] |
谢谢指点,我会尝试下的,感谢------------------ 原始邮件 ------------------
发件人: "qinchenguang"<[email protected]>
发送时间: 2019年7月22日(星期一) 晚上10:18
收件人: "Advanced-Frontend/Daily-Interview-Question"<[email protected]>;
抄送: "ZJINH"<[email protected]>;"Comment"<[email protected]>;
主题: Re: [Advanced-Frontend/Daily-Interview-Question] 第 6 题:请分别用深度优先思想和广度优先思想实现一个拷贝函数? (#10)
@atheist1
您好。我刚刚查看您的广度优先遍历 BFSdeepClone 函数。while递归中没有判断array的环装数据。
您可以将下面这段代码 添加到函数执行之前,来复现该问题
let a = [1,2,3,4]
let b = a
a.push(b)
obj.a = a;
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.
|
@atheist1 如下代码 |
深度优先算法深拷贝有很多就不写了
|
这好像还是深度遍历 |
const test = {a: {b: {v: 1}}, c: {f: 2}, h: new Date()}; |
|
深度优先搜索的拷贝函数,没有考虑一些复杂的对象。 function deepTraversal(target) {
// 使用WeakSet防止循环引用
let weakSet = new WeakSet();
function realDeepTraversal(target) {
if (target !== null && target !== undefined) {
if (typeof target === 'object' && !weakSet.has(target)) {
weakSet.add(target);
if (Array.isArray(target)) {
return target.map(realDeepTraversal);
} else {
return Object.keys(target).reduce((result, key) => ({
...result,
[key]: realDeepTraversal(target[key])
}), {});
}
} else {
return target;
}
}
}
return realDeepTraversal(target);
} |
@atheist1 广度优先拷贝存在一个问题: 最外层是数组时,会变成对象 |
容我抖个机灵,深度优先json.parse(json.stringify(obj)) |
我自己用的这个 |
这边不应该是放入的原始值吧,应该是放入深拷贝之后的值_obj
测试用例最后一个,拷贝后的结果的循环引用不正确。 |
@atheist1 |
谁能解释一下“环状数据”是什么意思? 我理解为 循环引用 |
这种不能满足所有场景,碰到函数,正则表达式,Symbol...就歇菜了 |
// 判断正则、日期函数等
if(value instanceof RegExp){return new RegExp(value)}
if(value instanceof Date){return new Date(value)} |
看不懂大佬的广度优先遍历 |
位运算 按位非 if (~-1) // 位运算值为 0,隐式转换后为:false
if (~0) // 位运算值为 -1,隐式转换后为:true
if (~1) // 位运算值为 -2,隐式转换后为:true |
@WozHuang 你的方法好像没有考虑函数,函数拷贝过来的还是原函数的指针,应该同博主一样再做一层判断,eval赋值过来吧? |
@panzhengtao 是的,环状数据指的就是子属性又去引用了父属性,导致了循环引用。 |
/**
* 对象深拷贝(深度优先遍历)
* 着重点
* 1. 注意拷贝源可能是对象或者是数组
* 2. 可能会出现循环引用的问题,用哈希表或数组来检测并正常退出,否则会调用栈溢出
* 3. 注意对象的key可能是symbol,用for in或者Object.keys遍历不出来,可以用Object.getOwnPropertySymbols获取
* @param originObj
* @return 拷贝后的结果
*/
function deepCopy(originObj) {
return helper(originObj, new WeakSet())
}
/**
*
* @param originObj
* @param appearSet 主要是用来检测循环引用的WeakSet
* @return {*}
*/
function helper(originObj, appearSet) {
// 深度优先
if (typeof originObj !== 'object') {
// 传入非对象,直接复制返回(基本类型复制值,引用类型复制引用)
return originObj
}
if (appearSet.has(originObj)) {
// 检测到循环引用,终止
return null
}
// 添加
appearSet.add(originObj)
let obj
if (Array.isArray(originObj)) {
// 数组
obj = []
for (let i = 0; i < originObj.length; i++) {
obj[i] = helper(originObj[i], appearSet)
}
} else {
// 对象
obj = {}
// 考虑上symbol
const keyList = [...Object.getOwnPropertySymbols(originObj), ...Object.keys(originObj)]
for (let key of keyList) {
// 深度优先
obj[key] = helper(originObj[key], appearSet)
}
}
// 删除
appearSet.delete(originObj)
// 返回
return obj
} |
广度优先遍历深拷贝,只考虑了Date这特殊对象, 其余特殊对象判断方法一样 (已自测一部分数据,可能有些未覆盖到,望指出~) function bfsDeepClone(obj) {
if (obj === null) return null;
if (typeof obj !== 'object') return null;
if (obj.constructor === Date) return new Date(obj);
const newObj = new obj.constructor();
// 使用队列进行广度遍历
const queue = [[newObj, obj]];
while (queue.length > 0) {
// target: 需要拷贝的目标对象,next: 被拷贝源对象
const [target, source] = queue.shift();
// 遍历源对象的每个属性
for (let key in source) {
if (source.hasOwnProperty(key)) {
// 不是对象, 直接拷贝 Notes: 需要注意Symbol
if (typeof source[key] !== 'object') {
target[key] = source[key];
} else if (source[key].constructor === Date) {
// 如果是Date对象直接拷贝一个新的Date
target[key] = new Date(source[key]);
} else {
// 如果属性是对象, 将其加入队列中继续拷贝
target[key] = new source[key].constructor();
queue.push([target[key], source[key]]);
}
}
}
}
return newObj
} |
嗯~原谅我是杠精,以Symbol为键的对象拷贝时会忽略该键的拷贝。 |
"use strict"
// 深度优先
function deepClone(source, hash = new WeakMap()) {
if (typeof source != "object" || source == null) return source; // 非对象或空对象返回自身
if (hash.has(source)) {
return hash.get(source); // 若表中存在这个键,则返回该键对应的值,解决克隆时循环引用的问题
}
let target = Array.isArray(source) ? [] : {}; // 识别是对象还是数组
hash.set(source, target);
Reflect.ownKeys(source).forEach((key) => { // 这里用Reflect.ownKeys而不用for...in迭代是因为Reflect.ownKeys可以访问Symbol属性
/**
* 1. 如果使用for...in会把原型链上的其他属性也迭代出来,那么就要多层判断Object.prototype.hasOwnProperty.call(source, key),
* 防止得到属于原型链而不属于source自身的属性,这里使用Reflect.ownKeys,因此可以省去判断
* 2. 不直接用source.hasOwnProperty(key)是因为如果source=Object.create(null),
那么source就没有继承Object的原型,使得source的hasOwnProperty方法为undefinde
*/
if (typeof source[key] == "object" && source[key] != null) {
target[key] = deepClone(source[key], hash);
} else {
target[key] = source[key];
}
});
return target;
}
// 广度优先
function deepClone2(source) {
if (typeof source != "object" || source == null) return source; // 非对象或空对象返回自身
let target = Array.isArray(source) ? [] : {}; // 识别是对象还是数组
let hash = new WeakMap();
// 栈
let loopList = [{
target: target,
source: source
}];
hash.set(source, target); // 注意这句不要漏,下面还有一个hash.set
while (loopList.length) {
// 广度优先
let data = loopList.pop();
let node = data.source;
let target = data.target;
Reflect.ownKeys(node).some((key) => { // 这里用Reflect.ownKeys而不用for...in迭代是因为Reflect.ownKeys可以访问Symbol属性
if (typeof node[key] == "object" && node[key] != null) {
if (hash.has(node[key])) {
target[key] = hash.get(node[key]);
} else {
target[key] = Array.isArray(node[key]) ? [] : {}; // 识别是对象还是数组
loopList.push({ target: target[key], source: node[key] });
hash.set(node[key], target[key]);
}
} else {
target[key] = node[key];
}
});
}
return target;
}
// 测试用例
var a = {
name: "muyiy",
book: {
title: "You Don't Know JS",
price: "45"
},
a1: undefined,
a2: null,
a3: 123,
Symbols: Symbol("test")
}
a.circleRef = a;
let test2 = deepClone2(a);
// let test2 = deepClone(a);
console.log(a, test2);
|
function deepClone(source, hash = new WeakMap()) { |
高赞默写 function getCopy (obj) {
if (Object.prototype.toString.call(obj) == '[Object Object]') {
return {};
}
if (Object.prototype.toString.call(obj) == '[Object Array]') {
return [];
}
return obj;
}
function dfs(obj) {
let stack = [];
let ret = getCopy(obj);
if (ret !== obj) {
stack.push([obj, ret]);
}
while (stack.length) {
let [origin, target] = stack.pop();
for (let key in origin) {
if (origin.hasOwnProperty(key)) {
let tmpOrigin = origin[key];
let tmpTarget = getCopy(tmpOrigin);
target[key] = tmpTarget;
if (tmpOrigin !== tmpTarget) {
stack.push([tmpOrigin, tmpTarget])
}
}
}
}
return ret;
}
function bfs(obj) {
let array = [];
let ret = getCopy(obj);
if (ret !== obj) {
array.push([obj, ret]);
}
while(array.length) {
let [origin, target] = array.shift();
for (let key in tmpOrigin) {
if (tmpOrigin.hasOwnProperty(key)) {
let tmpOrigin = target[key];
let tmpTarget = getCopy(tmpOrigin);
target[key] = tmpTarget;
if (tmpOrigin !== tmpTarget) {
array.push([tmpOrigin, tmpTarget]);
}
}
}
}
return ret;
}
// 递归版 dfs 能循环的就能递归... 有人爱问这个
function dfs(obj) {
let ret = getCopy(obj);
if (ret !== obj) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
let tmpOrigin = obj[key];
let tmpTarget = getCopy(tmpOrigin);
if (tmpTarget !== tmpOrigin) {
ret[key] = dfs(tmpOrigin);
} else {
ret[key] = tmpTarget;
}
}
}
}
return ret;
}
|
引用类型只考虑Array,Object function DFSCopy(old) {
if (typeof old !== 'object') return old;
const isArray = Array.isArray(old);
const newObj = isArray ? [] : {};
if (isArray) {
for (let i = 0; i < old.length; i++) {
newObj[i] = deepCopy(old[i]);
}
} else {
for (let i in old) {
newObj[i] = deepCopy(old[i]);
}
}
console.log(newObj);
return newObj;
}
function BFScopy(a) {
const queue = [];
const b = new a.constructor();
queue.push([a, b]);
while (queue.length) {
const [oldObj, newObj] = queue.shift();
for (let i in oldObj) {
if (typeof oldObj[i] === 'object') {
newObj[i] = new oldObj[i].constructor();
queue.push([oldObj[i], newObj[i]]);
} else {
newObj[i] = oldObj[i];
}
}
}
return b;
} |
function dfsCopy(src, copy={}) {
Object.keys(src).forEach((key) => {
// 需要复制其他类型请额外加判断
copy[key] = typeof src[key] === 'object' && src !== src[key] && src[key] !== null ? dfsCopy(src[key]) : copy[key] = src[key]
})
return copy
}
function bfsCopy(src, copy={}, collect=[]) {
Object.keys(src).forEach((key) => {
// 需要复制其他类型请额外加判断
if (typeof src[key] === 'object' && src !== src[key] && src[key] !== null) {
collect.push({ src: src[key], copy: {} })
copy[key] = collect[collect.length - 1].copy
} else {
copy[key] = src[key]
}
})
if (collect.length !== 0) {
let { src, copy } = collect.shift()
bfsCopy(src, copy, collect)
}
return copy
} |
补一个测试用例
|
太强了, 这个对环状结构的处理才是正确的 |
//深度clone
const DFSdeepClone = (data) => {
if (data === null) return null;
let obj = {};
if (Array.isArray(data)) {
obj = [];
}
for (const key in data) {
if (Object.hasOwnProperty.call(data, key)) {
const element = data[key];
const type = typeof element;
if (type === 'number' || type === 'boolean' || type === 'string' || type === 'undefined' || type === 'symbol' || type == 'bigint') {
obj[key] = element;
} else if (type === 'function') {
obj[key] = eval(`(${element.toString()})`);
} else {
obj[key] = DFSdeepClone(element)
}
}
}
return obj;
}
//广度clone
const BFSDeepClone = (data) => {
const result = Array.isArray(data) ? [] : {};
const stack = [[data, result]];
while (stack.length) {
const [originData, targetData] = stack.pop();
for (const key in originData) {
if (Object.hasOwnProperty.call(originData, key)) {
const element = originData[key];
const type = typeof element;
if (type === 'number' || type === 'boolean' || type === 'string' || type === 'undefined' || type === 'symbol' || type == 'bigint') {
targetData[key] = element;
} else if (type === 'function') {
targetData[key] = eval(`(${element.toString()})`)
} else {
let tmpTarget = Array.isArray(element) ? [] : {};
targetData[key] = tmpTarget;
stack.push([element, tmpTarget])
}
}
}
}
return result
} |
对象深拷贝大家可能最先想到的就是DFS递归属性,递归实现相对简单代码就不贴了,这里放下BFS实现,拷贝主要需要注意下循环引用和重复引用问题,这个可以引入一个缓存数组解决。 `/**
|
circleObj.foo.bar=1; |
只考虑基本数据类型和数组和对象。其他数据类型,例如map、set等可以在此基础上拓展 function isSimpleType(e) {
return typeof e !== "object" || e === null
}
function deepCloneDfs(obj) {
if (isSimpleType(obj)) return obj;
const clone = Array.isArray(obj) ? []: {};
Object.keys(obj).forEach(key => {
clone[key] = deepCloneDfs(obj[key]);
});
return clone;
}
const createBaseObj = obj => Array.isArray(obj) ? []: {};
function deepCloneBfs(obj) {
if (isSimpleType(obj)) return obj;
const clone = createBaseObj(obj);
const queue = [[/*source*/obj ,/*target*/clone]]
while (queue.length) {
const [source, target] = queue.shift();
Object.keys(source).forEach(key => {
const value = source[key];
if (isSimpleType(value)) {
target[key] = value;
} else {
const newClone = createBaseObj(value);
target[key] = newClone;
queue.push([value, newClone]);
}
})
}
return clone;
} |
这是来自QQ邮箱的假期自动回复邮件。 您好,您的邮件我已经收到,我会尽快给您回复,谢谢。
|
话不多说直接放代码
发现了比较多的错误,但由于最近工作有点忙,一直没来得及纠正
更改(0226)
深复制 深度优先遍历
广度优先遍历
测试
ps
关于深浅拷贝的问题博主已经在他的git上有文章说过了,我就不做多的叙述
这两个方法我认为主要区别在于对于深层次以及环状数据,用深度优先遍历递归去做容易爆栈,广度优先遍历我对环状数据进行了处理,已经存在过的对象会存在数组中,下次直接赋值即可,无需继续遍历
如果出现问题欢迎讨论指出
The text was updated successfully, but these errors were encountered: