javascript - 对 Javascript 树进行排序

标签 javascript sorting tree

我有一些具有深度的数据,我需要渲染显示按深度嵌套的数据。呈现数据后,需要对其进行排序,以便每个“深度”独立排序但仍与其父级相连。

到目前为止,我已经设法将深度转换为树状对象数组,并可以基于它进行渲染。我已经进行了排序,但无法弄清楚如何使子元素相对于父元素保持不变。

这是我到目前为止编写的代码:

var data = getData();

// convert the data to a tree structure
var tree = convertToTree(data);


$(function($) {

  init();


  function init() {
    renderTree(tree);
  }

  function renderTree(tree) {
    var $table = $('#tree1');

    var rows = [];

    function renderTreeRow(row) {
      if (row.hasOwnProperty('score')) {
        rows.push([
          '<tr class="depth_' + row.depth + '">',
          '  <td>' + row.label + '</td>',
          '  <td>' + row.row + '</td>',
          '  <td>' + row.depth + '</td>',
          '  <td>' + row.score + '</td>',
          '  <td>' + row.count + '</td>',
          '</tr>'
        ]);
      } else {
        rows.push([
          '<tr class="depth_' + row.depth + ' group_heading">',
          '  <td colspan="5">' + row.label + '</td>',
          '</tr>'
        ]);
      }
      if (row.hasOwnProperty('children') && row.children.length) {
        $.each(row.children, function(i, childRow) {
          renderTreeRow(childRow);
        });
      }
    }

    $.each(tree, function(i, row) {
      renderTreeRow(row);
    });

    $table.find('tbody tr').remove();
    $('tbody', $table).append(rows.join(''));
  }

  $('th[data-sort-asc]').off('.data_sort_asc').on('click.data_sort_asc', function(event) {
      var target = $(event.target).closest('th');
      var sortAscending = target.attr('data-sort-asc') == "true";
      var property = target.attr('data-sort-property');
      var treeSorter = sortByProperty(property, sortAscending);
      treeSorter(tree);
      renderTree(tree);
      // flip sort direction
      target.attr('data-sort-asc', !sortAscending);
      target.find('span').remove().end().append('<span class="glyphicon glyphicon-sort-by-attributes' + (sortAscending ? '' : '-alt') + '" aria-hidden="true"></span>');
    }).append('<span class="glyphicon glyphicon-sort" aria-hidden="true"></span>')
    .css({
      'cursor': 'pointer'
    });

});



function sortByProperty(property, ascending) {
  ascending = ascending !== undefined ? ascending : true;
  var flip = ascending ? 1 : -1;

  function sortTree(arr) {
    function compare(a, b) {
      if (a[property] < b[property]) {
        return -1 * flip;
      }
      if (a[property] > b[property]) {
        return 1 * flip;
      }
      return 0;
    }
    $.each(arr, function(i, el) {
      if (el.hasOwnProperty('children')) {
        sortTree(el.children);
      }
    });
    arr.sort(compare);
  }
  return sortTree;
}

/*
function flatternTree(tree) {
	var ret = [];
	$.each(tree, function(i, row) {
		ret.push(row);
		if(row.hasOwnProperty('children') && row.children.length) {
			ret = ret.concat(flatternTree(row.children));
		}
	});
	return ret;
}
*/

function convertToTree(arr) {
  return treeify(splitToSegments(arr));
}

function splitToSegments(arr) {
  var result = [];
  var accum, segment;

  $.each(arr, function(i, it) {
    if (it.depth === 0) {
      accum = true;
      if (segment && segment.length) {
        result.push(segment);
      }
      segment = [it];
    } else if (accum) {
      segment.push(it);
    }
  });

  if (segment && segment.length) {
    result.push(segment);
  }

  return result;
}

function treeify(arr) {
  return arr.map(function(o) {
    return o.reduce(function(a, b) {
      var lastChild;
      a.children = a.children || [];
      if (a.depth + 1 === b.depth) {
        a.children.push(b);
      } else {
        lastChild = a.children[a.children.length - 1];
        lastChild.children = lastChild.children || [];
        lastChild.children.push(b);
      }
      return a;
    });
  });
}


