Ordnen Sie ein Array gleichzeitig zu und filtern Sie es

155

Ich habe ein Array von Objekten, über die ich iterieren möchte, um ein neues gefiltertes Array zu erstellen. Aber ich muss auch einige der Objekte aus dem neuen Array herausfiltern, abhängig von einem Parameter. Ich versuche das:

function renderOptions(options) {
    return options.map(function (option) {
        if (!option.assigned) {
            return (someNewObject);
        }
    });   
}

Ist das ein guter Ansatz? Gibt es eine bessere Methode? Ich bin offen für jede Bibliothek wie lodash.

Daniel Calderon Mori
quelle
Was ist mit einem "object.keys" -Ansatz? developer.mozilla.org/fr/docs/Web/JavaScript/Reference/…
Julo0sS
3
.reduce()ist definitiv schneller als ein, .filter(...).map(...)was ich anderswo vorgeschlagen habe. Ich habe einen JSPerf-Test eingerichtet, um stackoverflow.com/a/47877054/2379922
Justin L.

Antworten:

218

Sie sollten dafür verwenden Array.reduce.

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

var reduced = options.reduce(function(filtered, option) {
  if (option.assigned) {
     var someNewValue = { name: option.name, newProperty: 'Foo' }
     filtered.push(someNewValue);
  }
  return filtered;
}, []);

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>


Alternativ kann der Reduzierer eine reine Funktion sein

var reduced = options.reduce(function(result, option) {
  if (option.assigned) {
    return result.concat({
      name: option.name,
      newProperty: 'Foo'
    });
  }
  return result;
}, []);
thefourtheye
quelle
2
Für mich ist das erste Argument filteredein Objekt. so filtered.pushist für mich undefiniert.
Rahmathullah M
4
Ich hatte auch ein Problem damit filtered, ein Objekt zu sein. Dies lag daran, dass ich den "Anfangswert" - das leere Array ( []) nach der Reduktionsfunktion - nicht übergeben habe. zB falsch var reduced = options.reduce(function(filtered, option) { ... }); richtig var reduced = options.reduce(function(filtered, option) { ... }, []);
Jon Higgins
Es gibt hier keinen Grund reduce, den Sie genauso gut verwenden könnten, forEachda Sie bei jeder Iteration das Array ändern filteredund es daher nicht rein funktional ist. Es wäre Ereignis lesbarer und kompakter.
Marko
1
@ Marko Ich habe auch einen reinen Reduzierer eingeführt. PTAL.
thefourtheye
Ja, das ist jetzt rein. +1 für deine Mühe. Nicht, dass ich ein reiner Verfechter der funktionalen Programmierung wäre, weit davon entfernt, ich konnte den Punkt einfach nicht erkennen :) Aber tatsächlich habe ich Ihren Trick verwendet, nachdem ich ihn gesehen habe, weil er in der gegebenen Situation so praktisch war. Aber für diese Aufgabe möchten Sie vielleicht einen Blick auf die flatMap-Funktion werfen. Ich denke, sie wurde nach Ihrer Antwort zum Standard (auch aus diesem Grund wird sie von einigen Browsern möglicherweise nicht unterstützt). Es sollte leistungsfähiger sein, da das Verketten von Arrays wie diesem die O (n ^ 2) -Aufgabe aus der O (n) -Aufgabe macht.
Marko
43

Verwenden Sie reduzieren, Luke!

function renderOptions(options) {
    return options.reduce(function (res, option) {
        if (!option.assigned) {
            res.push(someNewObject);
        }
        return res;
    }, []);   
}
Zuker
quelle
29

Array.prototype.flatMap ist seit 2019 eine gute Option.

options.flatMap(o => o.assigned ? [o.name] : []);

Von der oben verlinkten MDN-Seite:

flatMapkann verwendet werden, um Elemente während einer Karte hinzuzufügen und zu entfernen (die Anzahl der Elemente ändern). Mit anderen Worten, Sie können viele Elemente vielen Elementen zuordnen (indem Sie jedes Eingabeelement separat behandeln) und nicht immer eins zu eins. In diesem Sinne funktioniert es wie das Gegenteil von Filter. Geben Sie einfach ein Array mit 1 Elementen zurück, um das Element beizubehalten, ein Array mit mehreren Elementen, um Elemente hinzuzufügen, oder ein Array mit 0 Elementen, um das Element zu entfernen.

Trevor Dixon
quelle
4
Tolle Lösung! Die Leser bedenken jedoch, dass dies flatMapkürzlich eingeführt wurde und die Browserunterstützung begrenzt ist . Möglicherweise möchten Sie die offiziellen Polyfill / Shims verwenden: flatMap und flat .
Arel
24

Mit ES6 können Sie es sehr kurz machen:

options.filter(opt => !opt.assigned).map(opt => someNewObject)

