takeWhile在JavaScript中实现 - 寻找更好的想法

时间:2018-03-23 13:40:44

标签: javascript haskell recursion

Haskell有一个takeWhile函数:

Prelude> takeWhile odd [1,3,5,7,8,9]
[1,3,5,7]

只要在True中应用谓词函数结果,它就会从列表中“获取”元素。在它变为False时,它会停止。

我们如何实施?

这是我提出的Haskell递归方法:

takewhile::(a->Bool)->[a]->[a]
takewhile _ [] = []
takewhile f (x:xs) | f x == True = x : takewhile f xs
                   | otherwise = []

只要谓词f xTrue,它就会继续调用自身,否则它会返回一个空列表,而不会自行调用。

我可以为JavaScript提出以下实现。它有点冗长,并调用定义另一个函数来传递中间结果:

function takeWhile(f, xs) {
 return take(f, xs, [])
}

function take(f, xs, arr) {
 if(!xs || xs.length === 0) {
 return arr
 }
 x = xs.shift()
 if(f(x)) {
   arr.push(x)
   return take(f, xs, arr)
 } else {
   return arr
 }
}

takeWhile((x)=>{
 return x % 2 !== 0
},[1,3,5,7,9,11])

在JavaScript中实现它有更好的想法吗?

4 个答案:

答案 0 :(得分:6)

如果你希望你的takeWhile像HS一样表现,即懒惰,你需要JS中的生成器:



function* takeWhile(fn, xs) {
    for (let x of xs)
        if (fn(x))
            yield x;
        else
            break;
}

function* naturalNumbers() {
    let n = 0;
    while (true)
        yield n++;
}

result = takeWhile(x => x < 10, naturalNumbers())
console.log([...result])
&#13;
&#13;
&#13;

HS代码的直接端口也是可能的,但它只适用于物化数组(即热切地):

&#13;
&#13;
// would be nice, but JS sucks ;(
// let takeWhile = (f, [x, ...xs]) => f(x) ? [x, ...takeWhile(f, xs)] : [];

let takeWhile = (f, xs) => xs.length ? takeWhileNotEmpty(f, xs) : [];
let takeWhileNotEmpty = (f, [x, ...xs]) =>  f(x) ? [x, ...takeWhile(f, xs)] : [];


let odd = x => x % 2
a = [1,3,5,7,8,9]
r = takeWhile(odd, a)
console.log(r)
&#13;
&#13;
&#13;

实际上,由于@naomik显示here,这是处理空列表的更好方法:

&#13;
&#13;
let nil = {};
let takeWhile = (f, [x = nil, ...xs]) => (x === nil || !f(x)) 
    ? [] : [x, ...takeWhile(f, xs)];

console.log(takeWhile(x => x % 2, [1, 3, 5, 7, 8, 9]));
&#13;
&#13;
&#13;

最后,你的初步尝试确实有一点,因为,与上面不同,它是尾递归,这是一件好事。它可以更简洁地写成

let takeWhile = (f, xs) => take1(f, xs, []);
let take1 = (f, xs, acc) => xs.length ? take2(f, xs, acc) : acc;
let take2 = (f, [x, ...xs], acc) => f(x) ? take1(f, xs, acc.concat(x)) : acc;

两种方法的组合(即递归生成器)留作练习......

答案 1 :(得分:2)

这是一个简洁的解决方案:

const takeWhile = (fn, arr) => {
  const [x, ...xs] = arr;

  if (arr.length > 0 && fn(x)) {
    return [x, ...takeWhile(fn, xs)]
  } else {
    return [];
  }
};

以下是上述内容的精简版本:

const takeWhile = 
  (fn, a) => a.length && fn(a[0]) ? [a[0], ...takeWhile(fn, a.slice(1))] : [];

它不容易阅读,所以我不建议您使用压缩版本。


尾部递归版本:

const takeWhile = (fn, arr) => {
  const recur = (a, acc) => {
    const [x, ...xs] = a;

    if (a.length > 0 && fn(x)) {
      return recur(xs, [...acc, x]);
    } else {
      return acc;
    }
  }

  return recur(arr, []);
};

答案 2 :(得分:1)

    function takeWhile(array, predicate) {
      const index = array.findIndex((el, i) => !predicate(el, i));
            
      return index >= 0
        ? array.slice(0, index)
        : array;
    }
        
    console.log(takeWhile([1, 2, 3, 4, -1, 5, 6, 7], item => item > 0));

答案 3 :(得分:0)

你可以使用带有fliag的循环,jsbin

const takeWhile = (func, arr) => {
  for(let i = 0, isCorrect; !isCorrect; i++){
    if(!func(arr[i]) || i >= arr.length){isCorrect = true;}
    else {
     console.log(arr[i]);
    }
  }
}

这个函数有两个参数。一个将是你的谓词,第二个数组。 因此,如果谓词失败或数组结束,循环将结束。它不会返回任何值,但如果您只需要添加return语句。