function getData() {
  // rows of data that we get, we have depth but not nested as parent>child relationships. The order & depth determines parent>child relationship
    var payload = [
    {row: 1, group: "group_30", "depth": 0, "label": "TOP"},
    {row: 2, group: "group_30", "depth": 1, "score": 10, "count": 600, "label": "A1"},
    {row: 3, group: "group_30", "depth": 2, "score": 10, "count": 100, "label": "A1>2-1"},
    {row: 4, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "A1>2-1>3-1"},
    {row: 5, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "A1>2-1>3-2"},
    {row: 6, group: "group_30", "depth": 2, "score": 20, "count": 200, "label": "A1>2-2"},
    {row: 7, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "A1>2-2>3-1"},
    {row: 8, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "A1>2-2>3-2"},
    {row: 9, group: "group_30", "depth": 2, "score": 30, "count": 300, "label": "A1>2-3"},
    {row: 10, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "A1>2-3>3-1"},
    {row: 11, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "A1>2-3>3-2"},
    {row: 12, group: "group_30", "depth": 1, "score": 10000, "count": 10, "label": "B1"},
    {row: 13, group: "group_30", "depth": 2, "score": 1000, "count": 1, "label": "B1>2-1"},
    {row: 14, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "B1>2-1>3-1"},
    {row: 15, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "B1>2-1>3-2"},
    {row: 16, group: "group_30", "depth": 2, "score": 3000, "count": 3, "label": "B1>2-2"},
    {row: 17, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "B1>2-2>3-1"},
    {row: 18, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "B1>2-2>3-2"},
    {row: 19, group: "group_30", "depth": 2, "score": 6000, "count": 6, "label": "B1>2-3"},
    {row: 20, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "B1>2-3>3-1"},
    {row: 21, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "B1>2-3>3-2"}
  ];
  return payload;
}
/* Styles go here */