Natividad Lara Diaz
quelle
25
Dies führt zu zwei Schleifen, je nachdem, welche Filterrückgaben noch Speicher verbrauchen können.
Hogan
1
@ Hogan, aber dies ist die richtige Antwort (möglicherweise nicht die optimale Lösung) auf die ursprüngliche Anfrage
Vikramvi
1
@ Vikramvi Vielen Dank für Ihre Nachricht. Die Sache ist, wir können die gleichen Dinge auf unzählige Arten erreichen und ich würde die beste bevorzugen.
Hogan
10

Eine Zeile reducemit ausgefallener ES6- Spread-Syntax ist da!

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

const filtered = options
  .reduce((result, {name, assigned}) => [...result, ...assigned ? [name] : []], []);

console.log(filtered);

Maxim Kuzmin
quelle
1
Wirklich schön @Maxim! Ich stimme dem zu! Aber ... auf jedes hinzugefügte Element muss es alle Elemente verteilen in result... ist wie eine filter(assigned).map(name)Lösung
0zkr PM
Schön. Ich brauchte eine Sekunde, um zu erkennen, was los war ...assigned ? [name] : []- es ist vielleicht besser lesbar als...(assigned ? [name] : [])
johansenja
5

Ich würde einen Kommentar abgeben, aber ich habe nicht den erforderlichen Ruf. Eine kleine Verbesserung der ansonsten sehr guten Antwort von Maxim Kuzmin, um sie effizienter zu machen:

const options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

const filtered = options
  .reduce((result, { name, assigned }) => assigned ? result.concat(name) : result, []);

console.log(filtered);

Erläuterung

Anstatt das gesamte Ergebnis für jede Iteration immer wieder zu verteilen, hängen wir nur an das Array an und nur dann, wenn tatsächlich ein Wert eingefügt werden muss.

matsve
quelle
3

Verwenden Sie Array.prototy.filter selbst

function renderOptions(options) {
    return options.filter(function(option){
        return !option.assigned;
    }).map(function (option) {
        return (someNewObject);
    });   
}
AmmarCSE
quelle
2

Ist es nicht irgendwann einfacher (oder genauso einfach), a zu verwenden? forEach

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

var reduced = []
options.forEach(function(option) {
  if (option.assigned) {
     var someNewValue = { name: option.name, newProperty: 'Foo' }
     reduced.push(someNewValue);
  }
});

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>

Es wäre jedoch schön, wenn es eine malter()oder fap()Funktion gäbe , die die mapund filterFunktionen kombiniert . Es würde wie ein Filter funktionieren, außer dass anstelle von true oder false ein beliebiges Objekt oder ein null / undefined zurückgegeben wird.

Daniel
quelle
Vielleicht möchten Sie Ihre Memes überprüfen ... ;-)
Arel
2

Ich habe die Antworten mit folgenden Punkten optimiert:

  1. Umschreiben if (cond) { stmt; }alscond && stmt;
  2. Verwenden Sie ES6- Pfeilfunktionen

Ich werde zwei Lösungen vorstellen, eine mit forEach , die andere mit reduct :

Lösung 1: Verwenden von forEach

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];
var reduced = []
options.forEach(o => {
  o.assigned && reduced.push( { name: o.name, newProperty: 'Foo' } );
} );
console.log(reduced);

Lösung 2: Verwenden Sie Reduzieren

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];
var reduced = options.reduce((a, o) => {
  o.assigned && a.push( { name: o.name, newProperty: 'Foo' } );
  return a;
}, [ ] );
console.log(reduced);

Ich überlasse es Ihnen, zu entscheiden, für welche Lösung Sie sich entscheiden.

Stephen Quan
quelle
1
Warum um alles in der Welt würden Sie verwenden cond && stmt;? Dies ist viel schwieriger zu lesen und bietet überhaupt keine Vorteile.
jlh
1

Mit reduzieren können Sie dies in einer Array.prototype-Funktion tun. Dadurch werden alle geraden Zahlen aus einem Array abgerufen.

var arr = [1,2,3,4,5,6,7,8];

var brr = arr.reduce((c, n) => {
  if (n % 2 !== 0) {
    return c;
  }
  c.push(n);
  return c;
}, []);

document.getElementById('mypre').innerHTML = brr.toString();
<h1>Get all even numbers</h1>
<pre id="mypre"> </pre>

Sie können dieselbe Methode verwenden und sie für Ihre Objekte wie folgt verallgemeinern.

var arr = options.reduce(function(c,n){
  if(somecondition) {return c;}
  c.push(n);
  return c;
}, []);

arr enthält nun die gefilterten Objekte.

Bhargav Ponnapalli
quelle
0

Direkte Verwendung von .reduce kann schwer zu lesen sein, daher würde ich empfehlen, eine Funktion zu erstellen, die den Reduzierer für Sie generiert:

function mapfilter(mapper) {
  return (acc, val) => {
    const mapped = mapper(val);
    if (mapped !== false)
      acc.push(mapped);
    return acc;
  };
}

Verwenden Sie es so:

const words = "Map and filter an array #javascript #arrays";
const tags = words.split(' ')
  .reduce(mapfilter(word => word.startsWith('#') && word.slice(1)), []);
console.log(tags);  // ['javascript', 'arrays'];
Quelklef
quelle