Как правильно отсортировать этот массив хэш-карт

#javascript #node.js #algorithm #data-structures

#javascript #node.js #алгоритм #структуры данных

Вопрос:

Итак, у меня возникли проблемы, пытаясь понять, как правильно отсортировать этот массив хэш-карт. Данные:

 let array = 
    [ { name               : 'John'  } 
    , { species            : 'Human' } 
    , { species_sort_order : '1'     } 
    , { name_sort_order    : '2'     } 
    , { species            : 'Dog'   } 
    , { name               : 'oreo'  } 
    , { species_sort_order : ''      } 
    , { name_sort_order    : '1'     } 
    , { name_sort_order    : ''      } 
    , { name               : 'Susan' } 
    , { species            : 'Human' } 
    , { species_sort_order : ''      } 
   // , ... and so on
    ] 
 

1) Итак, существует 4 пары ключ-значение:

  • Имя,
  • Виды,
  • name_sort_order,
  • виды_sort_order

2) Связанные значения будут отображаться рядом друг с другом; однако они могут располагаться в случайном порядке, как показано выше и ниже:

  • {name: "John"}, {species: "Human"}, {species_sort_order: "1"}, {name_sort_order: "2"}
  • {species_sort_order: "1"}, {species: "Human"}, {name_sort_order: "2"}, {name: "John"}
  • {species: "Human"}, {species_sort_order: "1"}, {name: "John"}, {name_sort_order: "2"}

3) Если значение sort_order является пустой строкой («»), то у него нет позиции сортировки, и его размещение не имеет значения.

4) Желаемый результат будет выглядеть следующим образом

 <h2>Human</h2>
<ul>
  <li>John</li>
  <li>Susan</li>
</ul>
<h2>Dog</h2>
<ul>
  <li>Oreo</li>
</ul> 

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

До сих пор мой мыслительный процесс:

1)

 // Iterate through the list of hashmaps,
// if the index is dividable by 3 (i%3),
//     then push empty array
//     push hashmap to the last array in the array (e.g. ar[ar.length-1].push(hashmap))
// otherwise
//      push hashmap to the last array in the array (e.g. ar[ar.length-1].push(hashmap))
 
  1. Теперь у меня есть массив массивов, где каждый массив содержит связанные значения хэш-карты.
    Итак, мой следующий мыслительный процесс — начать сортировку массива в порядке
  • A) species_sort_order затем
  • B) name_sort_order… но это стало очень дорогостоящим алгоритмом с точки зрения временной сложности и просто не кажется эффективным.

РЕДАКТИРОВАТЬ: я знаю, что исходный ввод (массив объектов) усложняет задачу больше, чем нужно; но это данные, возвращаемые из ответа AJAX.

Я не могу использовать какие-либо внешние библиотеки, только JavaScript и его встроенные функции

Комментарии:

1. Этот вопрос заставляет меня задуматься, почему ваш массив объектов такой, какой он есть. Он помещает свойства, принадлежащие одному и тому же объекту, в отдельные объекты, что значительно усложняет сохранение данных при сортировке. Я бы сказал, сначала исправьте все, что генерирует эти данные. Тогда сортировка будет тривиальной.

2. может species_sort_order и name_sort_order иметь буквенное значение и не может быть изменено на целочисленные значения (или значения с плавающей запятой?)?

3. @jfriend00 Я знаю, что исходный ввод (массив объектов) усложняет задачу больше, чем нужно; но это данные, возвращаемые из ответа AJAX.

4. @MisterJojo нет, у них могут быть только целые числа в строковом формате или пустая строка. Вы определенно можете выполнить parseInt(), чтобы преобразовать «1» в 1

