简体   繁体   中英

How to access two arrays as associative array in JavaScript with low overhead?

When returning a table via Ajax, it can be done with column names and row values separately, like this:

let columns = ["col1", "col2", "col3"];
let rows = [
      ["row 1 col 1", "row 1 col 2", "row 1 col 3"]
    , ["row 2 col 1", "row 2 col 2", "row 2 col 3"]
    , ["row 3 col 1", "row 3 col 2", "row 3 col 3"]
    , ["row 4 col 1", "row 4 col 2", "row 4 col 3"]
    , ["row 5 col 1", "row 5 col 2", "row 5 col 3"]
];

Or as an associative array like this

let rows = [
      { "col1": "row 1 col 1", "col2": "row 1 col 2", "col3": "row 1 col 3" }
    , { "col1": "row 2 col 1", "col2": "row 2 col 2", "col3": "row 2 col 3" }
    , { "col1": "row 3 col 1", "col2": "row 3 col 2", "col3": "row 3 col 3" }
    , { "col1": "row 4 col 1", "col2": "row 4 col 2", "col3": "row 4 col 3" }
    , { "col1": "row 5 col 1", "col2": "row 5 col 2", "col3": "row 5 col 3" }
];

Now, if there is much data, the first variant will result in less data to transmit.

The problem is, I can't access it like rows[i]["col1] then, as I can with the second variant.

Now, I could just take the rows and the columns, and create an associative array from that, like

let columns = ["col1", "col2", "col3"];

let data = [
      ["row 1 col 1", "row 1 col 2", "row 1 col 3"]
    , ["row 2 col 1", "row 2 col 2", "row 2 col 3"]
    , ["row 3 col 1", "row 3 col 2", "row 3 col 3"]
    , ["row 4 col 1", "row 4 col 2", "row 4 col 3"]
    , ["row 5 col 1", "row 5 col 2", "row 5 col 3"]
];



let arr = [];


for (let j = 0; j < data.length; ++j)
{
    let obj = {}; // "associative array" or Object

    for (let i = 0; i < columns.length; ++i)
    {
        obj[columns[i]] = data[j][i];
    }
    arr.push(obj);
}

But then I have to copy the object, which might take time & memory if the rowCount and the columnCount is large.

Is there any good (non-time & non-memory consuming) way to achive this in JavaScript ?

What I would need is indexed properties, but it seems like they don't exist in JavaScript...

The closest I have come is with the proxy object:

let columns = ["col1", "col2", "col3"];
let rows = [
      ["row 1 col 1", "row 1 col 2", "row 1 col 3"]
    , ["row 2 col 1", "row 2 col 2", "row 2 col 3"]
    , ["row 3 col 1", "row 3 col 2", "row 3 col 3"]
    , ["row 4 col 1", "row 4 col 2", "row 4 col 3"]
    , ["row 5 col 1", "row 5 col 2", "row 5 col 3"]
];

let cols = {}; // "associative array" or Object

for (let i = 0; i < columns.length; ++i)
{
    cols[columns[i]] = i;
}


let handler2 = {
    get: function (obj, prop, receiver)
    {
        return obj[cols[prop]];
    }
};

// https://www.sitepoint.com/es6-proxies/
let handler = {
    get: function (obj, prop, receiver)
    {
        console.log("obj:", obj, "prop:", prop, "receiver :", receiver);
        //return obj[prop];
        //return obj[cols[prop]];
        return new Proxy(obj[prop], handler2);
    }

    , set: function (obj, key, value)
    {
        console.log(obj, key, value);
    }

};

let p = new Proxy(rows, handler);

The problem here is - apart from that IE11 doesn't support proxy - that I need to create a new proxy on each row access, because it looks like proxy doesn't properly support arrays of objects...

Is there any sane way of solving this particular problem in JavaScript in a good/performant way ?

About the last thing I want is write code like this:

let columns = ["col1", "col2", "col3"];
let rows = [
      ["row 1 col 1", "row 1 col 2", "row 1 col 3"]
    , ["row 2 col 1", "row 2 col 2", "row 2 col 3"]
    , ["row 3 col 1", "row 3 col 2", "row 3 col 3"]
    , ["row 4 col 1", "row 4 col 2", "row 4 col 3"]
    , ["row 5 col 1", "row 5 col 2", "row 5 col 3"]
];

let cols = {}; // "associative array" or Object

for (let i = 0; i < columns.length; ++i)
{
    cols[columns[i]] = i;
}

let index_col1 = cols["col1"];
let index_col2 = cols["col2"];
let index_col3 = cols["col3"];


for (var i = 0; i < rows.length; ++i)
{
    console.log("col1:", rows[i][index_col1], "col2:", rows[i][index_col2], "col3:", rows[i][index_col3]);
}

which is what I currently have to do...

May I just suggest two different approaches. It entirely depends on how you actually need to access the data later on.

If you need it "row-per-row" your first approach is not that bad. I just would combine both of them into a single result. Does not make sense to have the column definition in a separate array I guess.

let rows = [
      ["col1", "col2", "col3"],
      ["row 1 col 1", "row 1 col 2", "row 1 col 3"],
      ["row 2 col 1", "row 2 col 2", "row 2 col 3"],
      ["row 3 col 1", "row 3 col 2", "row 3 col 3"],
      ["row 4 col 1", "row 4 col 2", "row 4 col 3"],
      ["row 5 col 1", "row 5 col 2", "row 5 col 3"]
];

In this approach you have all entries of a row grouped together, and even empty cells are easily treated with null / unedfined values.

If you rather want to access the data "column-per-column" I suggest using an object with the column header as key.

let table = {
    "col1": ["row 1 col 1", "row 2 col 1", ..."],
    "col2": ["row 1 col 2", "row 2 col 2", ..."]
}

In this approach it is also easily possible (and fast) to get data for a row entry. table['col1'][index]

Ah, got the answer myself.
It is kindof-possible, by creating a class and sharing the current row index between two proxys:

interface IProxyHandler
{
    get(obj, prop, receiver);
}


declare class Proxy
{
    public constructor(obj, handler_callback: IProxyHandler);
}

interface SomeTable
{
    col1: string;
    col2: number;
    col3: Date;
}

export class table<T>
{

    public obj: T[];
    //public columns: map<string, number>;
    public columns: { [key: string]: number };

    protected i: number;
    protected accessor: Proxy;


    //get bar(): boolean
    //{
    //    return null; // this._bar;
    //}
    //set bar(theBar: boolean)
    //{
    //    //this._bar = theBar;
    //}

    public row(index:number): T
    {
        this.i = index;
        return <T><any>this.accessor;
    }

    public rows :T[];

    constructor(rows: any[][], columnNames:string[])
    {
        this.obj = <any>rows;
        this.i = 0;

        // this.columns = columnNames;
        this.columns = {}; // "associative array" or Object

        for (let i = 0; i < columnNames.length; ++i)
        {
            this.columns[columnNames[i]] = i;
        }


        let handler: IProxyHandler = {
            get: function (obj, prop, receiver)
            {
                return this.obj[this.i][this.columns[prop]];
            }
        };

        handler.get = handler.get.bind(this);
        this.row = this.row.bind(this);
        this.accessor = new Proxy(this.obj, handler);

        let handler2: IProxyHandler = {
            get: function (obj, prop, receiver)
            {
                return this.row(prop);
            }
        };
        handler2.get = handler2.get.bind(this);

        this.rows = <any> new Proxy(this.obj, handler2);
    }


}


// https://caniuse.com/#feat=proxy
// Sorry, your browser is no longer supported. 
// If you want this program to support IE11, develop a proxy-polyfill for IE11. 
// Hint from Babel-docs: ES2015-Proxies requires support on the engine level; 
// it is thus not possible to polyfill Proxy in ES5.
export function testTable()
{
    let columns = ["col1", "col2", "col3"];
    let rows = [
        ["row 1 col 1", "row 1 col 2", "row 1 col 3"]
        , ["row 2 col 1", "row 2 col 2", "row 2 col 3"]
        , ["row 3 col 1", "row 3 col 2", "row 3 col 3"]
        , ["row 4 col 1", "row 4 col 2", "row 4 col 3"]
        , ["row 5 col 1", "row 5 col 2", "row 5 col 3"]
    ];

    let x = new table<SomeTable>(rows, columns);

    console.log(x.rows[0].col1);
    // console.log(x.row(1).col1);
    // console.log(x.obj[0][0]);
}

PS:
If I were to use a function, I'd need to do this:

public rowz(index: number, colName: string): any
{
    this.i = index;
    return this.obj[index][this.columns[colName]];
}

or this

public rowz(index: number): any
{
    this.i = index;

    return function (colName)
    {
        this.obj[index][this.columns[colName]];
    };

}

This has the disadvanages that I would lose compile-time type-safety, both with the columnName as well as with the return type. Or I would need to copy the entiry row into an object, which duplicates data/memory-consumption.


Another option, which is compatible with ES5/IE11, is using object-properties:

interface ITestTable1
{
    col1:number;
    col2:number;
}


interface ITestTable2
{
    a:number;
    b:number;
    c:number;
}



export class TableWrapper<T>
{
    public rows:any[][];
    protected m_accessor:object;
    protected m_i:number;

    protected m_columnMap: { [columnName: string]: number; };
    protected m_columns: string[];
    protected m_columnLength:number;


    public get rowCount(): number
    {
        return this.rows.length;
    }


    public get columnCount(): number
    {
        return this.m_columns.length;
    }


    get columns(): string[]
    {
        return this.m_columns;
    }


    protected setColumns(cols: string[])
    {
        this.m_columnLength = cols.length;

        this.m_columnMap = null;
        this.m_columnMap = {};

        for (let i = 0; i < this.m_columnLength; ++i)
        {
            this.m_columnMap[cols[i]] = i;
        }

        this.m_columns = cols;
    } // End Sub setColumns 


    public row(i:number):T
    {
        this.m_i = i;
        return <T><any>this.m_accessor;
    }


    public getIndex(name:string):number
    {
        return this.m_columnMap[name];
    }


    public addRow(dat:any[])
    {
        this.rows.push(dat);
        return this;
    }


    public removeRow(i:number)
    {
        this.rows.splice(i, 1);
        return this;
    }


    constructor(columns: string[], data: Array<any[]>, ignoreCase?:boolean)
    {
        if (ignoreCase == null)
            ignoreCase = true;

        for (let i = 0; i< columns.length; ++i)
        {
            columns[i] = columns[i].toLowerCase();
        } // Next i 


        let that = this;
        this.getIndex.bind(this);
        this.setColumns.bind(this);
        this.row.bind(this);
        this.addRow.bind(this);
        this.removeRow.bind(this);

        this.rows = data;
        this.setColumns(columns);
        this.m_accessor = { }; // Creates a new object


        for (let i = 0; i < columns.length; ++i)
        {
            let propName = columns[i];

            Object.defineProperty(this.m_accessor, propName, {
                // Using shorthand method names (ES2015 feature). 
                // get() { return bValue;}, 
                // set(value) { bValue = value;}, 
                // This is equivalent to: 
                // get: function() { return bValue; }, 
                // set: function(value) { bValue = value; }, 
                // And could be written as (getter = getter.bind(this)) 
                // get: getter, 
                // set: setter, 
                get: function ()
                { 
                    let currentRow =  <any> that.rows[that.m_i];
                    return currentRow == null ? currentRow : currentRow[i]; 
                },
                set: function(value:any) 
                { 
                    let currentRow =  <any> that.rows[that.m_i];
                    if (currentRow!= null )
                        currentRow[i] = value; 
                },
                enumerable: true,
                configurable: true
            });
        } // Next i 

    }

}


let tab: TableWrapper<ITestTable1> = new TableWrapper<ITestTable1>(["col1","col2"], [[1,2], [3,4]]);
// tab.columns=["col1","col2", "col3"];

let hi :TableWrapper<ITestTable2>= new TableWrapper<ITestTable2>(["a","b","c"], [[1,2,3],[4,5,6] ]);


console.log(tab.row(0).col1);
console.log(hi.row(0).a);
console.log(hi.row(1).b);
console.log(hi.row(0).c);

hi.row(0).a = 123;


for (let i = 0; i< hi.rowCount; ++i)
{
    for (let j=0; j < hi.columnCount; ++j)
    {
        console.log(hi.rows[i][j]);

        console.log(hi.row(i).a);
        console.log(hi.row(i).b);
        console.log(hi.row(i).c);

        console.log((<any>hi.row(i))[hi.columns[j]]);
        console.log((<any>hi.row(i))[hi.columns[j]]);
        console.log((<any>hi.row(i))[hi.columns[j]]);
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM