【问题标题】:How can i implement functional loop statements instead of for loops in this case?在这种情况下,我如何实现功能循环语句而不是 for 循环?
【发布时间】:2018-10-25 02:51:52
【问题描述】:

如何使用函数循环语句(map、forEach、reduce)而不是 for 循环来检查数组中任意两个元素的总和是否存在于数组中。

例如这样的数组

[1, 2, 9, 4, 3] // would return true as 1 + 2 = 3
[2,7,12,6,8,20]  // true as 2 + 6 = 8 which is enough to make it true
[1, 2, 4, 9] //would return false

我可以通过 for 循环来做到这一点:

const checkSumExist = arr => {
  for(let i = 0; i < arr.length; i++) {
    for(let j = i + 1; j < arr.length; j++) {
      if(arr.includes(arr[i] + arr[j])) return true;   
    }
  }

  return false; 
}

那么在这种情况下有没有使用函数循环语句而不是嵌套for循环的解决方案???

【问题讨论】:

  • 我认为您的解决方案不正确,因为您只检查了 2 个元素,而不是“至少两个元素的总和”。
  • @slider 正确,如果数组中任意两个元素的和存在于数组中,则返回true,只有两个元素不是3或4
  • 在这种情况下,您应该将描述编辑为“任意两个”而不是“至少 2 个”。
  • @slider 完成了!!谢谢

标签: javascript for-loop functional-programming


【解决方案1】:

一个简化的实现——

const main = (xs = []) =>
  xs .some ((n, i) =>
    xs .some ((m, j) =>
      i < j && xs .includes (n + m)
    )
  )
  
console.log
  ( main ([ 1, 2, 4, 9, 4, 3 ])   // true
  , main ([ 2, 7, 12, 6, 8, 20 ]) // true
  , main ([ 1, 2, 4, 9 ])         // false
  )

这个优化使用Setimproves speed to O(1)——

const main = (xs = [], s = new Set (xs)) =>
  xs .some ((n, i) =>
    xs .some ((m, j) =>
      i < j && s .has (n + m)
    )
  )

console.log
  ( main ([ 1, 2, 4, 9, 4, 3 ])   // true
  , main ([ 2, 7, 12, 6, 8, 20 ]) // true
  , main ([ 1, 2, 4, 9 ])         // false
  )

记住只在必要时进行优化

【讨论】:

  • 啊我完全忘记了.some 将索引作为第二个参数提供给您!不错
  • 此外,如果您使用的是 V8,则根据此答案 stackoverflow.com/questions/33611509/…,看起来 Set 查找也应该是 O(1)
  • 感谢您的评论。很高兴看到 Set 以这种方式优化!
  • @user633183 不错的解决方案!微小的性能改进:将 i !== j 替换为 i &lt; j
  • Tex,我现在看到了。这实际上是一个不平凡的优化,尤其是对于大的xs。再次感谢。
【解决方案2】:

编辑——我忘了​​.some 提供每个元素的索引作为第二个参数。这样会更干净一点!

const foo = [1, 2, 9, 4, 3]; // would return true
const bar = [1, 2, 4, 9]; // would return false
const baz = [2,7,12,6,8,20]; // should also be true

const sumOf2Exists = (arr) => {
  // create a hash of the values in arr so we can check the
  // accept condition in O(1) time
  const hash = arr.reduce((acc, n) => {
    acc[n] = true;
    return acc;
  }, {});
  
  // find some n, m where n and m aren't the same entry 
  // and the sum n+m is in arr (using the hash)
  return arr.some((n, i) => arr.some((m, j) => j > i && hash[n + m]));
};

console.log(sumOf2Exists(foo))
console.log(sumOf2Exists(bar))
console.log(sumOf2Exists(baz));

灵感来自 tex 的评论并使用 Immutable.js

const { Set, Range } = Immutable

const foo = [1, 2, 9, 4, 3]; // would return true
const bar = [1, 2, 4, 9]; // would return false
const baz = [2, 7, 12, 6, 8, 20]; // should also be true

const sumOf2Exists = (arr) => {
  const hash = Set(arr);
  return arr.some((n, i) => (
    Range(i+1, arr.length).some(j => hash.has(n + arr[j]))
  ));
};

console.log(sumOf2Exists(foo))
console.log(sumOf2Exists(bar))
console.log(sumOf2Exists(baz));
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.min.js"&gt;&lt;/script&gt;

【讨论】:

  • 是的,我想是的,在这种情况下,for 循环非常清晰易读!
  • 可能有更简洁的解决方案,但至少我投票+1,因为你使用reduce。我认为这种在没有reduce 的情况下在列表中循环相同类型的问题是没有问题的。
  • 我认为 .some 也是 reduce 的一个特例,除了更好,因为它短路了。
  • @Pysul 也是好东西,尤其是第二个示例(由于其简洁性),适用于不使用Set 的任何人。在某些情况下,如果您将 !== 替换为 &lt;(使用 &amp;&amp; 作为控制流运算符,您将在某些情况下跳过对 hash[n + m] 的更多评估,并将 &lt; 替换为!==)。次要,但它会使您的函数式解决方案在效率方面更接近原始问题中的命令式示例。
