web-dev-qa-db-ja.com

JavaScriptでオブジェクトの配列から異なる値を取得する方法

私は以下のものを持っていると仮定します。

var array = 
    [
        {"name":"Joe", "age":17}, 
        {"name":"Bob", "age":17}, 
        {"name":"Carl", "age": 35}
    ]

次のような結果配列が得られるように、すべての異なる年齢層の配列を取得できるようにするための最良の方法は何ですか。

[17, 35]

別の方法でデータやより良い方法を構築して、 "age"の値をチェックして別の配列に対して存在を確認し、そうでない場合はそれを追加する必要がないようにすることができますか。

何もしなければ、何も繰り返さずに年齢を区別することができます。

現在の非効率的な方法私は改善したいのですが...もしそれが "array"の代わりにオブジェクトの配列ではなく、いくつかのユニークなキー(すなわち "1,2,3")を持つオブジェクトの "map"であることを意味するなら大丈夫です。私はただ最も効率的な方法を探しています。

以下は私が現在どのようにそれをしているかですが、私にとっては、反復はうまくいっているとしても効率のために単に不器用であるように思われます...

var distinct = []
for (var i = 0; i < array.length; i++)
   if (array[i].age not in distinct)
      distinct.Push(array[i].age)
253
Rolando

これがPHPであれば、キーを使って配列を作成し、最後にarray_keysを使用しますが、JSにはそのような豪華さはありません。代わりに、これを試してください:

var flags = [], output = [], l = array.length, i;
for( i=0; i<l; i++) {
    if( flags[array[i].age]) continue;
    flags[array[i].age] = true;
    output.Push(array[i].age);
}
102

ES6/ES2015以降を使用している場合は、次のようにして実行できます。

const unique = [...new Set(array.map(item => item.age))];

ここで はそのやり方の例です。

385
Vlad Bezden

あなたはこのような辞書のアプローチを使うことができます。基本的には、区別したい値を辞書のキーとして割り当てます。キーが存在しない場合は、その値を別個のものとして追加します。

var unique = {};
var distinct = [];
for( var i in array ){
 if( typeof(unique[array[i].age]) == "undefined"){
  distinct.Push(array[i].age);
 }
 unique[array[i].age] = 0;
}

これが実用的なデモです: http://jsfiddle.net/jbUKP/1

これはO(n)になります。ここで、nは配列内のオブジェクトの数、mは固有値の数です。 O(n)よりも早い方法はありません。それぞれの値を少なくとも1回検査する必要があるからです。

パフォーマンス

http://jsperf.com/filter-versus-dictionary 私がこの辞書を走らせたとき、30%早くなりました。

103
Travis J

eS6を使う

let array = [
  { "name": "Joe", "age": 17 },
  { "name": "Bob", "age": 17 },
  { "name": "Carl", "age": 35 }
];
array.map(item => item.age)
  .filter((value, index, self) => self.indexOf(value) === index)

> [17, 35]
98
Ivan Nosov

これが、2017年8月25日現在、TypeScript用のES6を介した新しいSetを使用してこれを解決する方法です。

Array.from(new Set(yourArray.map((item: any) => item.id)))
55

ES6の機能を使うと、次のようなことができます。

const uniqueAges = [...new Set( array.map(obj => obj.age)) ];
52
Russell Vea

マップを作成して重複を削除するだけです。

var ages = array.map(function(obj) { return obj.age; });
ages = ages.filter(function(v,i) { return ages.indexOf(v) == i; });

console.log(ages); //=> [17, 35]

編集: Aight!パフォーマンスの点で最も効率的な方法ではなく、最も単純で最も読みやすいIMOです。もしあなたが本当にマイクロ最適化を気にかけたり、大量のデータを持っているのであれば、通常のforループがより "効率的"になるでしょう。

51
elclanrs

@ travis-jの回答のforEachバージョン(最近のブラウザとNode JSの世界で役に立ちます):

var unique = {};
var distinct = [];
array.forEach(function (x) {
  if (!unique[x.age]) {
    distinct.Push(x.age);
    unique[x.age] = true;
  }
});

Chrome v29.0.1547の34%高速な動作: http://jsperf.com/filter-versus-dictionary/3

そして、マッパー関数を使う一般的な解決策(直接マップよりは少し遅いですが、それは予想されています):

function uniqueBy(arr, fn) {
  var unique = {};
  var distinct = [];
  arr.forEach(function (x) {
    var key = fn(x);
    if (!unique[key]) {
      distinct.Push(key);
      unique[key] = true;
    }
  });
  return distinct;
}

// usage
uniqueBy(array, function(x){return x.age;}); // outputs [17, 35]
18
Mrchief

私は アンダースコア をデフォルトですべての新しいプロジェクトに固執し始めたので、私はこれらの小さなデータ管理の問題について考える必要はありません。

var array = [{"name":"Joe", "age":17}, {"name":"Bob", "age":17}, {"name":"Carl", "age": 35}];
console.log(_.chain(array).map(function(item) { return item.age }).uniq().value());

[17, 35]を生成します。

12
Eevee

lodash を使用

var array = [
    { "name": "Joe", "age": 17 },
    { "name": "Bob", "age": 17 },
    { "name": "Carl", "age": 35 }
];
_.chain(array).pluck('age').unique().value();
> [17, 35]
8
Ivan Nosov

有効な答えはたくさんありますが、reduce()メソッドのみを使用するものを追加したいと思いました。

function uniqueBy(arr, prop){
  return arr.reduce((a, d) => {
    if (!a.includes(d[prop])) { a.Push(d[prop]); }
    return a;
  }, []);
}

このように使用してください。

var array = [
  {"name": "Joe", "age": 17}, 
  {"name": "Bob", "age": 17}, 
  {"name": "Carl", "age": 35}
];

var ages = uniqueBy(array, "age");
console.log(ages); // [17, 35]
7
Harry Stevens

これを解決するもう1つの方法があります。

var result = {};
for(var i in array) {
    result[array[i].age] = null;
}
result = Object.keys(result);

私はこのソリューションが他のものと比較してどれくらい速いのかわかりませんが、私はよりきれいな外観が好きです。 ;-)


編集:さて、上記はすべてここで最も遅い解決策のようです。

ここでパフォーマンステストケースを作成しました。 http://jsperf.com/distinct-values-from-array

年齢(整数)をテストする代わりに、名前(ストリング)を比較することにしました。

方法1(TSのソリューション)は非常に高速です。興味深いことに、方法7は他のすべての解決方法よりも優れています。ここでは、.indexOf()を削除し、「手動」実装を使用して、ループされた関数呼び出しを回避しました。

var result = [];
loop1: for (var i = 0; i < array.length; i++) {
    var name = array[i].name;
    for (var i2 = 0; i2 < result.length; i2++) {
        if (result[i2] == name) {
            continue loop1;
        }
    }
    result.Push(name);
}

SafariとFirefoxを使用した場合のパフォーマンスの違いは驚くべきものです。Chromeが最適化に最も適しているようです。

上記のスニペットが他のものに比べて非常に高速な理由、私には答えがあるより賢い人がいるのかどうか、私にはよくわかりません。 ;-)

7
BaM
function get_unique_values_from_array_object(array,property){
    var unique = {};
    var distinct = [];
    for( var i in array ){
       if( typeof(unique[array[i][property]]) == "undefined"){
          distinct.Push(array[i]);
       }
       unique[array[i][property]] = 0;
    }
    return distinct;
}
5

Lodash を使う

var array = [
    { "name": "Joe", "age": 17 },
    { "name": "Bob", "age": 17 },
    { "name": "Carl", "age": 35 }
];

_.chain(array).map('age').unique().value();

戻り値[17,35]

5
Yash Vekaria

underscore.js _.uniq(_.pluck(array,"age"))

私はあなたがgroupBy機能を探していると思います(Lodashを使って)

_personsList = [{"name":"Joe", "age":17}, 
                {"name":"Bob", "age":17}, 
                {"name":"Carl", "age": 35}];
_uniqAgeList = _.groupBy(_personsList,"age");
_uniqAges = Object.keys(_uniqAgeList);

結果を生成します。

