【问题标题】:Recursively Search in Array with a For Loop使用 For 循环在数组中递归搜索
【发布时间】:2021-04-03 02:24:50
【问题描述】:

我知道有更好的方法来搜索数组,但我真的很想了解在递归调用中找到值时如何返回。找到时记录不是问题,但我似乎无法在找到时让它返回 true。

问题是基本的。使用 for 循环和递归在多维数组中完全搜索一个值,如果找到则返回 true,如果未找到则返回 false。我已经尝试返回递归函数和我能想到的所有其他函数,但没有一个完全有效。

function lookForKey( arr ){
    for ( let i = 0; i < arr.length; i++ ) {
        if( arr[i] == "key" ){
            console.log( "Key found.");
            return true;
        } else if( Array.isArray( arr[i] ) ){
            lookForKey( arr[i] );
        }
    }

    return false;
}

let arr = [
    ["foo", "bar"],
    ["foo", "bar", "key"]
];

console.log( lookForKey( arr ) );

感谢您对此的任何见解!

【问题讨论】:

  • 您的 else if 不返回。因此,如果递归调用返回 true/false,那么调用者的结果不会做任何事情
  • 您是否在寻找递归解决方案?有很多方法可以做到这一点。
  • @jmargolisvt 他们不是在寻找解决方案。他们希望了解自己做错了什么。
  • 谢谢。我知道那是开始。现在我想起来,它完全改变了这个问题。返回递归调用会导致函数在检查第一个数组后返回,因为它不匹配任何条件。我只是不确定如何在第一次调用未找到匹配项后使递归调用继续前一个 for 循环。

标签: javascript arrays for-loop recursion


【解决方案1】:

递归是一种函数式遗产,因此将其与函数式风格一起使用会产生最佳效果。这意味着要避免诸如突变、变量重新分配和其他副作用之类的事情。 for 是一种副作用,是命令式风格中使用的循环结构。请注意,当我们使用函数式样式时,没有理由使用for 作为递归循环。

  1. 如果输入arr 为空,则没有可搜索的内容。返回基本情况,false
  2. (归纳)输入arr 不为空。如果第一个元素arr[0]是一个数组,递归search该元素递归搜索子问题,arr.slice(1)
  3. (归纳)输入arr 不为空且第一个元素不是数组。如果第一个元素与查询匹配,q,则返回 true,否则递归搜索子问题,arr.slice(1)

下面我们使用上面提供的inductive reasoning 编写search -

function search (arr, q)
{ if (arr.length === 0)
    return false                                         // 1
  else if (Array.isArray(arr[0]))
    return search(arr[0], q) || search(arr.slice(1), q)  // 2
  else
    return arr[0] === q || search(arr.slice(1), q)       // 3
}

const input =
  [ ["foo", "bar"]
  , ["foo", "bar", "key"]
  ]

console.log(search(input, "key"))
console.log(search(input, "bar"))
console.log(search(input, "zzz"))

如果我们要分叉,ifelsereturn 也是副作用。你也可以把search写成纯表达式-

const search = (arr, q) =>
  arr.length === 0
    ? false                                          // 1
: Array.isArray(arr[0])
    ? search(arr[0], q) || search(arr.slice(1), q)   // 2
: arr[0] === q || search(arr.slice(1), q)            // 3

const input =
  [ ["foo", "bar"]
  , ["foo", "bar", "key"]
  ]

console.log(search(input, "key"))
console.log(search(input, "bar"))
console.log(search(input, "zzz"))

而解构赋值使事情更具声明性 -

const none =
  Symbol("none")

const search = ([ v = none, ...more ], q) =>
  v === none
    ? false                             // 1
: Array.isArray(v)
    ? search(v, q) || search(more, q)   // 2
: v === q || search(more, q)            // 3

const input =
  [ ["foo", "bar"]
  , ["foo", "bar", "key"]
  ]

console.log(search(input, "key"))
console.log(search(input, "bar"))
console.log(search(input, "zzz"))

以上search 的所有变体都有相同的输出 -

true
true
false

我提出了一些关于递归和for 循环的cmets 不属于同一学派,从而引发了一些争议。首先,search 可能看起来像 没有递归 -

function search (arr, q)
{ const s = [arr]
  let v
  while(v = s.shift())
    if(Array.isArray(v))
      for (const _ of v)
        s.unshift(_)
    else if (v === q)
      return true
  return false
}

const input =
  [ ["foo", "bar"]
  , ["foo", "bar", "key"]
  ]

console.log(search(input, "key"))
console.log(search(input, "bar"))
console.log(search(input, "zzz"))

但这并不是说想法不能跨越学校。考虑这个变体,它使用forEach 和递归并利用call-with-current-continuation 技术,下面是callcc -