【解决方案3】:

以下解决方案的想法是使用reducemap构建2个数字的所有可能组合,然后使用some测试任何组合的总和是否在数组中。

function sumExists(arr) {
  return arr
    .reduce((acc, curr, i) => acc.concat(arr.slice(i + 1).map(e => [curr, e])), [])
    .some(([a, b]) => arr.includes(a + b));
}

console.log(sumExists([1, 2, 9, 4, 3]));
console.log(sumExists([2, 7, 12, 6, 8, 20]));
console.log(sumExists([1, 2, 4, 9]));

O(n^2) 版本如下:

function sumExists(arr) {
  const sums = new Set(arr);
  return arr
    .reduce((acc, curr, i) => {
      acc.push(...arr.slice(i + 1).map(e => [curr, e]));
      return acc;
    }, [])
    .some(([a, b]) => sums.has(a + b));
}

console.log(sumExists([1, 2, 9, 4, 3]));
console.log(sumExists([2, 7, 12, 6, 8, 20]));
console.log(sumExists([1, 2, 4, 9]));

【讨论】:

  • 这会慢很多!!但无论如何都很聪明
  • @SaherElgendy 你是对的。您可以通过将includes 检查更改为一组中的has 并避免使用concat 来使其更快,因为这会创建一个全新的数组。所以在时间复杂度方面,set 版本现在比 for 循环版本更好(这是 O(n^3) 因为includes)。
  • 这比 for 循环版本的空间复杂度更差
  • @Pysul 我同意。
【解决方案4】:

这是我使用 Rxjs 的解决方案:

import { Observable, from, of, zip, pairs, combineLatest, empty } from 'rxjs';
import { filter, map, takeUntil, takeWhile, single, zipAll, pairwise, combineAll, mergeMap, merge, first, skip, skipWhile, defaultIfEmpty } from 'rxjs/operators';

let test1 = [1, 2, 9, 4, 3]; // would return true as 1 + 2 = 3
let test2 = [2,7,12,6,8,20];  // true as 2 + 6 = 8 which is enough to make it true
let test3 = [1, 2, 4, 9]; //would return false


let observable1 = from(test1);

let skipSameIndex = (arr:number[], anchorIndex:number) => {
    return from(arr).pipe(
        filter((v, i) => {
        // console.log('anchodIndex:', anchorIndex, ', i:', i);
        return anchorIndex !== i;
        })
    )
}

let pairAll = (arr:number[]) => from(arr).pipe(mergeMap( (x, index) => {
    return combineLatest(of(x), skipSameIndex(arr, index))
}));

let isPairExistsInArray = (pair:[number, number], arr: number[]) => {
    let isExists = arr.indexOf(pair[0] + pair[1]) >= 0; 

    // console.log('pair:', pair, ', isExists:', isExists);

    return isExists;
}


let isSumEachElementsExists = (arr:number[]) => pairAll(arr).pipe(    
    map((pair:[number, number]) => isPairExistsInArray(pair, arr)),    
    // first(isExists => isExists)
    filter(x => x),
    defaultIfEmpty(false)
);

// skipSameIndex(test3, 1).subscribe(x => console.log(x));


isSumEachElementsExists(test1).toPromise()
    .then(isExists => console.log('test1 isExists:', isExists))
    .catch(err => console.log('ERROR:', err));


isSumEachElementsExists(test2).toPromise()
    .then(isExists => console.log('test2 isExists:', isExists))
    .catch(err => console.log('ERROR:', err));

isSumEachElementsExists(test3).toPromise()
    .then(isExists => console.log('test3 isExists:', isExists))
    .catch(err => console.log('ERROR:', err));

我的结论是 FP 很难,与迭代编程相比,解决方案过于复杂 :)。如果有人可以简化这一点,我愿意提出建议或更正。

【讨论】:

  • 实际上这是我的问题的目的,我讨厌 for 循环,我所有其他解决方案都非常复杂,所以我问是否有人有更简洁和更简单的解决方案,但似乎 for 循环可能是最好的解决方案在某些情况下!!!
【解决方案5】:

我不确定这是否是正确的解决方案,因为我不太明白您所说的功能循环是什么意思,但请检查一下,这个解决方案没有任何循环。

function check(list, i, j) {
    if(i >= list.length || j >= list.length) return false;
    if(i != j && list.includes(list[i] + list[j])) return true;
    return check(list, i+1, j) || check(list, i, j+1)
}

check([1,2,9,4,3], 0, 0)
true

check([1,2, 4, 9], 0, 0)
false

【讨论】:

  • 函数循环语句映射,forEach,reduce..你现在正在做递归!!
【解决方案6】:

也许这是一个蛮力和过度设计的解决方案,但我挑战自己提供一个普通的 JS,纯功能的方法来解决问题。

顺便说一句,那里有 SanctuaryRamda 之类的库,或许多其他库,这将节省我答案的样板部分。

它有一个问题:它检查所有可求和组合 (possibleSums),但这是一个优点,因为可以打印出所有巧合。此外,someSumExists 仅检查 possibleSums 是否输出一个或多个巧合。

另一方面,它还有另一个问题:它依赖于输入不会包含重复数字这一事实。

// ::: Combinators :::

// I :: a -> a -> a
const I = x => x

// T :: a -> (a -> a) -> a
const T = x => f => f (x)

// W :: a -> (a -> a -> a) -> a
const W = x => f => f (x) (x)

// ::: Non-generalized, partial Functor, Chain, Applicative Array-specific instances... :::

// map :: (a -> a) -> [a] -> [a]
const map = f => xs => xs.map (f)

// chain :: (a -> a) -> [a] -> [a]
const chain = f => xs => xs.reduce((o, x) => [...o, ...f (x)], [])

// ap :: [a -> b] -> [a] -> [b]
const ap = ms => m => chain (x => map (f => f(x)) (ms)) (m)

// lift2 :: [a -> c -> d] -> [a -> b] -> [b -> c] -> [d]
const lift2 = f => xs => ys => ap (map (f) (xs)) (ys)

// join :: [[a]] -> [a]
const join = chain (I)

// ::: Utils :::

// pipe :: [Any -> Any] -> Any -> Any
const pipe = xs => x => xs.reduce ((x, f) => f (x), x)

// any :: (a -> Boolean) -> [a] -> Boolean
const any = f => xs => xs.some (f)

// joinWith :: String -> [Any] -> [String]
const joinWith = sep => xs => xs.join (sep)

// length :: [Any] -> Number
const length = xs => xs.length

// equals :: a -> a -> Boolean
const equals = x => y => x === y

// not :: a -> Boolean
const not = x => x !== true

// possibleSums :: Number -> [[Number, Number, Number]]
const possibleSums = input =>
    pipe ([
       W,
       T (lift2 (x => y => x !== y && input.includes (x + y) ? [[x, y, x + y]] : [])),
       join
    ]) (input)
    
// someSumExists :: [Number] -> Boolean
const someSumExists = pipe ([ 
    possibleSums,
    length,
    equals (0),
    not
])

// printSums :: [[Number, Number, Number]] -> [String]
const printSums = pipe ([
    map (([x, y, r]) => `${x} + ${y} = ${r}`),
    joinWith ('\n')
])

// printPossibleSums :: [[Number, Number, Number]] -> String
const printPossibleSums = pipe ([ possibleSums, printSums ])
      
const input1 = [1, 2, 9, 4, 3] 
const input2 = [2,7,12,6,8,20] 
const input3 = [1, 2, 4, 9]

const output1 = printPossibleSums (input1)
const output1_ = someSumExists (input1)

const output2 = printPossibleSums (input2)
const output2_ = someSumExists (input2)

const output3 = printPossibleSums (input3)
const output3_ = someSumExists (input3)


console.log ('Input 1 has a case:', output1_)
console.log (output1)


console.log ('Input 2 has a case:', output2_)
console.log (output2)


console.log ('Input 3 has a case:', output3_)
console.log (output3)

更少样板:Ramda!

这与使用 Ramda 的函数来节省一些代码行的方法相同:

const { map, join, length, equals, not, pipe, lift, unnest } = R
const W = x => f => f (x) (x)
const T = x => f => f (x)

const possibleSums = input =>
    pipe (
       W,
       T (lift ((x, y) => x !== y && input.includes (x + y) ? [[x, y, x + y]] : [])),
       unnest
    ) (input)
    
const someSumExists = pipe ( 
    possibleSums,
    length,
    equals (0),
    not
)

const printSums = pipe (
    map (([x, y, r]) => `${x} + ${y} = ${r}`),
    join ('\n')
)

const printPossibleSums = pipe (possibleSums, printSums)

const input1 = [1, 2, 9, 4, 3] 
const input2 = [2,7,12,6,8,20] 
const input3 = [1, 2, 4, 9]

const output1 = printPossibleSums (input1)
const output1_ = someSumExists (input1)

const output2 = printPossibleSums (input2)
const output2_ = someSumExists (input2)

const output3 = printPossibleSums (input3)
const output3_ = someSumExists (input3)


console.log ('Input 1 has a case:', output1_)
console.log (output1)


console.log ('Input 2 has a case:', output2_)
console.log (output2)


console.log ('Input 3 has a case:', output3_)
console.log (output3)
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"&gt;&lt;/script&gt;

【讨论】:

    猜你喜欢
    • 2012-04-08
    • 1970-01-01
    • 2021-06-19
    • 1970-01-01
    • 2018-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多