在这种情况下,如何实现功能循环语句而不是for循环?

时间:2018-10-25 02:51:53

标签: javascript for-loop functional-programming

如何使用功能性循环语句(map,forEach,reduce)而不是for循环来检查数组中是否存在两个元素之和。

例如这样的数组

DateComponents

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

Calendar

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

6 个答案:

答案 0 :(得分:6)

简化的实现–

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
  )

此优化使用Set improves 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
  )

请记住仅在必要时进行优化

答案 1 :(得分:4)

编辑-我忘记了.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));
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.min.js"></script>

答案 2 :(得分:2)

以下解决方案中的想法是使用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]));

答案 3 :(得分:1)

这是我使用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之后,我的结论很难,与迭代编程相比,解决方案过于复杂:)。如果有人可以简化我的工作,我愿意提出建议或纠正。

答案 4 :(得分:0)

我不确定这是否是正确的解决方案,因为我不太了解功能循环的含义,但请检查一下,此解决方案没有任何循环。

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

答案 5 :(得分:0)

也许这是一种蛮力和过度设计的解决方案,但是我挑战自己提供一种纯净的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)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>