在map中查找可能的路径:javascript中的递归函数

时间:2017-04-01 07:09:35

标签: javascript recursion



doc = {
  'a': {
    'b': {
      'c': 'hello'
    },
    'd': {
      'c': 'sup',
      'e': {
        'f': 'blah blah blah'
      }
    }
  }
}

function get(json, path) {
  var str = path.split('.');
  var temp = json;
  var arr = [];
  var keystr = "";
  for (var i = 0; i < str.length; i++) {
    if (str[i] != "*") {

      keystr += str[i] + ".";

      if (temp[str[i]] === undefined)
        break;
      else {
        temp = temp[str[i]];
        if (i == str.length - 1) {
          var nObj = {};
          nObjKey = keystr.substr(0, keystr.length - 1);
          nObj[nObjKey] = temp
            // console.log("Obj check" + JSON.stringify(nObj) + keystr)
          arr.push(nObj);
        }
      }
    } else {
      for (var key in temp) {
        var concat = key + "."
        for (var j = i + 1; j < str.length; j++)
          concat += str[j] + ".";
        if (temp[key] !== undefined && temp[key] instanceof Object) {

          var m = keystr + concat.substr(0, concat.length - 1);
          var obj = (get(temp, concat.substr(0, concat.length - 1)));

          if (obj != "") {
            // console.log("existing arr "+JSON.stringify(arr))
            obj[m] = (obj[0])[concat.substr(0, concat.length - 1)]
              //  console.log("hello "+JSON.stringify(obj) + " end hello")
            arr.push(obj);
          }
        } else if (temp[key] !== undefined && i == str.length - 1) {
          // arr.push(temp);
        }
      }
    }
  }
  return arr;
}

var result = (get(doc, 'a.*.e'))
console.log(result)
&#13;
&#13;
&#13;

对于'a.*.e'的输入,输出应为{'a.d.e': {'f': 'blah blah blah'}}}。但是我在阵列中也获得了外卡的全部替代品。我确信有些事情是错误的,但无法检测出来。帮助将不胜感激。

3 个答案:

答案 0 :(得分:3)

您可以使用递归方法更改操作的结构,并通过退出早期退出范例,并使用退出选项检查单个部分,例如

  • 长度,找到部分结果,
  • falsy或不是对象类型,
  • 索引处的部分是星号,然后迭代对象中的所有键,或
  • 索引处的部分是一个键,然后再次调用该函数。

最后,使用找到的路径,连接路径并使用对象的实际值生成新属性。

function get(object, path) {

    function iter(o, p, i) {
        if (i === parts.length) {
            result[p.join('.')] = o;
            return;
        }
        if (!o || typeof o !== 'object') {
            return;
        }
        if (parts[i] === '*') {
            Object.keys(o).forEach(function (k) {
                iter(o[k], p.concat(k), i + 1);
            });
            return;
        }
        if (parts[i] in o) {
            iter(o[parts[i]], p.concat(parts[i]), i + 1);
        }
    }

    var result = {},
        parts = path.split('.');

    iter(object, [], 0);
    return result;
}

var doc = { a: { b: { c: 'hello' }, d: { c: 'sup', e: { f: 'blah blah blah' } } } };