const callcc = f =>
{ try { return f(value => { throw {callcc, value} }) }
  catch (e) { if (e.callcc) return e.value; else throw e }
}

function search (arr, q)
{ const loop = (t, exit) =>
    Array.isArray(t)
      ? t.forEach(v => loop(v, exit))
      : t === q && exit(t)
  return callcc(k => loop(arr, k))
}

const input =
  [ ["foo", "bar"]
  , ["foo", "bar", "key"]
  ]

console.log(search(input, "key"))
console.log(search(input, "bar"))
console.log(search(input, "zzz"))

【讨论】:

  • 这个答案是一门艺术
  • @python_learner 非常感谢。我激怒了一些羽毛,所以我对答案进行了修改。
  • @thank you 虽然这没有解决我的具体问题,但这是很好的信息,而且结构很好。我一定会探索您分享的所有内容。
  • 乐于助人,迈克。如果您在某个地方遇到困难,请随时与我们联系。
【解决方案2】:

结果是假的,因为我们试图从函数中返回假。当 for 循环运行时,当我们得到想要的结果时打破它的一种方法是使用break。我们可以将find的状态保存在一个名为result的变量中,该变量可以从函数lookForKey返回

function lookForKey( arr ) {
    var result = false;
    for ( let i = 0; i < arr.length; i++ ) {
        if( arr[i] == "key" ){
            result = true;
            break;
        } else if( Array.isArray( arr[i] ) ){
            result = lookForKey( arr[i] );
        }
    }

    return result;
}

在上面的代码中,我们没有从函数中返回 false,而是返回了包含实际搜索值的 result 变量。我们在这里使用break 语句,一旦我们得到想要的结果就从for循环中跳出来。

【讨论】:

  • @Taplar 感谢您的纠正。我误解了这个问题,现在已经编辑了。如果您认为我这次理解正确,请告诉我。请原谅我是新的贡献者。
【解决方案3】:

通过返回 true 来断言找到的条件可以在一个逻辑表达式中完成,一旦事实不可否认,该表达式就会快捷。

function myFind( haystack, needle ){
    for ( let i = 0; i < haystack.length; i++ ) {
        if ( haystack[i] === needle 
             ||
             ( Array.isArray( haystack[i] ) 
               &&
               myFind( haystack[i], needle )
             )
           ) 
           return true; // return only when found

        // not found, check next array element
    }

    return false;  // not found as any element or in any element
}

let arr = [
    ["foo", "bar"],
    ["foo", "bar", "key"]
];

console.log( myFind( arr, "key" ) );
console.log( myFind( arr, "foo" ) );
console.log( myFind( arr, "xyzzy" ) );

【讨论】:

  • 谢谢@richard!你的解释也很有帮助。
【解决方案4】:

function lookForKey( arr ){
    for ( let i = 0; i < arr.length; i++ ) {
        if( arr[i] == "key" ){
            console.log( "Key found.");
            return true;
        } else if( Array.isArray( arr[i] ) ){
            if (lookForKey(arr[i])) return true;
        }
    }

    return false;
}

let arr = [
    ["foo", "bar"],
    ["foo", "bar", "key"]
];

console.log( lookForKey( arr ) );

所以有两个变化。首先,您必须在递归调用中获得回报。但是,如果递归调用返回 false,您不希望立即从调用者返回。你想继续循环。因此,您可以将其设为有条件的,并且仅在递归调用返回 true 时才返回 true。

【讨论】:

  • 非常感谢您花时间解释。我明白为什么这有效。在检查第一个数组中的最后一项之后,我仍然缺少什么导致递归调用返回循环,而不是继续迭代。
  • @Mike 使用您的示例数据,它将从 if 返回 true,因为 key 是最后一个子数组中的最后一个值。如果它在任何子数组中都不存在,则所有 for 循环都将完成而不返回,并且会发生底部的 return false。
  • 对不起!我的意思是在添加条件之前,只是返回递归调用。 return lookForKey( arr[i] ); 这在检查我的示例数据中的第一个数组后停止,而不是移动到下一个数组。我不明白为什么会停止。
  • 如果嵌套调用没有条件,它将进入第一个数组的嵌套调用。然后它会检查所有元素,看看它们不是key。 else 不会发生,因为嵌套的数组值本身都不是数组,所以它会在方法的底部返回 false,这会返回 false 给调用者。因此,此时逻辑已验证密钥不在第一个子数组中。如果您立即返回该错误,那将终止该方法并且您将不会继续检查正在进行的子数组。 @迈克
  • 问题要求递归解决问题,而这个答案仍然使用for循环??‍♀️
猜你喜欢
  • 2021-06-27
  • 1970-01-01
  • 2013-08-28
  • 1970-01-01
  • 1970-01-01
  • 2023-03-19
  • 1970-01-01
  • 2016-07-18
  • 1970-01-01
相关资源
最近更新 更多