.depth_0 {
  background-color: #B2A788;
}
.depth_1 {
  background-color: #7686B2;
}
.depth_2 {
  background-color: #9BC2CC;
}
.depth_3 {
  background-color: #DCE5FF;
}
.group_heading td {
  border-bottom: 3px solid #BFB8A5
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<p>Clicking the headers should sort each 'grouping' independantly, so that child elements appear directly after the parent. When the parent is sorted the child elements should move with it. Thanks!</p>

<table id="tree1" class="table">
  <thead>
    <tr>
      <th>label</th>
      <th data-sort-asc="true" data-sort-property="row">row</th>
      <th data-sort-asc="true" data-sort-property="depth">depth</th>
      <th data-sort-asc="true" data-sort-property="score">score</th>
      <th data-sort-asc="true" data-sort-property="count">count</th>
    </tr>
  </thead>
  <tbody>

  </tbody>
</table>

感谢您的任何想法!

最佳答案

回答我自己的问题 - 我希望有一种更优雅的方法来做到这一点,但张贴以防它帮助其他人。欢迎提出改进建议。

var data = getData();

// convert the data to a tree structure
var tree = convertToTree(data);


$(function($) {

  init();


  function init() {
    renderTree(tree);
  }

  function renderTree(tree) {
    var $table = $('#tree1');

    var rows = [];

    function renderTreeRow(row) {
      if (row.hasOwnProperty('score')) {
        rows.push([
          '<tr class="depth_' + row.depth + '">',
          '  <td>' + row.label + '</td>',
          '  <td>' + row.row + '</td>',
          '  <td>' + row.depth + '</td>',
          '  <td>' + row.score + '</td>',
          '  <td>' + row.count + '</td>',
          '</tr>'
        ]);
      } else {
        rows.push([
          '<tr class="depth_' + row.depth + ' group_heading">',
          '  <td colspan="5">' + row.label + '</td>',
          '</tr>'
        ]);
      }
      if (row.hasOwnProperty('children') && row.children.length) {
        $.each(row.children, function(i, childRow) {
          renderTreeRow(childRow);
        });
      }
    }

    $.each(tree, function(i, row) {
      renderTreeRow(row);
    });

    $table.find('tbody tr').remove();
    $('tbody', $table).append(rows.join(''));
  }

  $('th[data-sort-asc]').off('.data_sort_asc').on('click.data_sort_asc', function(event) {
      var target = $(event.target).closest('th');
      var sortAscending = target.attr('data-sort-asc') == "true";
      var property = target.attr('data-sort-property');
      var treeSorter = sortByProperty(property, sortAscending);
      treeSorter(tree);
      renderTree(tree);
      // flip sort direction
      target.attr('data-sort-asc', !sortAscending);
      target.find('span').remove().end().append('<span class="glyphicon glyphicon-sort-by-attributes' + (sortAscending ? '' : '-alt') + '" aria-hidden="true"></span>');
    }).append('<span class="glyphicon glyphicon-sort" aria-hidden="true"></span>')
    .css({
      'cursor': 'pointer'
    });

});



function sortByProperty(property, ascending) {
  ascending = ascending !== undefined ? ascending : true;
  var flip = ascending ? 1 : -1;

  function sortTree(arr) {
    function compare(a, b) {
      if (a[property] < b[property]) {
        return -1 * flip;
      }
      if (a[property] > b[property]) {
        return 1 * flip;
      }
      return 0;
    }
    $.each(arr, function(i, el) {
      if (el.hasOwnProperty('children')) {
        sortTree(el.children);
      }
    });
    arr.sort(compare);
  }
  return sortTree;
}

function convertToTree(arr) {
  var result = [];
  var previous;
  var lastDepth = [];

  $.each(arr, function(i, it) {
    previous = arr[i-1]; 
    if (it.depth === 0) { // top level
      result.push(it);
    }
    else if (it.depth > previous.depth) {
      // child of previous
      previous.children = previous.children || [];
      previous.children.push(it);
    }
    else {
      lastDepth[it.depth-1].children = lastDepth[it.depth-1].children || [];
      lastDepth[it.depth-1].children.push(it);
    }
    lastDepth[it.depth] = it;
  });

  return result;
}

function getData() {
  // rows of data that we get, we have depth but not nested as parent>child relationships. The order & depth determines parent>child relationship
    var payload = [
    {row: 1, group: "group_30", "depth": 0, "label": "TOP"},
    {row: 2, group: "group_30", "depth": 1, "score": 10, "count": 600, "label": "A1"},
    {row: 3, group: "group_30", "depth": 2, "score": 10, "count": 100, "label": "A1>2-1"},
    {row: 4, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "A1>2-1>3-1"},
    {row: 5, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "A1>2-1>3-2"},
    {row: 6, group: "group_30", "depth": 2, "score": 20, "count": 200, "label": "A1>2-2"},
    {row: 7, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "A1>2-2>3-1"},
    {row: 8, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "A1>2-2>3-2"},
    {row: 9, group: "group_30", "depth": 2, "score": 30, "count": 300, "label": "A1>2-3"},
    {row: 10, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "A1>2-3>3-1"},
    {row: 11, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "A1>2-3>3-2"},
    {row: 12, group: "group_30", "depth": 1, "score": 10000, "count": 10, "label": "B1"},
    {row: 13, group: "group_30", "depth": 2, "score": 1000, "count": 1, "label": "B1>2-1"},
    {row: 14, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "B1>2-1>3-1"},
    {row: 15, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "B1>2-1>3-2"},
    {row: 16, group: "group_30", "depth": 2, "score": 3000, "count": 3, "label": "B1>2-2"},
    {row: 17, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "B1>2-2>3-1"},
    {row: 18, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "B1>2-2>3-2"},
    {row: 19, group: "group_30", "depth": 2, "score": 6000, "count": 6, "label": "B1>2-3"},
    {row: 20, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "B1>2-3>3-1"},
    {row: 21, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "B1>2-3>3-2"}
  ];
  return payload;
}
/* Styles go here */

.depth_0 {
  background-color: #B2A788;
}
.depth_1 {
  background-color: #7686B2;
}
.depth_2 {
  background-color: #9BC2CC;
}
.depth_3 {
  background-color: #DCE5FF;
}
.group_heading td {
  border-bottom: 3px solid #BFB8A5
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>


<table id="tree1" class="table">
  <thead>
    <tr>
      <th>label</th>
      <th data-sort-asc="true" data-sort-property="row">row</th>
      <th data-sort-asc="true" data-sort-property="depth">depth</th>
      <th data-sort-asc="true" data-sort-property="score">score</th>
      <th data-sort-asc="true" data-sort-property="count">count</th>
    </tr>
  </thead>
  <tbody>

  </tbody>
</table>

关于javascript - 对 Javascript 树进行排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34017011/

相关文章:

java - 查找此特定快速排序算法中的比较次数

java - 构建叶图 - 成对的祖先列表(父 - 子)

java - 创建字符树

php - 在 PHP 中通过树结构递归的特定算法

javascript - 将数据插入两个表,结果为 "Array to string conversion"

javascript - 无法设置未定义的属性 'props'

java - 使用 Collections.sort 和 SizeFileComparator 对 ArrayLlist<File> 中的 File 对象进行排序

javascript - 如果 JavaScript 中的特定单词匹配,则替换行

javascript - 是否有一个指令允许在选择选项后隐藏选择元素?

algorithm - 排序算法稳定或不稳定的原因是什么?