console.log(get(doc, 'a.*.e'));
console.log(get(doc, 'a.*.c'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

版本*作为任何级别的通配符。

function get(object, path) {

    function iter(o, p, i) {
        if (i === parts.length) {
            result[p.join('.')] = o;
            return;
        }
        if (!o || typeof o !== 'object') {
            return;
        }
        if (parts[i] === '*') {
            Object.keys(o).forEach(function (k) {
                iter(o[k], p.concat(k), i);
                iter(o[k], p.concat(k), i + 1);
            });
            return;
        }
        if (parts[i] in o) {
            iter(o[parts[i]], p.concat(parts[i]), i + 1);
        }
    }

    var result = {},
        parts = path.split('.');

    iter(object, [], 0);
    return result;
}

var doc = { a: { b: { c: 'hello' }, d: { c: 'sup', e: { f: 'blah blah blah' } } } };

console.log(get(doc, 'a.*.e'));
console.log(get(doc, 'a.*.c'));
console.log(get(doc, 'a.*.f'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

答案 1 :(得分:1)

首先,由于您所需的输出{'a.d.e': {'f': 'blah blah blah'}}}不包含任何数组,而只包含普通对象,因此您的代码中不需要变量arr

相反,返回nObj作为函数结果,并在开始时声明它,从不清除它。

其次,当您从递归调用中返回时,需要复制结果,同时为路径添加前缀。请注意,不应使用!= ""检查空数组,但无论如何,您不再需要它。

你可以用不同的方式从头开始编写这个(请参阅答案结尾处的解决方案),但我首先调整了你的代码,只改变了最低限度,并在我做出改动的评论中使其工作:

function get(json, path) {
  var str = path.split('.');
  var temp = json;
  var arr = [];
  var keystr = "";
  // *** Define here the object to return
  var nObj = {};
          
  for (var i = 0; i < str.length; i++) {
    if (str[i] != "*") {
      keystr += str[i] + ".";
      if (temp[str[i]] === undefined)
        break;
      else {
        temp = temp[str[i]];
        if (i == str.length - 1) {
          // *** Move this to start of the function
          //var nObj = {}; 
          nObjKey = keystr.substr(0, keystr.length - 1);
          nObj[nObjKey] = temp
        }
      }
    } else {
      for (var key in temp) {
        var concat = key + "."
        for (var j = i + 1; j < str.length; j++)
          concat += str[j] + ".";
        if (temp[key] !== undefined && temp[key] instanceof Object) {

          var m = keystr + concat.substr(0, concat.length - 1);
          var obj = get(temp, concat.substr(0, concat.length - 1));
          // *** Return value is object with path(s) as keys
          // *** Don't compare array with string
          //if (arr != "") { 
            // *** Iterate over the returned object properties, and prefix them 
            for (var deepKey in obj) {
                nObj[keystr + deepKey] = obj[deepKey];
            }
            //*** No need for array; we already have the object properties
            //arr.push(obj);
          //}
        // *** No need for array
        //} else if (temp[key] !== undefined && i == str.length - 1) {
          // arr.push(temp);
        }
      }
    }
  }
  // *** Return object 
  return nObj;
}

var doc = {
  'a': {
    'b': {
      'c': 'hello'
    },
    'd': {
      'c': 'sup',
      'e': {
        'f': 'blah blah blah'
      },
    },
    'g': {
      'e': {
        'also': 1
      }
    }
  }
}

var result = (get(doc, 'a.*.e'));
console.log(result);

如果不是名字对象json,也请考虑它们:JSON是一种文本格式,JavaScript对象变量与JSON不同。

紧凑型ES6解决方案

当您习惯于reduce函数式编程样式等数组函数时,以下紧凑型ES6解决方案可能对您有吸引力:

function get(obj, path) {
    if (typeof path === 'string') path = path.split('.');
    return !path.length ? { '': obj } // Match
        : obj !== Object(obj) ? {} // No match
        : (path[0] === '*' ? Object.keys(obj) : [path[0]]) // Candidates
            .reduce( (acc, key) => {
                const match = get(obj[key], path.slice(1)); // Recurse
                return Object.assign(acc, ...Object.keys(match).map( dotKey => 
                    ({ [key + (dotKey ? '.'  + dotKey : '')]: match[dotKey] })
                ));
            }, {});
}

const doc = {
  'a': {
    'b': {
      'c': 'hello'
    },
    'd': {
      'c': 'sup',
      'e': {
        'f': 'blah blah blah'
      },
    },
    'g': {
      'e': {
        'also': 1
      }
    }
  }
};

const result = get(doc, 'a.*.e');
console.log(result);

答案 2 :(得分:1)

列出monad

这是一个解决方案,借用List monad中的想法来表示可能具有0,1或更多结果的计算。我不打算详细介绍它,我只提供了足够的List类型来获得有效的解决方案。如果您对这种方法感兴趣,可以对该主题进行更多研究,或者向我提出后续问题。

我还使用辅助find函数,它是get的递归帮助器,它操作get准备的键数组

如果您喜欢这个解决方案,我已经在some other answers中写过了monad列表;你可能会发现它们有用^ _ ^

const List = xs =>
  ({
    value:
      xs,
    bind: f =>
      List (xs.reduce ((acc, x) =>
        acc.concat (f (x) .value), []))
  })

const find = (path, [key, ...keys], data) =>
  {
    if (key === undefined)
      return List([{ [path.join('.')]: data }])
    else if (key === '*')
      return List (Object.keys (data)) .bind (k =>
        find ([...path, k], keys, data[k]))
    else if (data[key] === undefined)
      return List ([])
    else
      return find ([...path, key], keys, data[key])
  }

const get = (path, doc) =>
  find ([], path.split ('.'), doc) .value

const doc =
  {a: {b: {c: 'hello'},d: {c: 'sup',e: {f: 'blah blah blah'}}}}
  
console.log (get ('a.b.c', doc))
// [ { 'a.b.c': 'hello' } ]
   
console.log (get ('a.*.c', doc))
// [ { 'a.b.c': 'hello' }, { 'a.d.c': 'sup' } ]

console.log (get ('a.*', doc))
// [ { 'a.b': { c: 'hello' } },
//   { 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]

console.log (get ('*.b', doc))
// [ { 'a.b': { c: 'hello' } } ]

仅限原生数组

为了获得相同的结果,我们不必进行花哨的List抽象。在这个版本的代码中,我将向您展示如何使用除本机数组之外的任何内容。这段代码的唯一缺点是'*' - 通过将flatmap代码嵌入到我们的函数中来使键分支变得有点复杂

const find = (path, [key, ...keys], data) =>
  {
    if (key === undefined)
      return [{ [path.join ('.')]: data }]
    else if (key === '*')
      return Object.keys (data) .reduce ((acc, k) =>
        acc.concat (find ([...path, k], keys, data[k])), [])
    else if (data[key] === undefined)
      return []
    else
      return find ([...path, key], keys, data[key])
  }

const get = (path, doc) =>
  find([], path.split('.'), doc)

const doc =
  {a: {b: {c: 'hello'},d: {c: 'sup',e: {f: 'blah blah blah'}}}}

console.log (get ('a.b.c', doc))
// [ { 'a.b.c': 'hello' } ]

console.log (get ('a.*.c', doc))
// [ { 'a.b.c': 'hello' }, { 'a.d.c': 'sup' } ]

console.log (get ('a.*', doc))
// [ { 'a.b': { c: 'hello' } },
//   { 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]

console.log (get ('*.b', doc))
// [ { 'a.b': { c: 'hello' } } ]

为什么我推荐使用List monad

我个人推荐List monad方法,因为它保持find函数的主体最干净。它还包含模糊计算的概念,并允许您重用,无论您需要这样的行为。如果不使用List monad,每次都会重写必要的代码,这会增加对代码理解的认知负担。

调整结果的形状

你的函数的返回类型非常奇怪。我们返回一个只有一个键/值对的Array对象。关键是我们找到数据的路径,值是匹配的数据。

通常,我们不应该以这种方式使用Object键。我们如何显示比赛结果?

// get ('a.*', doc) returns
let result =
  [ { 'a.b': { c: 'hello' } },
    { 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]

result.forEach (match =>
  Object.keys (match) .forEach (path =>
    console.log ('path:', path, 'value:', match[path])))
    
// path: a.b value: { c: 'hello' }
// path: a.d value: { c: 'sup', e: { f: 'blah blah blah' } }

如果我们返回[<key>, <value>]而不是{<key>: <value>}怎么办?使用这种形状的结果更舒服。支持此问题的其他原因是,您的数据的更好形状类似于Array#entriesMap#entries()

// get ('a.*', doc) returns proposed
let result =
  [ [ 'a.b', { c: 'hello' } ],
    [ 'a.d', { c: 'sup', e: { f: 'blah blah blah' } } ] ]

for (let [path, value] of result)
  console.log ('path:', path, 'value:', value)

// path: a.b value: { c: 'hello' }
// path: a.d value: { c: 'sup', e: { f: 'blah blah blah' } }

如果您同意这是一个更好的形状,更新代码很简单(更改粗体

// List monad version
const find = (path, [key, ...keys], data) => {
  if (key === undefined)
    return List ([[path.join ('.'), data]])
  ...
}

// native arrays version
const find = (path, [key, ...keys], data) => {
  if (key === undefined)
    return [[path.join ('.'), data]]
  ...
}