web-dev-qa-db-ja.com

JavaScriptで配列をペア(現在、次)として反復する

質問 リストをPythonでペア(現在、次)として反復する の場合、OPは、Pythonリストを一連の_current, next_として反復することに関心があります。私は同じ問題を抱えていますが、JavaScriptで lodash を使用して、可能な限り最もクリーンな方法でそれを実行したいと思います。

単純なforループでこれを行うのは簡単ですが、あまりエレガントに感じません。

_for (var i = 0; i < arr.length - 1; i++) {
  var currentElement = arr[i];
  var nextElement = arr[i + 1];
}
_

ロダッシュはほとんどこれを行うことができます:

__.forEach(_.Zip(arr, _.rest(arr)), function(Tuple) {
  var currentElement = Tuple[0];
  var nextElement = Tuple[1];
})
_

これに関する微妙な問題は、最後の反復でnextElementundefinedになることです。

もちろん、理想的なソリューションは、必要な範囲でループするpairwise lodash関数です。

__.pairwise(arr, function(current, next) {
  // do stuff 
});
_

これをすでに行う既存のライブラリはありますか?または、私が試したことのないJavaScriptでペアワイズ反復を実行する別の良い方法はありますか?


明確化:_arr = [1, 2, 3, 4]_の場合、pairwise関数は次のように反復します:_[1, 2]_、_[2, 3]_、_[3, 4]_、not _[1, 2]_、_[3, 4]_。これは、OPが Pythonの元の質問 で求めていたものです。

18
therealrootuser

「醜い」部分を関数にすると、見栄えが良くなります。

arr = [1, 2, 3, 4];

function pairwise(arr, func){
    for(var i=0; i < arr.length - 1; i++){
        func(arr[i], arr[i + 1])
    }
}

pairwise(arr, function(current, next){
    console.log(current, next)
})

次のものだけでなく、すべてのi、i + nペアを反復処理できるように少し変更することもできます。

function pairwise(arr, func, skips){
    skips = skips || 1;
    for(var i=0; i < arr.length - skips; i++){
        func(arr[i], arr[i + skips])
    }
}

pairwise([1, 2, 3, 4, 5, 6, 7], function(current,next){
    console.log(current, next) // displays (1, 3), (2, 4), (3, 5) , (4, 6), (5, 7)
}, 2)
9
juvian

Rubyでは、これは each_cons

(1..5).each_cons(2).to_a # => [[1, 2], [2, 3], [3, 4], [4, 5]]

Lodashの提案 でしたが、拒否されました。ただし、npmには each-cons モジュールがあります。

const eachCons = require('each-cons')

eachCons([1, 2, 3, 4, 5], 2) // [[1, 2], [2, 3], [3, 4], [4, 5]]

aperture 関数も Ramda にあり、同じことを行います。

const R = require('ramda')

R.aperture(2, [1, 2, 3, 4, 5]) // [[1, 2], [2, 3], [3, 4], [4, 5]]
9
chocolateboy

この回答は、Haskellで同様の質問に対して私が見た回答に触発されました: https://stackoverflow.com/a/4506000/5932012

Lodashのヘルパーを使用して、次のように記述できます。

const zipAdjacent = function<T> (ts: T[]): [T, T][] {
  return Zip(dropRight(ts, 1), tail(ts));
};
zipAdjacent([1,2,3,4]); // => [[1,2], [2,3], [3,4]]

(Haskellの同等の機能とは異なり、LodashのdropRightはHaskellの動作とは異なるため、Zipが必要です。最短の配列ではなく、最長の配列の長さが使用されます。)

ラムダでも同じ:

const zipAdjacent = function<T> (ts: T[]): [T, T][] {
  return R.Zip(ts, R.tail(ts));
};
zipAdjacent([1,2,3,4]); // => [[1,2], [2,3], [3,4]]

Ramdaには、これをカバーする関数 aperture がすでにあります。デフォルトの2ではなく、必要な連続要素の数を定義できるため、これは少し一般的です。

R.aperture(2, [1,2,3,4]); // => [[1,2], [2,3], [3,4]]
R.aperture(3, [1,2,3,4]); // => [[1,2,3],[2,3,4]]
4

ここに簡単なワンライナーがあります:

[1,2,3,4].reduce((acc, v, i, a) => { if (i < a.length - 1) { acc.Push([a[i], a[i+1]]) } return acc; }, []).forEach(pair => console.log(pair[0], pair[1]))

またはフォーマット:

[1, 2, 3, 4].
reduce((acc, v, i, a) => {
  if (i < a.length - 1) {
    acc.Push([a[i], a[i + 1]]);
  }
  return acc;
}, []).
forEach(pair => console.log(pair[0], pair[1]));

どのログ:

1 2
2 3
3 4
2
Robert Mitchell

iterables および generator functions を使用する別のソリューション:

function * pairwise (iterable) {
    const iterator = iterable[Symbol.iterator]()
    let current = iterator.next()
    let next = iterator.next()
    while (!current.done) {
        yield [current.value, next.value]
        current = next
        next = iterator.next()
    }
}

console.log(...pairwise([]))
console.log(...pairwise(['Apple']))
console.log(...pairwise(['Apple', 'orange', 'kiwi', 'banana']))
console.log(...pairwise(new Set(['Apple', 'orange', 'kiwi', 'banana'])))

利点:

  • 配列(セットなど)だけでなく、すべての反復可能オブジェクトで機能します。
  • 中間または一時配列を作成しません。
  • レイジー評価され、非常に大きなイテラブルで効率的に動作します。
2
salomvary

以下は、依存関係のない一般的な機能ソリューションです。

_const nWise = (n, array) => {
  iterators = Array(n).fill()
    .map(() => array[Symbol.iterator]());
  iterators
    .forEach((it, index) => Array(index).fill()
      .forEach(() => it.next()));
  return Array(array.length - n + 1).fill()
    .map(() => (iterators
      .map(it => it.next().value);
};

const pairWise = (array) => nWise(2, array);
_

私はまったく見栄えがよくないことを知っていますが、いくつかの汎用ユーティリティ関数を導入することで、見栄えをよくすることができます。

_const sizedArray = (n) => Array(n).fill();
_

sizedArrayforEachを組み合わせてtimesを実装することもできますが、これは非効率的な実装になります。私見それのような自明の機能のために命令的なコードを使うことは大丈夫です:

_const times = (n, cb) => {
  while (0 < n--) {
    cb();
  }
}
_

よりハードコアなソリューションに興味がある場合は、 this の回答を確認してください。

残念ながら_Array.fill_は単一の値のみを受け入れ、コールバックは受け入れません。したがって、Array(n).fill(array[Symbol.iterator]())はすべての位置に同じ値を入れます。これは次の方法で回避できます。

_const fillWithCb = (n, cb) => sizedArray(n).map(cb);
_

最終的な実装:

_const nWise = (n, array) => {
  iterators = fillWithCb(n, () => array[Symbol.iterator]());
  iterators.forEach((it, index) => times(index, () => it.next()));
  return fillWithCb(
    array.length - n + 1,
    () => (iterators.map(it => it.next().value),
  );
};
_

パラメータのスタイルをカレー化に変更することで、ペアワイズの定義はより見栄えが良くなります。

_const nWise = n => array => {
  iterators = fillWithCb(n, () => array[Symbol.iterator]());
  iterators.forEach((it, index) => times(index, () => it.next()));
  return fillWithCb(
    array.length - n + 1,
    () => iterators.map(it => it.next().value),
  );
};

const pairWise = nWise(2);
_

そして、これを実行すると、次のようになります。

_> pairWise([1, 2, 3, 4, 5]);
// [ [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ] ]
_
2
fodma1

Array.reduce を少しラップして、これをすべてクリーンに保つことができます。ループインデックス/ループ/外部ライブラリは必要ありません。

結果が必要な場合は、配列を作成して収集します。

function pairwiseEach(arr, callback) {
  arr.reduce((prev, current) => {
    callback(prev, current)
    return current
  })
}

function pairwise(arr, callback) {
  const result = []
  arr.reduce((prev, current) => {
    result.Push(callback(prev, current))
    return current
  })
  return result
}

const arr = [1, 2, 3, 4]
pairwiseEach(arr, (a, b) => console.log(a, b))
const result = pairwise(arr, (a, b) => [a, b])

const output = document.createElement('pre')
output.textContent = JSON.stringify(result)
document.body.appendChild(output)
2
DarkKnight

d3.js は、特定の言語で呼び出されるバージョンの built-in バージョンを提供しますsliding

console.log(d3.pairs([1, 2, 3, 4])); // [[1, 2], [2, 3], [3, 4]]
<script src="http://d3js.org/d3.v5.min.js"></script>

d3.pairs(array [、reducer]) <>

指定された配列内の隣接する要素のペアごとに、指定されたレデューサー関数を呼び出し、要素iと要素i-1を渡します。レデューサーが指定されていない場合、デフォルトで、それぞれに2要素の配列を作成する関数になりますペア。

1
Xavier Guihot

これがArray.prototype.shiftを使用した私のアプローチです。

Array.prototype.pairwise = function (callback) {
    const copy = [].concat(this);
    let next, current;

    while (copy.length) {
        current = next ? next : copy.shift();
        next = copy.shift();
        callback(current, next);
    }
};

これは次のように呼び出すことができます。

// output:
1 2
2 3
3 4
4 5
5 6

[1, 2, 3, 4, 5, 6].pairwise(function (current, next) {
    console.log(current, next);
});

それを分解するには:

while (this.length) {

Array.prototype.shiftは配列を直接変更するため、要素が残っていない場合、長さは明らかに0に解決されます。これはJavaScriptの「不正な」値であるため、ループが壊れます。

current = next ? next : this.shift();

nextが以前に設定されている場合は、これをcurrentの値として使用します。これにより、アイテムごとに1回の反復が可能になるため、すべての要素を隣接する後続ノードと比較できます。

残りは簡単です。

1
James Wright

私の2セント。基本的なスライス、ジェネレーターのバージョン。

function* generate_windows(array, window_size) {
    const max_base_index = array.length - window_size;
    for(let base_index = 0; base_index <= max_base_index; ++base_index) {
        yield array.slice(base_index, base_index + window_size);
    }
}
const windows = generate_windows([1, 2, 3, 4, 5, 6, 7, 8, 9], 3);
for(const window of windows) {
    console.log(window);
}

それが誰かを助けることを願っています! (好き)

arr = [1, 2, 3, 4];
output = [];
arr.forEach((val, index) => {
  if (index < (arr.length - 1) && (index % 2) === 0) {
    output.Push([val, arr[index + 1]])
  }
})

console.log(output);
0
Lu Chinke

このために、すべてのパラメーターを指定してforEachを使用するだけです。

yourArray.forEach((current, idx, self) => {
  if (let next = self[idx + 1]) {
    //your code here
  }
})
0
Andreas Bolz