jsFind. Выборка данных из массива объектов

    Задача: Язык javascript. Имеется большой массив объектов. Нужно выбрать из массива некоторые объекты, в зависимости от значений свойств этих объектов.

    Внимание! Всё нижеследующее возможно дикий баян и скорее всего стрёмный велосипед. Приведённый код опытным программистам отрубает пальцы, а неопытных разрывает в клочья.

    В качестве вступления: основной код был написан больше двух лет назад. Сначала он использовался как временный и в последствии я его хотел заменить обработкой на backend'e. Время шло код обрастал, мутировал и начал «инфицировать» собой другие проекты. И на моё удивление он везде очень хорошо приживался, заменяя собой связку клиент-сервер в тех случаях где было не так много данных, например, каталоги товаров в которых кол-во наименований не более 1000шт.

    Для демонстрации подготовил вот такой простой массивчик(автолюбителям посвещается):

    var cars=[
    			{
    				brand: 'audi',		//фирма производитель
    				model: 'a4',		//модель
    				volume_engine: '1.8',	//объём двигателя
    				hp: '120',		//кол-во лошадиных сил
    				awd: 'нет',   		//полный привод [да/нет]
    				automat: 'да',		//автомат [да/нет]
    			},
    			{
    				brand: 'audi',		//фирма производитель
    				model: 'a4 allroad',	//модель
    				volume_engine: '2',	//объём двигателя
    				hp: '211',		//кол-во лошадиных сил
    				awd: 'да',   		//полный привод [да/нет]
    				automat: 'да',		//автомат [да/нет]
    			},
    			{
    				brand: 'audi',		//фирма производитель
    				model: 'a6',		//модель
    				volume_engine: '2',	//объём двигателя
    				hp: '180',		//кол-во лошадиных сил
    				awd: 'нет',   		//полный привод [да/нет]
    				automat: 'да',		//автомат [да/нет]
    			},
    			{
    				brand: 'bmw',		//фирма производитель
    				model: '3 Series',	//модель
    				volume_engine: '1.6',	//объём двигателя
    				hp: '135',		//кол-во лошадиных сил
    				awd: 'нет',   		//полный привод [да/нет]
    				automat: 'нет',		//автомат [да/нет]
    			},
    			{
    				brand: 'bmw',		//фирма производитель
    				model: '5 Series',	//модель
    				volume_engine: '3',	//объём двигателя
    				hp: '258',		//кол-во лошадиных сил
    				awd: 'нет',   		//полный привод [да/нет]
    				automat: 'да',		//автомат [да/нет]
    			},
    			{
    				brand: 'volkswagen',	//фирма производитель
    				model: 'passat',	//модель
    				volume_engine: '1.8',	//объём двигателя
    				hp: '152',		//кол-во лошадиных сил
    				awd: 'нет',   		//полный привод [да/нет]
    				automat: 'да',		//автомат [да/нет]
    			},
    ]
    


    Начнём сразу с примеров. Не обращайте внимание на «ещё...» — осталось от console.log(). Для начала выберем из массива все автомобили марки audi (попробовать):

    cars.find({ brand:"audi"})
    
    //результат:
    [
    	{ brand="audi",  model="a4",  volume_engine="1.8",  ещё...}, 
    	{ brand="audi",  model="a4 allroad",  volume_engine="2",  ещё...},
    	{ brand="audi",  model="a6",  volume_engine="2",  ещё...}
    ]
    


    Выберем все модели a4 марки audi (попробовать):

    cars.find({ brand: "audi", model: "%a4%" })
    
    //результат:
    [
    	{ brand="audi",  model="a4",  volume_engine="1.8",  ещё...},
    	{ brand="audi",  model="a4 allroad",  volume_engine="2",  ещё...}
    ]
    


    Выберем все модели a4 марки audi с полным приводом (попробовать):

    cars.find({ brand: "audi", model: "%a4%", awd:"да" })
    
    //результат:
    [
    	{ brand="audi",  model="a4 allroad",  volume_engine="2",  ещё...}
    ]
    


    — тоже самое, но выборка идёт по очереди (попробовать):

    cars.find({ brand: "audi" }).find({ model: "%a4%" }).find({ awd: "да" })
    
    //результат:
    [
    	{ brand="audi",  model="a4 allroad",  volume_engine="2",  ещё...}
    ]
    


    А теперь найдем все авто с мощностью двигателя больше 200 лошадок (попробовать):

    cars.find({ hp: ">=200" })
    
    //результат:
    [
    	{ brand="audi",  model="a4 allroad",  volume_engine="2",  ещё...}, 
    	{ brand="bmw",  model="5 Series", volume_engine="3",  ещё...}
    ]
    


    Выберем двух и трёхлитровые машины (попробовать):

    cars.find({ volume_engine: ["2","3"] })
    
    //результат:
    [
    	{ brand="audi",  model="a4 allroad",  volume_engine="2",  ещё...}, 
    	{ brand="audi",  model="a6",  volume_engine="2",  ещё...}, 
    	{ brand="bmw",  model="5 Series",  volume_engine="3",  ещё...}
    ]
    


    Выберем двух и трёхлитровые машины фирмы audi (попробовать):

    cars.find({ brand:"audi", volume_engine:["2","3"] })
    
    //результат:
    [
    	{ brand="audi",  model="a4 allroad",  volume_engine="2",  ещё...}, 
    	{ brand="audi",  model="a6",  volume_engine="2",  ещё...}
    ]
    


    Выберем все машины у которых объём двигателя строго меньше 2ух литров и больше или равен 3ём (попробовать):

    cars.find({volume_engine:["<2",">=3"] })
    
    //результат:
    [
    	{ brand="audi",  model="a4",  volume_engine="1.8",  ещё...}, 
    	{ brand="bmw",  model="3 Series",  volume_engine="1.6",  ещё...}, 
    	{ brand="bmw",  model="5 Series",  volume_engine="3",  ещё...},
    	{ brand="volkswagen",  model="passat",  volume_engine="1.8",  ещё...}
    ]
    


    Думаю суть понятна. Метод find из исходного массива выбирает объекты, удовлетворяющие условиям, которые передаются методу, в качестве параметров. На выходе получается обычный массив объектов, и из него снова можно выбирать данные: cars.find({...}).find({...}).find({...}).find({...});

    GitHub
    jsfiddle

    p.s.#0 о методе find в MongoDB знаю, с него то всё и началось. Постараюсь в будущем сделать очень-очень похожим на него.
    p.s.#1 в сл.версии добавлю возможность расширять метод find своими способами фильтрации.
    p.s.#2 в следующей статье расскажу о jquery плагине на основе find.js, который берёт массив объектов и формирует из него фильтр со списком товаров и пагинацией, и разметкой bootstrap.
    p.s.#3 экранирование условий — пока ещё не придумал как это элегантно решить.
    Поделиться публикацией
    Похожие публикации
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 35
      0
      А чем метод filter у массива не угодил?
        –1
        Каждый раз для простых выборок писать callback функцию только код марать. Безусловно если нужно элементы отфильтровать каким-нибудь хитрым макаром, то filter это наш выбор.
          0
          Сравните:

          cars.filter(function(element, index, array) {
            return (element.brand == "audi" && (element.volume_engine == "2" || element.volume_engine == "3"));
          });
          

          и

          cars.find({ brand:"audi", volume_engine:["2","3"] })
          

            +1
            Ну можно чуть покороче, конечно :)

            cars.filter(function(el) {
              return (el.brand == "audi" && (el.volume_engine == "2" || el.volume_engine == "3"));
            });
            


            Тут, конечно, CoffeeScript чуть более хорош:
            (c for c in cars when c.brand == "audi" and c.volume_engine in ["2", "3"])
            
              +2
               cars.filter( (c) -> c.brand == "audi" and c.volume_engine in ["2", "3"] ) 

              если не ошибся в синтаксисе
                0
                Да, разумеется. Просто мне синтаксис генераторов нравится :)
                +2
                И ещё короче:
                cars.filter(function(car) car.brand==="audi" && ["2", "3"].indexOf(car.volume_engine)>=0});
                


                А в проекте и ещё чуть короче:
                cars.filter(car => car.brand==="audi" && ["2", "3"].indexOf(car.volume_engine)>=0);
                


                Или так:
                [car for(car of cars) if(car.brand==="audi" && ["2", "3"].indexOf(car.volume_engine)>=0)]
                
                  +1
                  Да уж, скорее бы Harmony «в люди» вышел: многие вещи значительно упрощают жизнь.
              +1
              Ну возможно, тогда ваш find — это просто «сахар», тем более что он, как я понимаю, является расширением для прототипа Array.

              А вообще, как идея — полноценно реализовать «язык запросов» MongoDB для обычных коллекций, да еще и с поддержкой аггрегирования было бы круто.
                0
                Ниже куча ссылок на подобные проекты.
                Плюс еще есть TinyData, которая полноценный запросник по слабоструктурированным данным :)
                0
                Как вариант (я использую)
                function Finder(arr){ this.data = arr; this.find = function(){ ...} }

                И уже кручу верчу массивами, без прототипирования базовых объектов.

                Как тот так
                var cars = new Finder(arrayCars); cars.find({ })
                +1
                Ну если посмотреть в код то он там и используется.

                Не радует что идет расширение базового прототипа…
                  0
                  Всегда есть дилемма расширять ли базовый прототип. С одной стороны в академических кругах это возбраняется, с другой стороны без расширения базового прототипа не получится сделать каскадную выбору данных:

                  cars.find({...}).find({...}).find({...}).find({...});
                  

                  Да и код такой:

                  cars.find({ brand: "audi" });
                  

                  читать приятнее, чем вот такой:

                  find(cars,{ brand: "audi" });
                  

                    +2
                    То что вы свой метод дописываете это меньше из зол, это можно указать в readme и человек подключая вашу библиотеку будет знать что базовые классы расширяться методам и find и page. Но вот то что он не может знать, это то что добавляются еще и стандартные методы filter, indexOf, some. Вот такое неявство и есть зло. И вот представьте ситуацию, человек подключил вашу библиотеку, и чудным образом начал везде в коде юзать indexOf, some и прочее, потом отключил и у него херак, и в IE<9 все упало.
                  0
                  — офигенная вещь!
                  +1
                  filter + функция, которая генерирует функцию.

                  arr.filter( Array.findFn({brand: 'audi'}) )
                  

                  Можно из этого сделать обёртку find, но основной смысл — не парсить входные данные каждый раз, а иметь возможность во время инициализации один раз сделать

                  var audiFilter = Array.findFn({brand: 'audi'});
                  // а потом юзать во всех местах где надо выбрать audi 
                  arr.filter( audiFilter ); 
                  


                  Кстати, это именно тот самый кейс где eval не является злом, а даёт ощутимое ускорение.

                  Array.findFn({brand: 'audi'}) // должно генерировать вот такую простую функцию
                  function(el){ return el['brand'] === 'audi'; }
                  
                    0
                    — ну как вариант. Только в таком случае уже теряется элегантность кода, что ли.
                      +1
                      Вот для кейса поиска равенства:
                      Array.find = function( match ){
                          var i, pairs = [];
                          for( i in match )
                              match.hasOwnProperty( i ) &&
                                  pairs.push( 'el[\'' + i + '\'] === \''+ match[i] +'\''); // тут надо учесть экранирование кавычек в кей и в валью
                                  
                          return Function('el', 'return ' + pairs.join(' && ')  + ';');
                      };
                      Array.prototype.find = function( match ){
                          return this.filter( Array.find.call( this, match ) );
                      }
                      


                      После этого можно делать вот так:
                      cars.find({brand: 'audi'});
                      


                      А можно вот так:
                      var audiFilter = Array.find({ brand: 'audi' });
                      cars.filter( audiFilter );
                      


                      Чейнинг тоже работает. ( cars.find({ brand: «audi» }).find({ awd: «да» }) ) Получается покрытие обоих кейсов.
                    0
                    Писать велосипеды это хорошо и полезно.
                    Но я все равно хочу обратить внимание, на замечательную библиотеку linq.js (как можно догадаться порт LINQ на JavaScript).
                    Позволяет делать с массивами данных все то же, что умеет оригинальный LINQ, т.е. выбрать данные любой вложенности по запросу любой сложности, отсортировать их по любому количеству полей и еще много всего.
                    Вы можете оценить возможности библиотеки посмотрев код из документации.
                      0
                      Есть ещё проект jLinq.
                        0
                        Интересная библиотека, но все же LINQ на мой взгляд гораздо функциональнее.
                      +5
                      underscorejs.org/ умеет и выборки делать, и еще много всяких приятных плюшек
                        0
                        И sugarjs.com/ — кому-то такой вариант может показаться удобнее, особенно если был опыт работы с Prototype.
                        +1
                          +5
                          В стандарте ES6 есть метод Array.prototype.find и наиболее продвинутые его уже используют.
                          Плохо расширять прототип, у вас повторяется ситуация которая была с Prototype — стандартные методы реализованные по спецификации конфликтовали с методами библиотеки.

                          Тем более у вас же в библиотеки проверяется наличие этого метода и если я подключу es6-shim или когда браузеры реализуют Array#find ваша библиотека перестанет работать.

                            0
                            Правильно я понимаю, что .find (ES6) — это тот же .filter, только плюс механиз break при возврате null?
                              +1
                              Что-то я не правильно прочитал описание метода, сейчас уже разобрался, что выше спросил глупость.
                            0
                            По моему — всего лишь простейшая обертка для filter.
                            Вот вещь! square.github.io/crossfilter/
                              0
                              Похоже ссылка неправильная.
                                0
                                Ссылка правильная, это пример работы кроссфильтр на массиве в 200к записей. Кроссфильтр это не просто обертка на массиве, строится индекс по каждому из заданых полей и благодаря этому обеспечивается такой быстрый поиск.
                                  0
                                  Прошу прощения, интернет подвел. Теперь все открывается как нужно.
                              +1
                              Справедливости ради стоит отметить, что LoDash умеет where-предикат на большинстве функций, принимающих коллбек.

                              Например, представим себе функцию filter (или функцию find, или функцию any).
                              Каждая из них в прямолинейном варианте принимает коллбек, который должен возвращать истиностное значение, результат проверки.
                              Но есть также два специальных синтаксиса:
                              1. Если передать вместо коллбека строку, то будет создан специальный коллбек, который возвращает атрибут передаваемого ему объекта с таким именем (т.н. pluck-стиль, по имени функции, выбирающей атрибуты объектов).
                              2. Если передать вместо коллбека словарь, то будет производиться сверка его свойств со сверками объекта (т.н. where-стиль). Именно этот случай описан у топикстартера.
                              Также такой специальный коллбек можно создавать самому.

                              Преимущество данной либы в возможности задавать оператор (использовать не только равенство). Схожие возможности удобны в ORM. Из знакомых мне, ORM Django позволяет такие фишки.
                                0
                                Автору несомненно благодарность за поделку. Всячески поддерживаю ваше начинание. Но есть несколько пожеланий:

                                1) Полностью согласен с комментарием пользователя termi в отношении фразы плохо расширять прототип. Видел ваш код накануне и это стало поводом воздержаться от звезды.

                                2) Жаль что написано не на coffee. Я сам хотел написать подобную поделку just for fun или присоединиться к разработке чего-то подобного. Но писать на чистом JS уже лень

                                В целом считаю идею проекта хоть и не новой, но весьма занятной и достойной потраченного времени. Желаю успехов!

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое