Count and compare number of keys and values between objects

Asked

Viewed 457 times

1

I am using Javascript objects to transcribe the interface of an app, however, I would like to create a tool for collaborative transcription but do not know how to proceed.

Simplistically the object has indices, items or sub-indices with items and all their values are strings.

Example:

// objeto original
var i18n = {
    // índice único
    title: "Titulo qualquer",

    // índice com sub-índices
    words: {
       logout: "Encerrar sessão",
       open: "abrir",
       close: "fechar"
    },

    // índice com sub-índices e itens
    combo: {
       btn_top: "De volta ao topo",
       err_conn: {
          dberr: "Erro ao conectar a base de dados",
          reqerr: "Erro ao requisitar recurso"
       }
    }
}

Assuming this object is the "original" object to have its values modified would be a copy of this.

How to compare the edited object to the original showing this difference in percentage % ?

  • The percentage of each property is equal?

  • The overall percentage of items modified in the "copy" relative to the "original"

  • Lauro, if you think of each depth level as a tree as you want to do? In this case you have 3 properties on the first level. Each one is worth 33,333% ?

  • Actually the object is much larger than the example, if I use Object.keys(i18n) return the number of keys in the "first level" thought to divide the total by 100 however the items or sub-items within these "first level" indices do not allow me to express this greatness so

  • Don’t allow why? The simplest way is to give a percentage to each property and its sub-properties a fraction of that %. If you do not adopt this model you must have rules for the weight/value of all properties

  • Well I believe I do not arrive at a logic of how to compare these differences. To fetch all keys would pass Object.keys() on the object and then on this result and then on its results. And how would you do this check/comparison? How would you subtract the percentage of what was modified from the "original" ?

  • For there are many ways, so I ask. The simplest are: #1 each sub-property is an equal fraction of the total of properties. #2 each property has the weight of its offspring.

  • Which of the preferred models?

  • I think the #2 because the percentage should express the total contained in the object? no?

Show 4 more comments

2 answers

1


You can do this recursively, so you can use any type of object without having to configure N loops for that specific object.

function compare(objA, objB){
    return Object.keys(objA).reduce(function(arr, prop){
        if (typeof objA[prop] == 'string' || typeof objA[prop] == 'number') {
            return arr.concat(objA[prop] === objB[prop])
        }
        else return arr.concat(compare(objA[prop], objB[prop]));
    }, [])
}

var changes = compare(original, changed);
var percentageEqual = changes.filter(Boolean).length * 100 / changes.length;

By using your example:

function compare(objA, objB) {
  return Object.keys(objA).reduce(function(arr, prop) {
    if (typeof objA[prop] == 'string' || typeof objA[prop] == 'number') {
      return arr.concat(objA[prop] === objB[prop])
    } else return arr.concat(compare(objA[prop], objB[prop]));
  }, [])
}

// objeto original
var i18n1 = {
  // índice único
  title: "Titulo qualquer",
  // índice com sub-índices
  words: {
    logout: "Encerrar sessão",
    open: "abrir",
    close: "fechar"
  },
  // índice com sub-índices e itens
  combo: {
    btn_top: "De volta ao topo",
    err_conn: {
      dberr: "Erro ao conectar a base de dados",
      reqerr: "Erro ao requisitar recurso"
    }
  }
}

// objeto original
var i18n2 = {
  // índice único
  title: "Titulo qualquer modificado",
  // índice com sub-índices
  words: {
    logout: "Encerrar sessão",
    open: "abrir janela",
    close: "fechar"
  },
  // índice com sub-índices e itens
  combo: {
    btn_top: "De volta ao topo",
    err_conn: {
      dberr: "Erro ao conectar a base de dados",
      reqerr: "Reco reco"
    }
  }
}

var changes = compare(i18n1, i18n2);
var percentageEqual = changes.filter(Boolean).length * 100 / changes.length;
console.log(percentageEqual.toFixed(3) + '%');

Another variant as you mentioned "return also an array containing the words|unedited sentences (in an index with the full path of the property within the object)" would be so:

jsFiddle: https://jsfiddle.net/1a22mz0p/

  • Gracias. If I want to return also an array containing the words|unedited sentences (in an index with the full path of the property within the object) it would be possible in your example?

  • @Lauromoraes this functionality does not give the example as it is, but changing a little gives. I walk a bit tight time, but then I make an Edit with this idea. The main thing is to use recursiveness, so it is more compact and scalable.

  • OK. I really liked the solution that you presented, I modified a little and I was able to bring edited words and the unedited ones but without the path in the object :(

  • 1

    @Lauromoraes I added to the answer

0

Knowing that: the object "original" possesses properties and that these possess other properties themselves and all end values are of type string and without addressing validation issues (outside the scope of the question) a viable method would be the following.

  • iterate every object and its properties to recur all values (string)

  • create a percentage index from the total value of the object "original"

  • iterate the total value of the object "original" and of "clone" to verify equality and based on this (or the lack thereof) increase indexes used to assemble the completed and remaining total percentage (to be completed)

The following approach meets the issue.

Example:

// objeto original
var i18n1 = {
    // índice único
    title: "Titulo qualquer",
    // índice com sub-índices
    words: {
       logout: "Encerrar sessão",
       open: "abrir",
       close: "fechar"
    },
    // índice com sub-índices e itens
    combo: {
       btn_top: "De volta ao topo",
       err_conn: {
          dberr: "Erro ao conectar a base de dados",
          reqerr: "Erro ao requisitar recurso"
       }
    }
}

// objeto original
var i18n2 = {
    // índice único
    title: "Titulo qualquer modificado",
    // índice com sub-índices
    words: {
       logout: "Encerrar sessão",
       open: "abrir janela",
       close: "fechar"
    },
    // índice com sub-índices e itens
    combo: {
       btn_top: "De volta ao topo",
       err_conn: {
          dberr: "Erro ao conectar a base de dados",
          reqerr: "Reco reco"
       }
    }
}

// função para percorrer o object
var get_values = function(obj){
    var strings = [];
    // initialize loops
    for (k in obj) {
         // values from first level
         if ( typeof obj[k] === 'string' ) {
              strings.push(obj[k])
         }
         // first level keys search
         if ( typeof obj[k] === 'object' ) {
              for (k1 in obj[k] ) {
                   // values from second level
                   if ( typeof obj[k][k1] === 'string' ) {
                        strings.push(obj[k][k1])
                   }
                   // second level keys search
                   if ( typeof obj[k][k1] === 'object' ) {
                        for (k2 in obj[k][k1]) {
                             // values from third level
                             if ( typeof obj[k][k1][k2] === 'string' ) {
                                  strings.push(obj[k][k1][k2])
                             }
                             // third level keys search
                             if ( typeof obj[k][k1][k2] === 'object' ) {
                                  for (k3 in obj[k][k1][k2]) {
                                       // values from fourt level
                                       if ( typeof obj[k][k1][k2][k3] === 'string' ) {
                                            strings.push(obj[k][k1][k2][k3])
                                       }
                                       /*
                                       if ( typeof obj[k][k1][k2][k3] === 'object' ) {
                                            console.info(obj[k][k1][k2][k3])
                                       }
                                       */
                                  }
                             }
                        }
                   }
              }
         }
    }
    return strings;
};

// função para comparar
var compare = function(obj1, obj2){
    var indx1 = get_values(obj1),
        indx2 = get_values(obj2),
        len   = indx1.length;

    var percentage = (100/indx1.length),
        yes = 0,    // traduzidas
        not = 0;    // faltando
    // loop
    for (;len--;) {
         if ( indx1[len] !== indx2[len] ) {
              yes++
         } else {
              not++
         }
    }
    var done = ( (percentage * yes) === 100.00 || (percentage * yes) === 0.00) ? (percentage * yes).toFixed(0) : (percentage * yes).toFixed(2);
    var need = ( (percentage * not) === 100.00 || (percentage * not) === 0.00) ? (percentage * not).toFixed(0) : (percentage * not).toFixed(2);

    return {
        done: done +'%',
        need: need +'%'
    }
};

// buscar resultado
var res = compare(i18n1, i18n2);

// printar
console.log('tradução concluída: ' + res.done);
// output: tradução concluída: 42.86%

console.log('faltando traduzir: ' + res.need);
// output: faltando traduzir: 57.14%

However I’m not sure if the amount of loops using for is the best way or if there is a less "costly" method so I leave the question open for corrections, suggestions or a more qualified response that will arise.

Grateful for any help.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.