ebody: Javascript includes einzelne Array Werte

problematische Seite

Hallo,

mein Ziel ist es ein Array nach verschiedenen Kriterien zu filtern. Ein einzelner Filter sieht z.B. so aus:

let dataset = arrMovies.filter(movie => movie.Genre.includes('Abenteuer') === true);

In Genre soll aber nicht nur nach einem Begriff gesucht werden, sondern nach verschiedenen wie "Abenteuer" oder "Action" oder ...

Dafür würde ich gerne ein Array nutzen, welches diese verschiedenen Begriffe enthält: arrFilter.Genre

const arrFilter = {
  Genre: ['Abenteuer','Action'],
  Tags: ['New York','Favorit','Top10']
};

includes müsste jeden einzelnen Wert aus dem Array arrFilter.Genre prüfen. So prüft includes() aber keinen String,...

for (let filter in arrFilter) {
  dataset = arrMovies.filter(movie => movie[filter].includes(arrFilter[filter]) === true);
}

...sondern ein Array:

dataset = arrMovies.filter(movie => movie[filter].includes(['Abenteuer','Action']) === true);

Gibt es eine Möglichkeit die einzelnen Werte aus arrFilter.Genre ohne Schleife zu prüfen?

Gruß ebody

  1. problematische Seite

    Gibt es eine Möglichkeit die einzelnen Werte aus arrFilter.Genre ohne Schleife zu prüfen?

    Du suchst also eine Überschneidung, wie wäre es damit:

    function intersection(xs, ys) {
      return xs.filter(x => ys.includes(x))
    }
    

    Und dann prüfen ob die Schnittmenge nicht leer ist:

    intersection(xs, ys).length > 0
    
  2. problematische Seite

    Hallo ebody,

    (1) Ein Konstrukt vom Typ if (something === true) lässt sich als if (something) verkürzen, wenn man weiß, dass something definitiv ein boolescher Wert ist. Beim Ergebnis von includes ist das der Fall.

    Eine Abfrage auf === true brauchst Du nur, wenn Du außer true auch truthy Werte (Siehe unser Wiki, "Was ist Wahrheit") bekommen könntest und diese nicht als true akzeptieren willst.

    (2) Es gibt die so genannten Quantorfunktionen für Arrays, die ermitteln, ob ein Callback für mindestens ein Array-Element (some) oder für alle Array-Elemente (every) zutrifft. Die kannst Du statt der zusätzlichen Schleife nutzen, landest dann aber bei mehrfach geschachtelten Pfeilfunktionen. Für Mathematiker: some entspricht $$\exists$$ und every implementiert $$\forall$$.

    (3) Ich würde Dir definitiv empfehlen, für den filter-Callback eine Funktion zu verwenden. Und zwar so, dass Du über eine Helfer-Funktion gehst, die die gesuchten Genres als Parameter erhält und eine Funktion zurückgibt, die als Callback genutzt wird. Die übergebenen Genres stehen dieser Callbackfunktion dann als Teil der gebildeten Closure zur Verfügung.

    Was unklar ist: Machst Du eine "UND" oder eine "ODER" Suche? Wenn ich Dir [ "Komödie", "Horror" ] vorgebe - suchst Du dann alle Horrorkomödien oder alles, was Horror oder Komödie ist? Anders gefragt: Brauchst Du some oder every? Aber man kann ja zwei Callback-Generatoren bereitstellen:

    function createFilterForAll(...genresToFind) {
       // User hat Array übergeben - ... legt ein weiteres Array drumherum
       if (Array.isArray(genresToFind[0]))
          genresToFind = genresToFind[0];
       
       return movie => genresToFind.every(genre => movie.genres.includes(genre));
    }
    function createFilterForSome(...genresToFind) {
       // User hat Array übergeben - ... legt ein weiteres Array drumherum
       if (Array.isArray(genresToFind[0]))
          genresToFind = genresToFind[0];
       
       return movie => genresToFind.some(genre => movie.genres.includes(genre));
    }
    
    movies.filter(createFilterForAll("Horror", "Komödie"));
    movies.filter(createFilterForSome("Horror", "Komödie"));
    

    Die isArray Abfrage ist drin, falls jemand filterForAll(["Horror", "Komödie"]) aufruft - in dem Fall macht der Rest-Parameter ...genresToFind ein weiteres Array drumherum, das entfernt werden muss.

    "every" und "some" kennt sogar schon der IE, aber Rest-Parameter nicht. Wenn Du auch im IE funktionieren willst, musst Du statt Rest-Parametern das arguments Objekt verwenden (was aber kein Array ist, d.h. Du musst every und some als Schleife ausprogrammieren) oder einfach definieren, dass der User ein Array übergeben muss. Letzteres macht auch die isArray Prüfung obsolet.

    Einen solchen Filter kannst Du - wenn das not tut - auch vorab erzeugen und speichern.

    Mich persönlich würde jetzt noch triggern, dass die beiden Funktionen sich lediglich darin unterscheiden, ob sie mit some oder every suchen, und ich würde das vermutlich noch abstrahieren. Und in ein Objekt kapseln. Das wird dann aber schwerer verständlich. Aussehen täte es so:

    const createFilter = (function() {
      function createFilter(genresToFind, quantor) {
        // Durch ... kann ein Array of Array entstehen
        if (Array.isArray(genresToFind[0]))
          genresToFind = genresToFind[0];
       
        return movie => quantor.call(
                          genresToFind, 
                          movie => movie.genres.includes(genre));
      }
      return { 
        all(...genresToFind) {
          return createFilter(genresToFind, Array.prototype.every);
        },
        some(...genresToFind) {
          return createFilter(genresToFind, Array.prototype.some);
        }
      }
    })();
    
    movies.filter(createFilter.all("Horror", "Komödie"));
    movies.filter(createFilter.some("Horror", "Komödie"));
    

    Die Kurzschreibweise für Methoden in Objektliteralen kennt der IE nicht, dort bräuchtest Du

      return { 
        all: function() {
          return createFilter(arguments, Array.prototype.every);
        },
        some: function() {
          return createFilter(arguments, Array.prototype.some);
        }
    

    Rolf

    --
    sumpsi - posui - obstruxi
    1. problematische Seite

      @@Rolf B

      Wenn Du auch im IE funktionieren willst

      Sprachlich kreativ.

      Und sind wir nicht schon an dem Punkt, wo die einzigen IE-Zugriffe in den Serverlogs man selber ist, wenn man testet, wie’s die eigene Seite im IE tut?

      (Abgesehen von Bots, die sich als IE ausgeben.)

      😷 LLAP

      --
      “When I was 5 years old, my mother always told me that happiness was the key to life. When I went to school, they asked me what I wanted to be when I grew up. I wrote down ‘happy.’ They told me I didn’t understand the assignment, and I told them they didn’t understand life.” —John Lennon
      1. problematische Seite

        Hallo Gunnar,

        ein Jahr und eine Woche noch. Dann ist der Dodo auch laut Microsoft tot.

        https://blogs.windows.com/windowsexperience/2021/05/19/the-future-of-internet-explorer-on-windows-10-is-in-microsoft-edge/

        Aber noch nicht begraben. Er lebt noch mindestens 7 weitere Jahre als Zombie im Edge weiter - als IE-Modus, den man per Enterprise-Policy für bestimmte Sites festlegen kann. Inclusive Trident Engine und aller Alt-Quirks.

        Und vermutlich gibt's nächstes Jahr immer noch Unternehmen, die ihre Infrastruktur so immobil verwalten, dass sie 3 Jahre Nachlauf brauchen und den Edge nicht ans Fliegen bekommen. Vermutlich, weil sie kritische, selbstentwickelte Software als Java 8 Applets entwickelt haben und alle Leute, die damit was anfangen können, in Rente sind. Aber die sind dann selbst schuld...

        Rolf

        --
        sumpsi - posui - obstruxi
        1. problematische Seite

          Hi,

          Und vermutlich gibt's nächstes Jahr immer noch Unternehmen, die ihre Infrastruktur so immobil verwalten, dass sie 3 Jahre Nachlauf brauchen und den Edge nicht ans Fliegen bekommen. Vermutlich, weil sie kritische, selbstentwickelte Software als Java 8 Applets entwickelt haben

          Doch nicht so modern. Eher als HTA ... 😉

          Ein Java-Applet ist relativ einfach in eine Standalone-Java-Application umzubauen. Selber schon gemacht. Vor Jahren ...

          cu,
          Andreas a/k/a MudGuard

      2. problematische Seite

        Hi there,

        Und sind wir nicht schon an dem Punkt, wo die einzigen IE-Zugriffe in den Serverlogs man selber ist, wenn man testet, wie’s die eigene Seite im IE tut?

        Schön wär's. Ich vor ca. 2 Monaten als Auftragsarbeit eine Webapplikation auf die Menschheit losgelassen, die vor "offizieller" Inbetriebnahme von ausgesuchten Kunden des beauftragenden Unternehmens getestet wurde.

        Von einigen dieser Testkunden kamen Fehlermeldungen, die ich einfach nicht nachvollziehen konnte; mit keinem Browser, den ich probiert habe. Erst als ich auf einem Surface Pro das Programm unter einem IE 11 aufgerufen habe, konnte ich die Probleme dieser Opfer nachvollziehen. Ich hab das dann halt (auf Kundenwunsch und bezahlterweise) mit einer "Vorschaltseite" gelöst, so nach dem Motto: "Komm wieder, wenn Du Dir einen anständigen Browser besorgt hast"...however, es gibt sie leider noch, die IE-User...

        1. problematische Seite

          Hallo klawischnigg,

          na immerhin leide ich nicht alleine 😉

          Rolf

          --
          sumpsi - posui - obstruxi
          1. problematische Seite

            Hallo klawischnigg,

            na immerhin leide ich nicht alleine 😉

            Genau! Geteiltes Leid und so, ich fühl' mich schon etwas besser...😉

        2. problematische Seite

          Hallo,

          ... however, es gibt sie leider noch, die IE-User...

          also bei mir sind es weniger 1%, im März waren es noch 1,5%.

          Ich messe über die Logfiles, wieviele Browser ein Javascript einbinden, und wieviele einen Details-Polyfill. In der kleinen Anzahl sind also auch noch alte Edge mit dabei.

          Gruß
          Jürgen

          1. problematische Seite

            Hi there,

            ... however, es gibt sie leider noch, die IE-User...

            also bei mir sind es weniger 1%, im März waren es noch 1,5%.

            Ja, ich hab' die Erfahrung gemacht, daß das stark branchenabhängig ist. In technik-affinen Sparten verwendet niemand mehr den IE, seit Jahrzehnten nicht mehr.

            Banken, Juristen und ähnlich merkbefreite Erwerbszweige nehmen halt einfach was da ist, und das war eine sehr lange Zeit der Internet-Explorer und wenn die Kiste, auf der die Software läuft, recht alt ist, dann wird der immer noch verwendet.

            Zudem, was mir noch aufgefallen ist, Browserverwendung ist eine recht länderspezifische Sache. Ich mach relativ viel für Schweizer Pensionskassen, und dort ist der IE durchaus noch häufiger in Gebrauch...

            1. problematische Seite

              Hallo klawischnigg,

              Banken, Juristen und ähnlich merkbefreite Erwerbszweige nehmen halt einfach was da ist,

              Vergiss die Versicherer nicht...

              Aber "was da ist" ist nicht der Grund. Sondern der Umstand, dass der IE tatsächlich mal irgendwann die bessere Alternative war. Und dann hat man entschieden: ok, den nehmen wir.

              Tjaaaaa. Und dabei ist's dann geblieben. Die Finanzbranche ist konservativ. 20 Jahre sind rum wie nix. Hypotheken und Lebensversicherungen sind nicht alle 3 Jahre neu.

              So'n neumodisches Zeugs wie Opera, Chrome oder Firefox, nö, brauchen wir nicht. Und so wird aus der Branche eine Brache.

              Rolf

              --
              sumpsi - posui - obstruxi
              1. problematische Seite

                Hi there,

                Banken, Juristen und ähnlich merkbefreite Erwerbszweige nehmen halt einfach was da ist,

                Vergiss die Versicherer nicht...

                Aber "was da ist" ist nicht der Grund. Sondern der Umstand, dass der IE tatsächlich mal irgendwann die bessere Alternative war. Und dann hat man entschieden: ok, den nehmen wir.

                Ja, das war aber eine relativ kurze Zeit, ich würde sagen, so um die Jahrtausendwende, da ist der Netscape-Navigator nur mehr ein Schatten seiner selbst gewesen und der Firefox war noch nicht veröffentlicht...

                1. problematische Seite

                  Hallo klawischnigg,

                  ich würde sagen, so um die Jahrtausendwende

                  Passt. Und genau das war die Zeit, wo der Aufbruch Richtung Web begann. Also, bei den Versicherern. Anderswo schon früher 😉

                  Rolf

                  --
                  sumpsi - posui - obstruxi
            2. problematische Seite

              Ja, ich hab' die Erfahrung gemacht, daß das stark branchenabhängig ist. In technik-affinen Sparten verwendet niemand mehr den IE, seit Jahrzehnten nicht mehr.

              Seit Jahrzehnten? Also mindestens seit 2001 nicht mehr? Ich habe Zweifel ;-)

              1. problematische Seite

                Ja, ich hab' die Erfahrung gemacht, daß das stark branchenabhängig ist. In technik-affinen Sparten verwendet niemand mehr den IE, seit Jahrzehnten nicht mehr.

                Seit Jahrzehnten? Also mindestens seit 2001 nicht mehr? Ich habe Zweifel ;-)

                Na gut. Ich denke, die erste brauchbare Alternative hat's 2003 oder 2004 gegeben, da war die Erstveröffentlichung des Firefox...

    2. problematische Seite

      Mich persönlich würde jetzt noch triggern, dass die beiden Funktionen sich lediglich darin unterscheiden, ob sie mit some oder every suchen, und ich würde das vermutlich noch abstrahieren. Und in ein Objekt kapseln. Das wird dann aber schwerer verständlich. Aussehen täte es so:

      Deine Grundidee mit some und every zu arbeiten gefällt mir besser als meine erste Eingebung mit der Schnittmenge. Ich glaube deinen Ansatz kann man noch noch stark vereinfachen. Ich würde bspw. auf die Überladung verzichten und den dynamischen Array-Test rausnehmen. Dadruch verliert man nichts, weil man die Funktion ja immernoch mit f([arg1, arg2,]) anstatt f(arg1, arg2) aufrufen kann. Außerdem würde ich nicht über den Quantor abstrahieren, sondern einen Abschluss (Closure) über das gemeinsame Prädikat bilden. Dann bleibt folgendes ürbig:

      
      function all(genresToFind) {
        return movie => genresToFind.every(isIncludedIn(movie.genres))
      }
      
      function some(genresToFind) {
        return movie => genresToFind.some(isIncludedIn(movie.genres))
      }
      
      function isIncludedIn (movieGenres) {
        return genre => movieGenres.includes(genre)
      }
      
      1. problematische Seite

        Hallo 1unitedpower,

        es ist schon spannend, welche Minimalkonstrukte herauskommen können wenn man eine Weile am Code herumfaktoriert 😀

        Durch das Entfernen des Arraytests wird natürlich alles einfacher, der sollte ja auch vor allem vor einer "Fehlbedienung" schützen.

        Vor allem verlangt dein Code einen weiteren Abstraktionsschritt vom Leser. Die Funktion "isIncludedIn" ist ja nicht der Callback, sondern generiert den erforderlichen Callback. Natürlich liest sich genresToFind.every(isIncludedIn(movie.genres)) sehr flüssig. Aber man muss dabei im Hinterkopf haben, dass every einen Callback erwartet und isIncludedIn diesen liefert - und nicht selbst der Callback ist.

        Für eingefleischte funktionale Programmierer mag das eine ganz natürliche Vorgehensweise sein. Ich musste erstmal einen Moment nachdenken, was denn da nun eigentlich passiert. Und dabei habe ich das Konstrukt ja selbst angezettelt. Mein Stolperstein war tatsächlich der Funktionsname. Er suggeriert ein anderes Verhalten, als die Funktion tatsächlich zeigt. Vom Laufzeitverhalten dürften dein und mein Ansatz gleich sein. Es sei denn, dass der Umstand schädlich ist, dass Du pro Movie eine neue Closure erzeugst - ich tue das nicht. Ich erzeuge nur einmal zu Beginn eine.

        Nicht, dass ich gerade die bessere Idee hätte. Aber hast Du noch eine Idee, die Factory-Charakteristik von isIncludedIn besser im Namen darzustellen? Und eigentlich müsste man es auch noch so bauen, dass der Suchalgorithmus nicht wissen muss, dass er in den durchsuchten Objekten die Matches im genres Property findet, das müsste man injizieren.

        Und es muss irgendwie ge-namespace-d sein, denn Funktionen wie all und some sind viel zu generisch um sie im globalen Namespace rumfliegen zu lassen. Vielleicht doch besser eine Klasse mit einem fluid interface?

        new TopicSearcher()
           .search(movie => movie.genres)
           .forAll("Horror", "Komödie")     // oder: .forAny("Foo", "Bar")
           .in(movies)
        

        search, all, any und in sind Methoden des von new TopicSearcher erzeugten Objekts. Mit search, forAll und forAny konfiguriert man die Suche, und in führt sie durch.

        Sehr gewöhnungsbedürftig beim ersten Anblick, aber ich mag sowas. Ich finde, für den Nutzer liest sich das wie ein Satz. Um es zu bauen, braucht man etwas mehr Schmalz. Letztlich passiert aber nicht viel anderes als bei den oben diskutierten Closures.

        Und ja, man könnte statt dessen auch einfach zwei Funktionen schreiben, die ohne Objekte das Gleiche tun. Aber hey, es ist 2021 und nicht 1971 😂

        Rolf

        --
        sumpsi - posui - obstruxi
        1. problematische Seite

          Nicht, dass ich gerade die bessere Idee hätte. Aber hast Du noch eine Idee, die Factory-Charakteristik von isIncludedIn besser im Namen darzustellen?

          Leider nein. Ich bin fürchterlich schlecht darin Funktionen sinnvolle Namen zu geben. Ich versuche Prädikaten, also Funktionen, die schlussendlich boolsche Wert zurückgeben, mit Verben wie is und has zu prefixen. Das gibt zumindest etwas Auskunft über den Rückgabetypen, aber natürlich nicht über die erwarteten Parameter und deren Reihenfolge. Persönlich schreibe ich manchmal eine Pseudo-Typ-Annotation in Anlehnung an Haskells Typsprache als Kommentar an die Funktionen. Im Falle von isIncludedIn zum Beispiel:

          isIncludedIn :: Eq a => [a] -> a -> Bool
          

          Der Kommentar gibt mir ein paar Auskünfte: Die Funktion erwartet als ersten Parameter eine Liste [a] und als zweiten Parameter einen Wert vom Typ a und liefert mir schlussendlich einen boolschen Wert. Außerdem soll da scheinbar etwas verlichen werden, das sagt mir die Vorbedingung Eq a. Das hilft aber natürlich nur derjenigen, die mit dem Haskell-Typsystem vertraut ist, für alle anderen ist das nur Kauderwelsch. Das TypeScript Typ-System ist leider sehr langatmig und deshalb weniger geeignet, um diese Informationen zu transportieren.

          Ich habe dann gerade mal auf Hoogle, einer Suchmaschine für Haskell-Funktionen, nach diesem Typen gesucht und da werden unter anderem folgende Namen vorgeschlagen:

          • has
          • hasElem
          • elem

          Also da ist auch nichts Brauchbares bei. Es scheint etwas dran zu sein, an dem Zitat:

          There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors.

          1. problematische Seite

            Hallo 1unitedpower,

            ich habe meine Klasse mit fluid interface nun mal in jsFiddle hinterlegt.

            https://jsfiddle.net/Rolf_b/gzwn7ja8/

            Wie für eine Library üblich ist der Code mit Errorhandling zugestopft...

            Rolf

            --
            sumpsi - posui - obstruxi
            1. problematische Seite

              Tach!

              ich habe meine Klasse mit fluid interface nun mal in jsFiddle hinterlegt.

              Alle let können durch const ersetzt werden, weil sich die Werte nicht ändern.

              Der Javascript-Code ist nicht besonders verwenderfreundlich so ganz ohne Dokumentation. Man sieht nicht, was da für Parameter erwartet werden. Ich hab das ganze mal mit Typescript-Typen versehen, und es hat eine Weile gedauert, bis ich die korrekt herausgefunden habe.

              Deine Methode mit der Haskel-Annotation mag eine große Hilfe für dich sein, aber sie hilft anderen Verwendern nur bedingt. Vor allem kann keine IDE damit eine Analyse auführen und auf falsche Verwendung hinweisen. Da sind die Typescript-Typen wesentlich hilfreicher.

              dedlfix.

              1. problematische Seite

                Hallo dedlfix,

                und es hat eine Weile gedauert, bis ich die korrekt herausgefunden habe

                const, let, tomayto, tomahto, pff... 😉

                Aber ernst jetzt: Wenn ich KONSTANTEN für mein Programm definiere, verwende ich const. Wenn ich Variablen definiere, egal wie oft ich sie setzen will, verwende ich let. Ich denke doch nicht bei jeder Variablen nach, ob ich sie einmal oder mehrmals setzen will. Möglicherweise kann der JS JIT effizienteren Code erzeugen, wenn ich const schreibe. Aber das soll er selbst rausfinden. Kann er bestimmt auch.

                Da meine Typescript Kenntnisse weniger als rudimentär sind, habe ich mir den Versuch erspart, das Ganze in idiomatisch korrektem Typescript formulieren zu wollen. Es ist ja auch keine fertige Lib (sonst wär's bei Github), sondern eine Idee, wie man die Lösung konstruieren könnte. Wie man es verwendet sieht man am Beispiel. Das machen die meisten Githuber (oder Githupen?) auch nicht anders.

                TypeScript hat natürlich den Vorteil, dass man es auf ES5 oder ES3 hinuntercompiliert. Mir fehlt aber immer noch die Motivation, es zu lernen und das gelernte aktiv zu halten.

                Und mit dem Haskell-Hinweis meintest Du 1UP?

                Rolf

                --
                sumpsi - posui - obstruxi
                1. problematische Seite

                  Tach!

                  Aber ernst jetzt: Wenn ich KONSTANTEN für mein Programm definiere, verwende ich const. Wenn ich Variablen definiere, egal wie oft ich sie setzen will, verwende ich let. Ich denke doch nicht bei jeder Variablen nach, ob ich sie einmal oder mehrmals setzen will.

                  Die meiste Zeit werden sie einmal beschrieben verwendet. const sollte also dein Standardwert sein. Nur in Ausnahmen braucht man let. Und das sagt dir dann die IDE schon, wenn du let brauchst, falls du dir da das Denken ersparen möchtest.

                  Möglicherweise kann der JS JIT effizienteren Code erzeugen, wenn ich const schreibe. Aber das soll er selbst rausfinden. Kann er bestimmt auch.

                  Betreiben die JIT-Compiler eine derartige Code-Analyse?

                  TypeScript hat natürlich den Vorteil, dass man es auf ES5 oder ES3 hinuntercompiliert. Mir fehlt aber immer noch die Motivation, es zu lernen und das gelernte aktiv zu halten.

                  Die statische Typanalyse hilft enorm beim Entwickeln von Javascript-Anwendungen, weil die IDE sehr genau ermitteln kann, wie der Code fortgesetzt werden kann, und damit nur die sinnvollen Vorschläge auflistet. Man spart sich damit viel Dokumentation-Nachschlagen.

                  Und mit dem Haskell-Hinweis meintest Du 1UP?

                  Ach, war das seine Vorgehensweise? Hab ich wohl nicht darauf geachtet, wer das Posting verfasst hat. Dann muss er sich eben diese Feder an den Hut stecken.

                  dedlfix.

                  1. problematische Seite

                    Und mit dem Haskell-Hinweis meintest Du 1UP?

                    Ach, war das seine Vorgehensweise? Hab ich wohl nicht darauf geachtet, wer das Posting verfasst hat. Dann muss er sich eben diese Feder an den Hut stecken.

                    Die Feder heftet schon an meinem Hut, wenn ich mich kurz selbst zitieren darf:

                    Das hilft aber natürlich nur derjenigen, die mit dem Haskell-Typsystem vertraut ist, für alle anderen ist das nur Kauderwelsch. Das TypeScript Typsystem ist leider sehr langatmig und deshalb weniger geeignet, um diese Informationen zu transportieren.

                    Das sollte aber kein Votum gegen den Einsatz von TypeScript sein, ganz im Gegenteil. Ein statisches Typsystem erfüllt für mich zwei wichtige Aufgaben: Es vermeidet Laufzeit-Fehler, und es dokumentiert den Code. Eine Typ-Annotation gibt mir Aufschluss über die erwarteten Parameter und Rückgabewerte einer Funktion, ohne dass ich den Funktionskörper lesen muss. Die erste Aufgabe erfüllt TypeScript m.M.n. sehr gut, aber die Typ-Annotationen sind recht langatmig und es fällt mir relativ schwer sie gedanklich zu parsen und mir ihren Informationsgehalt zu erschließen. Deswegen benutze ich manchmal eben diese Pseudo-Typ-Annotation zusätzlich zu TypeScript. Mir ist durchaus bewusst, dass nicht jede:r von dieser Dokumentation profitiert. Aber es scheint in JavaScript eine wachsende Functional-Programming-Community zu geben, die dem durchaus etwas abgewinnen kann. Siehe bspw. fantasyland und sanctuary.

            2. problematische Seite

              Hallo 1unitedpower,

              ich habe meine Klasse mit fluid interface nun mal in jsFiddle hinterlegt.

              https://jsfiddle.net/Rolf_b/gzwn7ja8/

              Das habe ich für meine Idee auch mal gemacht und noch ein bisschen vereinfacht:

              https://stackblitz.com/edit/typescript-filter-some-and-all?file=index.ts

              Wie für eine Library üblich ist der Code mit Errorhandling zugestopft...

              Viele von der Tests kann man auch über das TypeScript Typsystem abfangen. Aber ich will damit nicht nerven, du kennst ja die Vor- und Nachteile von statischen und dynamischen Errorhandling ja auch.

    3. problematische Seite

      Hallo und vielen Dank für die Hilfe. Das muss ich erstmal in ruhe nachvollziehen, um es zu verstehen.

      Wenn z.B. die Filter Genre: ['Abenteuer','Action'] gewählt wurden, soll jeder Film ausgewählt werden, der in Genre einen dieser Begriffe enthält, also brauche ich some, wenn ich es richtig verstanden habe.

      Bei diesem Filter würde dieser Film ausgewählt, da Genre "Abenteuer" enthält, auch wenn er nicht "Action" enthält:

      {
       Titel: 'Pirates of the Caribbean Salazars Rache',
       Genre: 'Abenteuer,Fantasy',
       Tags: 'Piraten,Meer'
      }
      

      Ich hatte jetzt noch eine Sache probiert und verstehe nicht, warum dataset[] immer leer ist. Ich habe diese 2 Funktionen zugefügt:

      function valInArray(arrFilter){
        console.log('valInArray arrFilter: ', arrFilter);
        for(let filterWord of arrFilter){
          console.log('valInArray filterWord: ', filterWord);
          myCallback(filterWord);
        }
      }
      
      function myCallback(filterWord){
        console.log('myCallback filterWord: ', filterWord);
        return filterWord;
      }
      

      Testweise, erstmal ohne Schleife, rufe ich im Filter die Funktion auf:

      dataset = arrMovies.filter(movie => movie['Genre'].includes(valInArray(arrFilter['Genre'],myCallback)) === true);
      
      

      Obwohl jeder Filter Begriff zurückgeben wird und geprüft werden müsste, ob dieser im Array enthalten ist movie['Genre'].includes(...) ist dataset[] am Ende leer. Warum?

      Ausgabe

      Gruß ebody

    4. problematische Seite

      Hallo Rolf,

      return movie => genresToFind.some(genre => movie.genres.includes(genre));
      

      Was ist movie? Eigentlich müsste es ein Parameter sein, aber diesen gibt es gar nicht.

      Wäre das die ausführliche Schreibweise?

          return function(movie){
              if(genresToFind.some(
                  if(movie.genres.includes(genre)){
                   return true;
                  }
              ){
                  return true;
              }
          }
      

      Gruß ebody

      1. problematische Seite

        Hallo ebody,

        das ist genao so eine Pfeilfunktion, wie Du sie in deinem eigenen Code an die filter-Methode übergeben hast. Ich bin davon ausgegangen, dass Du weißt, was Du da verwendet hast. Aber vielleicht hast Du es ja auch nur für merkwürdige Syntax für die filter-Methode gehalten?!

        Eine Pfeilfunktion ist grundsätzlich eine verkürzte Definition einer anonymen Funktion, also:

        const ermittleGenre1 = (movie) => movie.genres;
        const ermittleGenre2 = function(movie) { return movie.genres; }
        

        ist beinahe das Gleiche. Die kleinen, aber gemeinen Unterschiede werden im Wiki beschrieben.

        Was ist movie? Eigentlich müsste es ein Parameter sein, aber diesen gibt es gar nicht.

        Ja, doch, den gibt es. Der ist links von dem => aufgeschrieben und wird dadurch als Parameter deklariert. Wenn Du mehr als einen Parameter brauchst, verwendest Du Klammern:

        let summiere = (s1, s2) => s1 + s2;
        

        ist identisch zu

        function summiere(s1, s2) {
           return s1 + s2;
        }
        

        Ansonsten sind Pfeilfunktionen, genau wie klassische Funktionen, in JavaScript ganz normale Objekte, und das heißt, dass man sie nicht nur als Parameter übergeben kann, wie hier:

        //                                 .-- Definition Pfeilfunktion
        //                                 ! 
        let ergebnis = movies.filter(movie => movie.fsk <= 6);
        //                                              !
        //                    Kleiner-Gleich Operator --'
        

        Man kann sie auch an Variablen zuweisen und als Wert zurückgeben.

        Und weil so eine Pfeilfunktion ganz schön verwirren kann, wenn in ihrer Nähe einer der Operatoren <= oder >= auftaucht, habe ich das lieber mal beschriftet 😉

        Du könntest aber auch so filtern - und falls Du den IE unterstützen willst, musst Du das sogar, weil der die Pfeilfunktionen noch nicht kennt.

        let ergebnis = movies.filter(function(movie) { return movie.fsk <= 6; });
        

        Zur Frage nach der ausführlichen Schreibweise

        return movie => genresToFind.some(genre => movie.genres.includes(genre));
        

        Wäre das die ausführliche Schreibweise?

            return function(movie){
                if(genresToFind.some(
                    if(movie.genres.includes(genre)){
                     return true;
                    }
                ){
                    return true;
                }
            }
        

        Nein, das wäre ein Syntaxfehler und es wäre auch nicht inhaltlich äquivalent.

        • es fehlt jeweils der else-Zweig für die if() Abfragen, der false zurückgibt
        • der fragliche Code enthält nicht nur eine Pfeilfunktion, sondern zwei. Du musst beide durch Funktionen ersetzen.
        • Ein if-Statement als Parameter für some ist syntaktisch nicht möglich. An dieser Stelle wird eine Funktion benötigt, die dieses if-Statement enthält.
           return function(movie){
              if (genresToFind.some(
                     function(genre) {
                        if(movie.genres.includes(genre)) {
                           return true;
                        } else {
                           return false;
                        }
                     })) {
        //               '-- gehört zum 1. IF
        //             '---- gehört zum 1. IF
        //            '----- schließende Klammer von .some(...
        //           '------ schließende Klammer von function(genre) {
                 return true;
              } else {
                 return false;
              }
           }
        

        Aber das ist nun viel zu ausführlich geschrieben. Einen IF-Block, der einen booleschen Wert auswertet und nichts anderes tut als true oder false zurückzugeben, braucht man nicht.

        if (movie.genres.includes(genre)) {
           return true;
        } else {
           return false;
        }
        

        ersetzt man durch

        return movie.genres.includes(genre);
        

        Die sinnvolle "ausführliche Schreibweise" wäre also diese:

           return function(movie) {
              return genresToFind.some(
                        function(genre) {
                           return movie.genres.includes(genre);
                        }
                     );
           }
        

        Rolf

        --
        sumpsi - posui - obstruxi
        1. problematische Seite

          @@Rolf B

          Und weil so eine Pfeilfunktion ganz schön verwirren kann, wenn in ihrer Nähe einer der Operatoren <= oder >= auftaucht, habe ich das lieber mal beschriftet 😉

          Protip: Wenn man ein Schriftart verwendet, die dafür Ligaturen bereitstellt (bspw. Fira Code), kann man >= und => problemlos unterscheiden:

          😷 LLAP

          --
          “When I was 5 years old, my mother always told me that happiness was the key to life. When I went to school, they asked me what I wanted to be when I grew up. I wrote down ‘happy.’ They told me I didn’t understand the assignment, and I told them they didn’t understand life.” —John Lennon
          1. problematische Seite

            Hallo Gunnar,

            wow, sowas muss man erstmal kennen.

            Und ich bin noch nicht sicher, ob ich damit dauerhaft arbeiten will; schließlich muss man die Ligaturzeichen auch erstmal lernen und es könnte passieren, dass es irgendwo zu Ligaturen kommt, wo man keine will.

            Aber das guck ich mir jetzt an 😀

            Rolf

            --
            sumpsi - posui - obstruxi
          2. problematische Seite

            Hallo Gunnar,

            hm, gleich zwei Unschönheiten: In der Standard-Schriftgröße des Forums sind die horizontalen Striche nicht gleich breit

            Und in zitiertem Code sehen die Ligaturen merkwürdig aus

            Mag aber Gewöhnungssache sein; ich lass das im Forum jetzt mal als Custom CSS aktiv 😀

            Rolf

            --
            sumpsi - posui - obstruxi
            1. problematische Seite

              @@Rolf B

              hm, gleich zwei Unschönheiten: In der Standard-Schriftgröße des Forums sind die horizontalen Striche nicht gleich breit

              Nö. Auf einem vernünftigen OS mit vernünftigem Schriftrendering und einem vernünftigen Monitor (früher hießen die mal „hochauflösend“) sind die Striche gleich dick.

              😷 LLAP

              --
              “When I was 5 years old, my mother always told me that happiness was the key to life. When I went to school, they asked me what I wanted to be when I grew up. I wrote down ‘happy.’ They told me I didn’t understand the assignment, and I told them they didn’t understand life.” —John Lennon
              1. problematische Seite

                Hallo Gunnar,

                Nö, es lag scheinbar an der variable font Version oder daran, dass ich Chrome nicht neu gestartet hatte.

                Jetzt sieht's besser aus.

                Rolf

                --
                sumpsi - posui - obstruxi
        2. problematische Seite

          Tach!

          let summiere = (s1, s2) => s1 + s2;
          

          ist identisch zu

          function summiere(s1, s2) {
             return s1 + s2;
          }
          

          Im Prinzip ja, aber man muss dabei das Hoisting außer Acht lassen.

          let summiere = function(s1, s2) {
             return s1 + s2;
          }
          

          So wäre es identisch. (Die Sache mit dem this ist ja in dem Beispiel nicht relevant.)

          dedlfix.

        3. problematische Seite

          das ist genao so eine Pfeilfunktion, wie Du sie in deinem eigenen Code an die filter-Methode übergeben hast. Ich bin davon ausgegangen, dass Du weißt, was Du da verwendet hast. Aber vielleicht hast Du es ja auch nur für merkwürdige Syntax für die filter-Methode gehalten?!

          Hallo Rolf,

          ich wusste, dass es eine verkürzte Schreibweise ist. Als ich deinen verkürzten Code jetzt aber in die ausführliche Schreibweise "übersetzen" wollte, um ihn (für mich) besser zu verstehen, stieß ich an meine Grenzen.

          Daher Danke für die ausführliche Erklärung!

          Ja, doch, den gibt es. Der ist links von dem => aufgeschrieben und wird dadurch als Parameter deklariert.

          Aber wo wird denn der Wert vom Parameter festgelegt und wo/wie an die Funktion übergeben? Das sehe/verstehe ich nicht.

          Gruß ebody

          1. problematische Seite

            Hallo ebody,

            sprichst Du von Fällen wie diesem?

            gefiltert = movies.filter(movie => movie.fsk <= 6);
            

            Ich lass das Genrezeugs mal weg und beschränke mich auf eine einfachere Pfeilfunktion.

            Die wird intern von filter aufgerufen. Letztlich könntest Du filter auch selbst programmieren. Das ist nicht ganz einfach, einen vollständigen Polyfill für Array.prototype.filter findest Du bei Mozilla. Ich zeige hier mal eine vereinfachte Demoversion als standalone-Funktion, die im Wesentlichen den Callback-Aufruf zeigt:

            function demo_array_filter(inputArray, filterCallback) {
               let len = inputArray.length,
                   ergebnis = new Array(len),
                   i;
               for (i=0; i<len; i++) {
                  if (filterCallback(inputArray[i], i, inputArray)) {
                     ergebnis[i] = inputArray[i];
                  }
               }
            }
            

            Die Demoversion zeigt auch, dass der Callback eigentlich noch mehr Parameter bekommt außer dem aktuellen Element. Weitere Parameter sind der Index und das gefilterte Array. Aber die muss man nicht entgegennehmen.

            Rolf

            --
            sumpsi - posui - obstruxi
            1. problematische Seite

              Hi,

              die Frage bezog sich auf diesen Code:

              function createFilterForSome(...genresToFind) {
                 // User hat Array übergeben - ... legt ein weiteres Array drumherum
                 if (Array.isArray(genresToFind[0]))
                    genresToFind = genresToFind[0];
                 
                 return movie => genresToFind.some(genre => movie.genres.includes(genre));
              }
              
              movies.filter(createFilterForSome("Horror", "Komödie"));
              

              und diese Zeile:

              return movie => genresToFind.some(genre => movie.genres.includes(genre));
              

              movie ist ein Parameter. Aber wo wird der Wert von diesem Parameter festgelegt und wo/wie wird dieser Parameter an die anonyme Funktion übergeben?

              genre kann ich nachvollziehen. Das ist jeder einzelne Wert aus dem Array genresToFind[].

              genresToFind.some(genre => movie.genres.includes(genre))
              

              Gruß ebody

              1. problematische Seite

                Hallo ebody,

                Aber wo wird der Wert von diesem Parameter festgelegt und wo/wie wird dieser Parameter an die anonyme Funktion übergeben?

                Ah, ok. Das ist etwas tricky. createFilterFor... ist eine Funktion, die ein Objekt zurückgibt. Und dieses Objekt ist: eine Funktion. Ich zeig's mal einfacher:

                function createAdder(summand) {
                   return x => x + summand; 
                }
                
                let add7 = createAdder(7);
                let add2 = createAdder(2);
                let summe1 = add7(5);     
                let summe2 = add2(9);     
                // summe1 enthält jetzt 12 und summe2 enthält 11.
                

                createAdder gibt eine Funktion zurück, nämlich x => x + summand. Der Aufrufer von createAdder nimmt diese Funktion entgegen (speichert sie in add7) und verwendet sie dann: add7(5).

                So ähnlich läuft das in meinem Beispiel von vor 4 Tagen. Es wird eine Filterfunktion erzeugt, und die wird dann gleich an die filter-Methode des Arrays übergeben.

                Wieso tut man das? Es geht um die Closure, die von der create-Funktion erzeugt wird. Die Closure ist eine interne Eigenschaft der zurückgegebenen Funktion und schließt alle Variablen ein (daher Closure), die beim Erzeugen der Funktion sichtbar und gültig waren. Insbesondere auch den summand-Parameter. createAdder wird oben zweimal aufgerufen. Augenscheinlich erzeugt sie zweimal die gleiche Funktion, aber das stimmt nicht. Es sind zwei verschiedene Funktionsobjekte, und ihre Closure ist unterschiedlich. Die Closure von add7 enthält den Aufrufkontext von createAdder(7), und die Closure von add2 den Aufrufkontext von createAdder(2). Und deswegen addiert add7 den Wert 7 und add2 den Wert 2.

                Bei createFilterFor enthält die Closure der zurückgegebenen Funktion den genresToFind Parameter, so dass die Funktion ein movie nach den gewünschten Genres durchsuchen kann.

                Das ist ein Einstieg in funktionale Programmierung - je nach Sichtweise die Hölle auf Erden oder aber eine schöne Alternative bzw. Ergänzung zur objektorientierten Programmierung. FP ist nicht neu. Das gab's schon in den 60er Jahren, in Sprachen wie LISP oder APL. Allerdings waren die Computer da noch nicht so richtig dafür geeignet. Fahrt aufgenommen hat das in den letzten 20 Jahren, indem klassische Sprachen funktionale Erweiterungen enthielten. Die ersten FP Sprachen waren rein funktional, was den Einstieg schwer machte. Neuere Sprachen unterstützen funktionale Ansätze, zwingen aber nicht zur FP. Reine FP Sprachen wie Haskell oder Erlang (bzw. dessen Abkömmling Elixier, worin dieses Forum geschrieben ist) sind prozedural aufgewachsenen Wesen wie mir immer noch suspekt.

                Rolf

                --
                sumpsi - posui - obstruxi
                1. problematische Seite

                  Das ist ein Einstieg in funktionale Programmierung - je nach Sichtweise die Hölle auf Erden oder aber eine schöne Alternative bzw. Ergänzung zur objektorientierten Programmierung. FP ist nicht neu. Das gab's schon in den 60er Jahren, in Sprachen wie LISP oder APL.

                  Die theoretischen Grundlagen reichen sogar noch weiter in die Vergangenheit zurück. Alonzo Church und sein Team haben in den 1930er Jahren den Lambda Kalkül entwickelt, der bis heute das theoretische Fundament für viele funktionale Programmiersprachen bildet.

                  Die ersten FP Sprachen waren rein funktional, was den Einstieg schwer machte. Neuere Sprachen unterstützen funktionale Ansätze, zwingen aber nicht zur FP.

                  Es gibt keinen wirklichen Konsens darüber was "rein funktional" eigentlich bedeutet. Der Begriff wird heute meist in Verbindung mit Sprachen wie Haskell gebraucht, die eine glasklare Trennung zwischen Funktionen machen, die immer die selben Eingaben für die selben Ausgaben berechnen, und solchen, die darüber hinaus zusätzliche Nebenwirkungen haben können, wie zum Beispiel das Lesen und Schreiben von Dateien oder das Abfragen einer Datenbank. Das ist eine relativ junge Errungenschaft aus den 90ern. Darunter fallen tatsächlich nur sehr wenige Programmiersprachen.

                  Aber ich verstehe schon was du meinst. Sprachen aus den Lisp-, Erlang-, und ML-Familien setzen funktionale Programmierkonzepte an erste Stelle. Das ist in Sprachen aus der C-Großfamilie (C/C++, Java, C# etc.) anders, die bevorzugen prozedurale und objekt-orientierte Konzepte und integrieren nach und nach einige funktionale Ideen.

                  Reine FP Sprachen wie Haskell oder Erlang (bzw. dessen Abkömmling Elixier, worin dieses Forum geschrieben ist) sind prozedural aufgewachsenen Wesen wie mir immer noch suspekt.

                  Das betrifft etwa 99.99% aller Programmierer:innen. Der erste Kontakt findet fast immer mit einer prozeduralen und/oder objekt-orientierten Sprache statt. Das ist in unserem Bildungssystem schon so angelegt. Und wenn man dann mal ein paar Jahre Erfahrung gesammelt hat, fällt es umso schwierigier die antrainierten Denkmuster beseite zu schieben, um eine anders geartete Programmiersprache zu erlernen. Ich habe mich auch schwer damit getan.

    5. problematische Seite

      Hi Rolf,

      anhand deiner Lösung habe ich hier einen fertigen Code erstellen können. Ist etwas abgeändert.

      Vielen Dank an alle!

      /** Array mit Filmen  */
      const arrMovies = [
        {
          Titel: 'Prometheus',
          Genre: 'Science-Fiction,Fantasy',
          Tags: 'Favorit,Weltall'
        },
        {
          Titel: 'Arrival',
          Genre: 'Science-Fiction,Fantasy',
          Tags: 'Weltall'
        },
        {
          Titel: 'Pirates of the Caribbean Salazars Rache',
          Genre: 'Abenteuer,Fantasy',
          Tags: 'Piraten,Meer'
        },
        {
          Titel: 'Shutter Island',
          Genre: 'Drama,Thriller',
          Tags: 'Favorit,De Caprio'
        }  
        
      ];
      
      /** Array mit Filtern */
      const arrFilter = {
        Genre: ['Abenteuer','Action','Thriller'],
        Tags: ['New York','Favorit','Top10']
      };
      
      /** Gefiltertes Array mit Filmen */
      let dataset = [];
      
      /** 
       * Filtern 
       */
      
      /** arrFilter[] wird durchlaufen */
      for(let filterWord in arrFilter){
        
        /** 
         * Wenn ein Wert wie z.B. "Abenteuer" aus arrFilter[filterWord] (z.B. arrFilter['Genre']) in einem der Elemente aus arrMovies['Genre'] enthalten ist, speicher diesen Datensatz im Array filterCheck 
         * arrFilter['Genre']['Abenteuer','Action','Thriller'] würde mit dieser einen Zeile zwei Elemente aus arrMovies[] in filterCheck[] speichern.
         * arrFilter['Tags']['Favorit'] würde mit dieser einen Zeile zwei Elemente aus arrMovies[] in filterCheck[] speichern.
         */
        let filterCheck = arrMovies.filter(movie => arrFilter[filterWord].some(genre => movie[filterWord].includes(genre)));
        
        /** filterCheck[] Elemente enthält */
        if(filterCheck.length){
          /** filterCheck[] durchlaufen */
          for(let filterCheckElement of filterCheck){
            /** Wenn dataset[] das Array Element aus dem Array filterCheck[] nicht enthält */
            if(!dataset.includes(filterCheckElement)){
              /** Füge das Array Element aus dem Array filterCheck[] dem Array dataset[] hinzu */
              dataset.push(filterCheckElement);
            }
          }
        }
        
      }
      
      /** Ausgabe des Arrays dataset[], welche alle Datensätze gefiltert aus arrMovies[] enthält */
      console.log('dataset final: ', dataset);
      

      Gruß ebody