5. Логика порядка ключей неясна. Глядя на ваш вывод HTML, Human-John` имеет значения ключей 1-2 , Dog-oreo -> empty-2 и Human-Susan -> empty-empty . Как Dog и раньше Susan , и оба они имеют и имеют ключ для species_sort_order . Ваше объяснение указывает, что их взаимный порядок остается неизменным, что нарушает группировку видов. Если мы сортируем сначала по species , а затем по species_sort_order , группировка сохраняется, но в этом случае species:'Dog' перед «species:»Human», чего больше нет в вашем выводе html.

Ответ №1:

Я думаю, что Lodash может быть хорошим способом добиться этого

 const arr = [ {name: "John"}, {species: "Human"}, {species_sort_order: "1"}, {name_sort_order: "2"},
      {species: "Dog"}, {name: "oreo"}, {species_sort_order: ""}, {name_sort_order: "1"}, 
      {name_sort_order: ""}, {name: "Susan"}, {species: "Human"}, {species_sort_order: ""}]
    
_.orderBy(arr, ['name', 'species'], ['asc', 'asc'])
 

Комментарии:

1. К сожалению, я должен был заметить, что я не могу использовать какие-либо внешние библиотеки, только JavaScript и его встроенные функции

2. Звучит подозрительно, как проблема с домашним заданием

3. @Hive7 lol Я специализировался в области компьютерных наук, и я посещал полный учебный лагерь stack … поверьте мне, ни одна школа не задает такие сложные задачи! Это реальный мир, он приходит в ответ на запрос AJAX.

4. Похоже, это не относится к требуемой группировке.

Ответ №2:

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

 // utility functions
const chunk = (n) => (xs) =>
  xs .length <= n ? [[...xs]] : [xs .slice (0, n), ... chunk (n) (xs .slice (n))]

const group = (fn) => (xs) =>
  Object .values (xs .reduce (
    (a, x) => ({...a, [fn(x)]: [...(a[fn(x)] || []), x]}),
    {}
  ))


// helper function
const makeRecords = (xs) =>
  chunk (4) (xs) .map (gs => Object .assign (...gs))


// main function
const restructure = (array) => group ((x) => x.species) (makeRecords (array))
  .sort (([{species_sort_order: s1}], [{species_sort_order: s2}]) => (s1 || Infinity) - (s2 || Infinity))
  .map (s => s.sort (({name_sort_order: n1}, {name_sort_order: n2}) => (n1 || Infinity) - (n2 || Infinity)))


// sample data
const array = [{name: "John"}, {species: "Human"}, {species_sort_order: "1"}, {name_sort_order: "2"}, {species: "Dog"}, {name: "oreo"}, {species_sort_order: ""}, {name_sort_order: "1"}, {name_sort_order: ""}, {name: "Susan"}, {species: "Human"}, {species_sort_order: ""}]


// demo
const results = restructure (array)

document .getElementById ('output') .innerHTML = results .map (xs => `<h2>${xs[0].species}</h2><ul>${xs.map(x => `<li>${x.name}</li>`).join('')}</ul>`).join('')
document .getElementById ('result') .innerHTML = `<pre>${JSON.stringify(results, null, 4)}</pre>` 
 <div id="output"></div><hr/><h3>Transformed data</h1><pre id="result"></pre> 

Начнем с двух служебных функций:

  • chunk разбивает массив на n подмассивы длиной -long (при этом последний, возможно, короче).
     chunk (3) ([8, 6, 7, 5, 3, 0, 9])  //=> [[8, 6, 7], [5, 3, 0], [9]]`
     
  • group принимает функцию, возвращающую строку / число, и массив значений, группируя значения в подмассивы с тем же результатом при применении функции.
     group (n => n % 10) ([23, 12, 16, 33, 3, 42])
    //=> [[12, 42], [23, 33, 3], [16]]
    ``
    
     

Тогда у нас есть единственная вспомогательная функция:

  • makeRecords берет массив ваших входных элементов, разбивает их на группы по четыре и объединяет результат в элементы, которые выглядят следующим образом:
     {
        name: "John",
        species: "Human",
        species_sort_order: "1",
        name_sort_order: "2"
    }
     

Затем наша основная функция:

  • restructure принимает ваш ввод, превращает его в записи с makeRecords помощью, вызывает group группировку их по видам, затем сортирует виды в соответствии с первым species_sort_order в каждой группе и сортирует группы внутри по их name_sort_order s.

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


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

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

 const push = (x) => (xs = []) =>
  ((xs .push (x)), xs)

const set = (v) => (k) => (o = {}) =>
  ((o[k] = v), o)

const group = (fn) => (xs) =>
  Object .values (xs .reduce (
    (a, x) => set (push (x) (a [fn (x)])) (fn(x)) (a),
    {}
  ))

 

Ответ №3:

насколько это возможно из того, что я могу сделать…

 const keyS = (str,lg) => ((str||'')   ' '.repeat(lg)).substring(0, lg)
const keyN = (num,def,mask) => (!num) ? ((!def) ? '9'.repeat(mask.length) : `${mask}${def}`.slice(-mask.length)) : `${mask}${num}`.slice(-mask.length)

const data = 
  [ { name               : 'John'  } 
  , { species            : 'Human' } 
  , { species_sort_order : '1'     } 
  , { name_sort_order    : '2'     }
  , { species            : 'Dog'   } 
  , { name               : 'oreo'  } 
  , { species_sort_order : ''      } 
  , { name_sort_order    : '1'     }
  , { name_sort_order    : ''      } 
  , { name               : 'Susan' } 
  , { species            : 'Human' } 
  , { species_sort_order : ''      } 
  // , ... and so on
  ] 


, dataOrd = data.reduce((a,c,i,{[i 1]:next})=>
  {
  let ref = i%4, pos = Math.floor(i/4);
  if (!ref ) a.r.push({...c})
  else       a.r[pos] = {...a.r[pos],...c}    // group 4 by 4 objects

  if (ref===3) // === last item of 4
    {
    let grp = a.r[pos]
    a.nso = Math.max( a.nso, (grp.name_sort_order    || 0))  // get max
    a.sso = Math.max( a.sso, (grp.species_sort_order || 0)) // values amp;
    a.sLg = Math.max( a.sLg, (grp.species || '').length)   // max length

    if (grp.species_sort_order != '')             // memorize sort orders
      a.sKeys[grp.species] = grp.species_sort_order; // by species names
    }

  if (!!next) return a  // still in reduce loop
  else   // no next element -> reduce loop final              
    {
    let nsoMask = '0'.repeat( (  a.nso).toString(10).length)  // keys
      , ssoMask = '0'.repeat( (  a.sso).toString(10).length) // mask buiders
      ;
    a.r.forEach(({species, species_sort_order, name, name_sort_order},i,t) =>
        {
        t[i].key = keyN( species_sort_order, a.sKeys[species], ssoMask )
                  keyS( species, a.sLg )
                  keyN( name_sort_order, '', nsoMask )
        })
    a.r.sort((o1, o2) => o1.key.localeCompare(o2.key))

    // console.log(JSON.stringify(a,0,2))
    return a.r
    }
  },{ r:[], nso:0, sso:0, sLg:0, sKeys:{} })

// displaying HTML result
const divOutPut = document.querySelector('div#outPut')
let speciesGrp = ''
  , speciesElm = null
  , ulGrp      = null
  , liElm      = null

dataOrd.forEach(({species, name}) => 
  {
  if (speciesGrp != species)
    {
    speciesElm = divOutPut.appendChild(document.createElement('h2'))
    ulGrp      = divOutPut.appendChild(document.createElement('ul'))
    speciesElm.textContent = speciesGrp = species
    }
  liElm             = ulGrp.appendChild(document.createElement('li'))
  liElm.textContent = name
  }) 
 <div id="outPut"></div> 

Комментарии:

1. @ezg Я потратил время на разработку этого ответа, мне бы хотелось иметь хотя бы один комментарий…

2. @ezg это не только место для вопросов и ответов, но и место для обмена информацией, где также применяется сетевой этикет en.wikipedia.org/wiki/Etiquette_in_technology#Netiquette