17,35

jsFiddleデモ: http://jsfiddle.net/4J2SX/201/ /

4
Amil Sajeev
var unique = array
    .map(p => p.age)
    .filter((age, index, arr) => arr.indexOf(age) == index)
    .sort(); // sorting is optional

// or in ES6

var unique = [...new Set(array.map(p => p.age))];
4
oleg gabureac

あなたがArray.prototype.includeを持っているか、それを polyfill にするつもりなら、これはうまくいきます:

var ages = []; array.forEach(function(x) { if (!ages.includes(x.age)) ages.Push(x.age); });
2
quillbreaker

私のようにスピードを落とさずにもっと "機能的"を好むのであれば、この例ではreduceクロージャーに囲まれた高速な辞書検索を使います。

var array = 
[
    {"name":"Joe", "age":17}, 
    {"name":"Bob", "age":17}, 
    {"name":"Carl", "age": 35}
]
var uniqueAges = array.reduce((p,c,i,a) => {
    if(!p[0][c.age]) {
        p[1].Push(p[0][c.age] = c.age);
    }
    if(i<a.length-1) {
        return p
    } else {
        return p[1]
    }
}, [{},[]])

これによると テスト 私の解決策は提案された答えの2倍の速さです

2
bmaggi

これを見つけただけで便利だと思いました

_.map(_.indexBy(records, '_id'), function(obj){return obj})

ここでも アンダースコア を使用します。このようなオブジェクトがある場合は

var records = [{_id:1,name:'one', _id:2,name:'two', _id:1,name:'one'}]

それはあなたにユニークな物だけを与えるでしょう。

ここで起こることはindexByがこのようなマップを返すことです

{ 1:{_id:1,name:'one'}, 2:{_id:2,name:'two'} }

それがマップだからといって、すべてのキーは一意です。

それでは、このリストを配列にマッピングし直しています。

個別の値のみが必要な場合

_.map(_.indexBy(records, '_id'), function(obj,key){return key})

keyは文字列として返されるので、整数が必要な場合は、次のようにしてください。

_.map(_.indexBy(records, '_id'), function(obj,key){return parseInt(key)})
2
simo

これは、reduceを使用し、マッピングを可能にし、挿入順序を維持する多用途のソリューションです。

items :配列

mapper :アイテムを基準にマップする単項関数、またはアイテム自体をマップする場合は空です。

function distinct(items, mapper) {
    if (!mapper) mapper = (item)=>item;
    return items.map(mapper).reduce((acc, item) => {
        if (acc.indexOf(item) === -1) acc.Push(item);
        return acc;
    }, []);
}

使用法

const distinctLastNames = distinct(items, (item)=>item.lastName);
const distinctItems = distinct(items);

これを配列のプロトタイプに追加して、 items パラメータを省略しても構いません。

const distinctLastNames = items.distinct( (item)=>item.lastName) ) ;
const distinctItems = items.distinct() ;

マッチングを高速化するために、配列の代わりにSetを使用することもできます。

function distinct(items, mapper) {
    if (!mapper) mapper = (item)=>item;
    return items.map(mapper).reduce((acc, item) => {
        acc.add(item);
        return acc;
    }, new Set());
}
1
Steven Spungin
unique(obj, prop) {
    let result = [];
    let seen = new Set();

    Object.keys(obj)
        .forEach((key) => {
            let value = obj[key];

            let test = !prop
                ? value
                : value[prop];

            !seen.has(test)
                && seen.add(test)
                && result.Push(value);
        });

    return result;
}
1
Devonte
const x = [
  {"id":"93","name":"CVAM_NGP_KW"},
  {"id":"94","name":"CVAM_NGP_PB"},
  {"id":"93","name":"CVAM_NGP_KW"},
  {"id":"94","name":"CVAM_NGP_PB"}
].reduce(
  (accumulator, current) => accumulator.some(x => x.id === current.id)? accumulator: [...accumulator, current ], []
)

console.log(x)

/* output 
[ 
  { id: '93', name: 'CVAM_NGP_KW' },
  { id: '94', name: 'CVAM_NGP_PB' } 
]
*/
1

高性能のシンプルなワンライナー。私の テストではES6ソリューションより6%速い

var ages = array.map(function(o){return o.age})。filter(function(v、i、a){return a.indexOf(v)=== i});

0
Craig

ちょうど試して

var x = [] ;
for (var i = 0 ; i < array.length ; i++)
{
 if(x.indexOf(array[i]['age']) == -1)
  {
    x.Push(array[i]['age']);
  }
}
console.log(x);
0
Pravin Pareek

私はこれが古くて比較的よく答えられた質問であることを知っています、そして、私が与えている答えは完全なオブジェクトを取り戻します(この投稿に関する多くのコメントで提案されたものを見ます)。それは「粘着性がある」かもしれませんが、読みやすさの点では、他の多くのソリューションよりもずっと効率的ではありますが(効率は劣ります)。

これにより、配列内の完全なオブジェクトの一意の配列が返されます。

let productIds = data.map(d => { 
   return JSON.stringify({ 
      id    : d.sku.product.productId,
      name  : d.sku.product.name,
      price : `${d.sku.product.price.currency} ${(d.sku.product.price.gross / d.sku.product.price.divisor).toFixed(2)}`
   })
})
productIds = [ ...new Set(productIds)].map(d => JSON.parse(d))```
0
Conor Reid

TypeScriptで 強く型付けされた問い合わせ可能なコレクション を提供するライブラリがあります。

コレクションは以下のとおりです。

  • リスト
  • 辞書

このライブラリは ts-generic-collections と呼ばれます。

GitHubのソースコード:

https://github.com/VeritasSoftware/ts-generic-collections

以下のような明確な値を得ることができます

  it('distinct', () => {
    let numbers: number[] = [1, 2, 3, 1, 3];
    let list = new List(numbers);

    let distinct = list.distinct(new EqualityComparer());

    expect(distinct.length == 3);
    expect(distinct.elementAt(0) == 1);
    expect(distinct.elementAt(1) == 2);
    expect(distinct.elementAt(2) == 3);
  });

  class EqualityComparer implements IEqualityComparer<number> {
    equals(x: number, y: number) : boolean {
      return x == y;
    }
  }
0
John

新しいEcma機能を使うのは素晴らしいことですが、すべてのユーザーがまだそれらを利用できるわけではありません。

次のコードは、 distinct という名前の新しい関数をGlobal Arrayオブジェクトに追加します。オブジェクトの配列の個別の値を取得しようとしている場合は、値の名前を渡してそのタイプの個別の値を取得できます。

Array.prototype.distinct = function(item){   var results = [];
for (var i = 0, l = this.length; i < l; i++)
    if (!item){
        if (results.indexOf(this[i]) === -1)
            results.Push(this[i]);
        } else {
        if (results.indexOf(this[i][item]) === -1)
            results.Push(this[i][item]);
    }
return results;};

デモはCodePenで 私の投稿 をチェックしてください。

0
Hasan Savran

KotlinのArray.distinctBy {}...のような一般的なケースのために、私はTypeScriptで自分で書きました。

function distinctBy<T, U extends string | number>(array: T[], mapFn: (el: T) => U) {
  const uniqueKeys = new Set(array.map(mapFn));
  return array.filter((el) => uniqueKeys.has(mapFn(el)));
}

もちろん、Uはハッシュ可能です。オブジェクトの場合、 https://www.npmjs.com/package/es6-json-stable-stringify が必要になる場合があります

0
Polv

私の下のコードは重複した年齢を持たない新しい配列と同様に年齢のユニークな配列を示します

var data = [
  {"name": "Joe", "age": 17}, 
  {"name": "Bob", "age": 17}, 
  {"name": "Carl", "age": 35}
];

var unique = [];
var tempArr = [];
data.forEach((value, index) => {
    if (unique.indexOf(value.age) === -1) {
        unique.Push(value.age);
    } else {
        tempArr.Push(index);    
    }
});
tempArr.reverse();
tempArr.forEach(ele => {
    data.splice(ele, 1);
});
console.log('Unique Ages', unique);
console.log('Unique Array', data);```
0
Shridhar Sagari