JavaScript: Как создать объект и фильтровать эти атрибуты?

У меня есть массив массивов JavaScript, например,

{
    "homes" : [{
        "home_id"         : "1",
        "address"         : "321 Main St",
        "city"            : "Dallas",
        "state"           : "TX",
        "zip"             : "75201",
        "price"           : "925",
        "sqft"            : "1100",
        "year_built"      : "2008",
        "account_type_id" : "2",
        "num_of_beds"     : "2",
        "num_of_baths"    : "2.0",
        "geolat"          : "32.779625",
        "geolng"          : "-96.786064",
        "photo_id"        : "14",
        "photo_url_dir"   : "\/home_photos\/thumbnail\/2009\/06\/10\/"
    }],
    ..........
}

Я хочу предоставить 3 разных метода поиска.

Как я могу вернуть подмножество этого массива домашних домов, в котором есть:

  • price между X и Y
  • bathrooms >= Z
  • # of bedrooms == A or == B or == C

Например, как создать код psuedo, например:

homes.filter {price >= 150000, price <= 400000, bathrooms >= 2.5, bedrooms == 1 | bedrooms == 3}

Ответ 1

Javascript 1.6 (FF, основанный на Webkit) имеет встроенную функцию Array.filter, поэтому нет необходимости изобретать колесо.

result = homes.
  filter(function(p) { return p.price >= 150000 }).
  filter(function(p) { return p.price <= 400000 }).
  filter(function(p) { return p.bathrooms >= 2.5 }) etc

для резервного копирования msie см. страницу, указанную выше.

Ответ 2

Взгляните на JSONPath http://code.google.com/p/jsonpath/

Что-то вроде jsonPath (json, "$.. homes [? (@. price < 400000)]" ). toJSONString() должен работать.

JAQL также выглядит интересным для фильтрации JSON. http://www.jaql.org/

Ответ 3

Здесь вы идете:

var filteredArray = filter(myBigObject.homes, {

    price: function(value) {
        value = parseFloat(value);
        return value >= 150000 && value <= 400000;
    },

    num_of_baths: function(value) {
        value = parseFloat(value);
        return value >= 2.5;
    },

    num_of_beds: function(value) {
        value = parseFloat(value);
        return value === 1 || value === 3;
    }

});

И функция filter:

function filter( array, filters ) {

    var ret = [],
        i = 0, l = array.length,
        filter;

    all: for ( ; i < l; ++i ) {

        for ( filter in filters ) {
            if ( !filters[filter](array[i][filter]) ) {
                continue all;
            }
        }

        ret[ret.length] = array[i];

    }

    return ret;

}

Ответ 4

Посмотрите $. grep в jQuery. Даже если вы не используете библиотеку, вы можете посмотреть на код и посмотреть, как они выполнили задачу, которую вы пытаетесь сделать.

ИЗМЕНИТЬ: Вот код:

 function filter(houses, filters) // pass it filters object like the one in @J-P answer
 {
      retArr = $.grep(houses, 
           function(house)
           {
                for(var filter in filters)
                {
                     function test = filters[filter];
                     if(!test(house)) return false;
                }
                return true;
           });

      return retArr;
 }

Ответ 5

Array.prototype.select = function(filter) {

    if (!filter) return this;

    var result = [], item = null;
    for (var i = 0; i < this.length; i++) {
        item = this[i];
        if (filter(item)) {
            result.push(item);
        }
    }

    return result;
}

function filterHomes(homes) {
    var a = 1, b = 2, c = 3, x = 4, y = 5, z = 6;
    return homes.select(function(item) {
        return between(item.price, x, y) && item.num_of_baths >= z && inArray(item.num_of_beds, [a, b, c]);
    });
}

function between(value, min, max) {
    return value >= min && value <= max;
}

function inArray(value, values) {
    for (var i = 0; i < values.length; i++) {
        if (value === values[i]) return true;
    }
    return false;
}

Ответ 6

Мне нравится этот вопрос, поэтому я снимаю его. Как насчет "закованного в цепочку" стиля кода, где объект, который у вас есть, сам по себе, является одним из фреймворков JavaScript DOM.

Я вызываю ваш Object MyObj:

MyObj.filter('home.price >= 150000')
     .filter('home.price <= 400000')
     .filter('home.num_of_baths >= 2.5')
     .filter('home.num_of_beds == 1 || home.bedrooms == 3');

И вот исходный код, этот пример работает.

var MyObj = {
    filter : function(rule_expression) {
        var tmpHomes = [];
        var home = {};
        for(var i=0;i<this.homes.length;i++) {
            home = this.homes[i];
            if (eval(rule_expression)) {
                tmpHomes.push(home);
            }
        }
        this.homes = tmpHomes;
        return this;
    },
    homes: [
    {
        "home_id"         : 1,
        "address"         : "321 Main St",
        "city"            : "Dallas",
        "state"           : "TX",
        "zip"             : "75201",
        "price"           : 300000,
        "sqft"            : 1100,
        "year_built"      : 2008,
        "account_type_id" : 2,
        "num_of_beds"     : 1,
        "num_of_baths"    : 2.5,
        "geolat"          : 32.779625,
        "geolng"          : -96.786064,
        "photo_id"        : "14",
        "photo_url_dir"   : "\/home_photos\/thumbnail\/2009\/06\/10\/foo.jpg"
    },
    {
        "home_id"         : 2,
        "address"         : "555 Hello World Way",
        "city"            : "Dallas",
        "state"           : "TX",
        "zip"             : "75201",
        "price"           : 200000,
        "sqft"            : 900,
        "year_built"      : 1999,
        "account_type_id" : 2,
        "num_of_beds"     : 1,
        "num_of_baths"    : 1.0,
        "geolat"          : 32.779625,
        "geolng"          : -96.786064,
        "photo_id"        : "14",
        "photo_url_dir"   : "\/home_photos\/thumbnail\/2009\/06\/10\/foo.jpg"
    },
    {
        "home_id"         : 3,
        "address"         : "989 Foo St",
        "city"            : "Dallas",
        "state"           : "TX",
        "zip"             : "75201",
        "price"           : 80000,
        "sqft"            : 1100,
        "year_built"      : 2003,
        "account_type_id" : 2,
        "num_of_beds"     : 3,
        "num_of_baths"    : 3,
        "geolat"          : 32.779625,
        "geolng"          : -96.786064,
        "photo_id"        : "14",
        "photo_url_dir"   : "\/home_photos\/thumbnail\/2009\/06\/10\/foo.jpg"
    },
    {
        "home_id"         : 4,
        "address"         : "1560 Baz Rd",
        "city"            : "Dallas",
        "state"           : "TX",
        "zip"             : "75201",
        "price"           : 100000,
        "sqft"            : 1100,
        "year_built"      : 2008,
        "account_type_id" : 2,
        "num_of_beds"     : 3,
        "num_of_baths"    : 1.5,
        "geolat"          : 32.779625,
        "geolng"          : -96.786064,
        "photo_id"        : "14",
        "photo_url_dir"   : "\/home_photos\/thumbnail\/2009\/06\/10\/foo.jpg"
    }
    ]
};

Ответ 7

Забавно, как большинство из этих ответов ничего не делают, кроме как попытаться поставить вопрос об итерации по массиву более элегантно, когда это даже не то, что требуется Джорджу.

Предполагая, что у вас огромный JSON, вы хотите что-то быстро. Итерация - это не так. Вам нужно jOrder (http://github.com/danstocker/jorder), который выполняет поиск в 100 раз быстрее в таблице 1000 строк, чем итеративная фильтрация. Чем больше таблица, тем выше соотношение.

Вот что вы делаете.

Так как jOrder не может обрабатывать более одного фильтра неравенства за один раз, вам придется сделать это за три шага, но все равно будет больше.

Сначала создайте таблицу jOrder на основе исходных данных и добавьте в нее индекс цены:

var table = jOrder(data.homes)
    .index('price', ['price'], { ordered: true, grouped: true, type: jOrder.number });

Вы можете повторно использовать эту таблицу в будущих поисках. Теперь вы начинаете с получения результатов, отфильтрованных по цене, и обертываете их в другую таблицу jOrder и, наконец, добавляете индекс ванной комнаты:

var price_filtered = jOrder(table.where([{ price: { lower: 150000, upper: 400000 } }]))
    .index('bathrooms', ['num_of_baths'], { ordered: true, grouped: true, type: jOrder.number });

Затем мы продолжаем, делая то же самое для ванных комнат, добавляя индекс в спальнях, который не нужно заказывать, так как здесь мы не будем использовать фильтры неравенства:

var bath_filtered = jOrder(price_filtered.where([{ num_of_baths: { lower: 2.5 } }]))
    .index('bedrooms', ['num_of_beds'], { grouped: true });

Наконец, вы получите полностью отфильтрованный набор:

var filtered = jOrder(bath_filtered.where([{ num_of_beds: 1 }, { num_of_beds: 3 }]));

Конечно, вы можете обернуть все это в функцию, которая принимает три аргумента:

  • { price: { lower: 150000, upper: 400000 } }
  • { num_of_baths: { lower: 2.5 } }
  • [{ num_of_beds: 1 }, { num_of_beds: 3 }]

и возвращает filtered.

Ответ 8

Библиотека Underscore.js хорошо работает для этих задач. Он использует собственные команды JavaScript, когда они присутствуют. Мне удалось получить множество итерационных циклов, используя эту библиотеку.

Бонус, последняя версия связана с цепочкой, например jQuery.

Ответ 9

Я несколько раз пересматривал этот ответ, и люди могут посмотреть на wiki, если они заинтересованы в пересмотрах, но это окончательное решение, которое я придумал. Он очень похож на многие из других размещенных здесь, главное отличие состоит в том, что я расширил Array.prototype завернутую функцию Array.prototype.filter, так что тестовые утверждения оцениваются в области массива вместо массива.

Основное преимущество, которое я вижу в этом решении, а не просто использование метода filter напрямую, заключается в том, что он позволяет писать общие тесты, которые проверяют значение this[key]. Затем вы можете создать общие элементы формы, связанные с конкретными тестами, и использовать метаданные из подлежащих фильтрации объектов (в данном случае дома), чтобы связать конкретные экземпляры элементов формы с ключами. Это не только делает код более многоразовым, но я думаю, что он фактически создает запросы программно немного более прямолинейно. Кроме того, поскольку вы можете определить общие элементы формы (например, множественный выбор для разных значений или набор выпадающих списков для диапазона), вы также можете создать более мощный интерфейс запросов, в который могут быть введены дополнительные элементы формы, динамически позволяющие пользователям создавать сложных и настраиваемых запросов. Вот что код разбивается на:

Прежде чем делать что-либо, вы должны сначала проверить, существует ли функция filter и расширять Array.prototype, если она не упоминается в других решениях. Затем разверните Array.prototype, чтобы обернуть функцию filter. Я сделал так, чтобы аргументы были необязательными, если кто-то хочет использовать тестовую функцию, которая по какой-то причине не принимает, я также попытался включить ошибки, чтобы помочь вам реализовать код:

Array.prototype.filterBy = function(testFunc, args)
{
    if(args == null || typeof args != 'object') args = [args];
    if(!testFunc || typeof testFunc != 'function') throw new TypeError('argument 0 must be defined and a function');
    return this.filter(function(elem)
        {
        return testFunc.apply(elem, args);
    });
};

Это принимает тестовую функцию и массив аргументов и определяет встроенную функцию как обратный вызов для функции Array.prototype.filter, которая вызывает function.prototype.apply для выполнения тестовой функции в области проверяемого элемента массива с указанным аргументы. Затем вы можете написать набор общих функций тестирования, таких как:

testSuite = {

     range : function(key, min, max)
     {
          var min = parseFloat(min);
          var max = parseFloat(max);
          var keyVal = parseFloat(this[key]);
          if(!min || !max|| !keyVal) return false;
          else return keyVal >= min && keyVal <= max;
     },

     distinct : function(key, values)
     {
          if(typeof key != 'string') throw new TypeError('key must be a string');
          if(typeof values != 'object') values = [values];
          var keyVal = this[key];
          if(keyVal == undefined) return false;


          for(var i in values)
          {
             var value = values[i];
             if(typeof value == 'function') continue;
             if(typeof value == 'string')
             {
                  if(keyVal.toString().toLowerCase() == value.toLowerCase()) return true;
                  else continue; 
             }
             else
             {
                 keyVal = parseFloat(keyVal);
                 value = parseFloat(value);
                 if(keyVal&&value&&(keyVal==value)) return true;
                 else continue;

            }
          }
          return false;
     }
};

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

Затем вы расширяете объект, содержащий массив домов, таким образом (я назвал его houseData, назовите его, что бы он ни находился в вашем коде):

 housesData.filterBy = function(tests)
 {
      ret = this.homes.slice(0);
      if(tests)
      {
           for(var i in tests)
           {
               var test = tests[i];
               if(typeof test != 'object') continue;
               if(!test.func || typeof test.func != 'function') throw new TypeError('argument 0 must be an array or object containing test objects, each with a key "func" of type function');
               else ret = ret.filterBy(test.func, test.args ? test.args : []);
           }
      }
      return ret;
 }

Затем вы можете вызвать эту функцию так, чтобы получить набор результатов, используя общие функции, определенные выше:

 result = housesData.filterBy([{func:range,args:['price','150000','400000'],
                      {func:distinct,args:['num_of_bedsf',[1, 2, 3]]}]);

То, что я действительно хотел бы использовать, это, однако, сериализовать общие элементы формы, о которых я упоминал ранее, в массив или хэш-карту тестовых объектов. Вот простой пример, который я использовал для проверки этого кода (я использовал jQuery, потому что это проще, все предыдущие коды находятся в example.js вместе с массивом darty @artlung):

 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE html 
      PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:myNS="http://uri.for.your.schema" xml:lang="en" lang="en">
<head>
    <script src="jquery-1.3.2.js" type="text/javascript" language="javascript"></script>
    <script src="example.js" type="text/javascript" language="javascript"></script>
    <script type="text/javascript" language="javascript">
        runQuery = function(event)
        {
            var tests = [];
            $('#results').attr('value', '');;
            controls = $('#theForm > fieldset');
            for(var i = 0; i < controls.length ; i++)
            {
                var func;
                var args = [];
                control = controls.eq(i);
                func = testSuite[control.attr('myNS:type')];
                args.push(control.attr('myNS:key'));
                var inputs = $('input', control);
                for(var j=0; j< inputs.length; j++)
                {
                    args.push(inputs[j].value);
                }
                tests.push({func:func,args:args});
            }
            result = housesData.filterBy(tests);
            resultStr = '';
            for(var i = 0; i < result.length; i++)
            {
                resultStr += result[i]['home_id'];
                if(i < (result.length -1)) resultStr += ', ';
            }
            $('#results').attr('value', resultStr);
        }
    </script>
</head>
<body>
  <form id="theForm" action="javascript:null(0)">
    <fieldset myNS:type="range" myNS:key="price">
        <legend>Price:</legend>
        min: <input type="text" myNS:type="min"></input>
        max: <input type="text" myNS:type="max"></input>
    </fieldset>
    <fieldset myNS:type="distinct" myNS:key="num_of_beds">
        <legend>Bedrooms</legend>
        bedrooms: <input type="text" myNS:type="value"></input>
    </fieldset>
    <button onclick="runQuery(event);">Submit</button>
  </form>
  <textarea id="results"></textarea>
    </body>
 </html>

Ответ 10

o = ({
'homes' : 
[{
    "home_id"         : "1",
    "address"         : "321 Main St",
    "city"            : "Dallas",
    "state"           : "TX",
    "zip"             : "75201",
    "price"           : "20000",
    "sqft"            : "1100",
    "year_built"      : "2008",
    "account_type_id" : "2",
    "num_of_beds"     : "3",
    "num_of_baths"    : "3.0",
    "geolat"          : "32.779625",
    "geolng"          : "-96.786064",
    "photo_id"        : "14",
    "photo_url_dir"   : "\/home_photos\/thumbnail\/2009\/06\/10\/"
}
]})

Array.prototype.filterBy = function( by ) {
    outer: for ( 
    var i = this.length,
        ret = {},
        obj; 
        i--; 
    ) 
    {
    obj = this[i];
    for ( var prop in obj ) {

        if ( !(prop in by) ) continue

        if ( by[prop](obj[prop]) ) {
        ret[prop] = obj[prop]
        }

    }
    }

    return ret;
}

var result = o.homes.filterBy({
    price:function(price) {
        price = parseFloat(price)
        return price >= 15000 && price <=40000
    },
    num_of_baths:function(bathroom){
        bathroom = parseFloat(bathroom)
        return bathroom > 2.5
    },
    num_of_beds:function(bedroom){
        bedroom = parseFloat(bedroom)
        return bedroom === 1 || bedroom === 3
    }
});

for ( var p in result ) alert(p + '=' + result[p])

Ответ 11

Я соглашаюсь не изобретать колесо и просто использовать собственный метод фильтра Array.

Определение функции, показанное на странице Mozilla, добавит filter, если оно отсутствует, независимо от браузера, с помощью обнаружения объекта (if (!Array.prototype.filter))

Любой из методов, предлагающих eval и/или несколько циклов for, медленный и потенциально опасный.

Вы так не сказали, но я предполагаю, что значения будут поступать от ввода пользователем (форма), поэтому я бы сделал что-то вроде:

var filtered_results = obj.homes
    .filter(function (home)
    {
        return parseInt(home.price) >= document.getElementsByName('price_gt')[0].value;
    })
    .filter(function (home)
    {
        return parseInt(home.price) <= document.getElementsByName('price_lt')[0].value;
    })
    .filter(function (home)
    {
        return parseFloat(home.num_of_baths) >= document.getElementsByName('baths_gt')[0].value;
    });

еще лучше, не перебирайте по списку N раз и просто делайте:

var filtered_results = obj.homes.filter(function (home)
{
    return (
        parseInt(home.price) >= document.getElementsByName('price_gt')[0].value &&
        parseInt(home.price) <= document.getElementsByName('price_lt')[0].value &&
        parseFloat(home.num_of_baths) >= document.getElementsByName('baths_gt')[0].value
    );
});

Я знаю, что это не соответствует запрошенному синтаксису, но этот подход быстрее и гибче (не используя значения eval или hardcoded).

Ответ 12

Ryan Lynch. Если вы используете строки для сравнения ( "сравнение операторов значений" ), вы можете использовать простой JavaScript (сравнение оператора). Логическая негибкость такая же, только с меньшей скоростью и безопасностью (через eval()).

Отсутствие гибкости в моем примере объясняется тем, какие поля (цена, ванны и т.д.) и какие операторы мы интересуем, но исходный запрос плаката перечислял конкретные поля и сравнения (цена < X, baths > Y, и т.д.).

GeorgeG - если вас это интересует, и мои предположения о значениях фильтра, поступающих из формы, верны, я могу написать более общий "для каждого из заданных пользователем значений, фильтровать результаты по запросу", но Я буду ждать подтверждения и, если возможно, образца кода.

Ответ 13

Я предполагаю, что вы хотите, чтобы синтаксический сахар мог вызвать направление фильтрации в массиве homes, чтобы генерировать новый массив только принятых фильтром результатов.

Используйте прототип объекта для определения функции фильтра для объекта массива, который принимает хэш фильтров параметров. Затем вы можете легко использовать этот хэш для генерации и возврата нового массива домов, соответствующих желаемому свойству.