1

Using JavaScript, I have an array of file paths that I'm looping through and checking. If the current item is a folder, I want to bump it to the back of the line and move on to the next item. The end result would be files in front, folders after the last file.

I know this should be easy, but I've been fighting with it for 2 days. I've tried various versions of the following code with no success. For the purpose of this project, I'm assuming it's a folder if there's no period present in the filename.

function sortArray(array) {

    console.log('Original array: ' + array);
    var z = array.length;
    console.log('Array length is ' + z);

    for(i = 0; i < z-1; i++)
    { 
        var n = array.indexOf(array[i]);


        if (array[i].indexOf('.') === -1)
        {
            console.log('Item is folder');
            var fldr = array.splice(n, 1);
            array.push(fldr);
        }
    }

    console.log('Sorted array: ' + array);
}
2
  • 2
    Just use the native .sort() and pass a comparator that compares by whether the elements have "." in the name (which is possible for folders too, of course, but whatever). Commented Aug 8, 2014 at 19:23
  • Wow - lots of good answers here. I'll try some of these out and update. I like the idea of giving files & folders separate arrays. That would eliminate some of the complex logic I have to use in a later function. Commented Aug 8, 2014 at 20:26

6 Answers 6

5

The short answer is that by calling splice followed by push, you're adding the element back onto the end (tail) of the array, when in reality you'd want to move it to the front (head) using unshift.

But there are other, simpler approaches you could take. For example, you could just sort the array using sort with a custom comparison function:

array.sort(function(x, y) {
  var xFolder = x.indexOf('.') == -1 ? 1 : 0;
  var yFolder = y.indexOf('.') == -1 ? 1 : 0;
  return yFolder - xFolder;
});

This will quickly sort the array, putting all the folders (based on your "no period means it's a folder" criterion) first.

Another option would be to return a new array rather than modify the current one in place. This would be easy to implement:

function putFoldersFirst(entries) {
  var folders = [],
      files = [];

  for (var i = 0; i < entries.length; ++i) {
    if (entries[i].indexOf('.') == -1) {
      folders.push(entries[i]);
    } else {
      files.push(entries[i]);
    }
  }

  return folders.concat(files);
}

As always, there's more than one way.

Sign up to request clarification or add additional context in comments.

3 Comments

Since the sort isn't guaranteed to be stable anyway, you can just sort by return b.indexOf(".") - a.indexOf("."); to push all non-files to the end. Files will be sorted by the position of their first ., which is no worse than the otherwise-random version above. Example: codepen.io/paulroub/pen/Jepqg
@PaulRoub: Good point. Really any of the solutions proposed here I would consider fine.
Not sure who answered first, but this is basically the approach I took so I chose this as the answer. I was able to sort the files and folders off into two new arrays, then work off those.
0

You should use different arrays for looping and for modifying. This is something that has tripped me up in the past plenty of times. As soon as you move something, the index of your loop, or the iterator, isn't guaranteed to point to the correct item anymore.

Just create a temparray and while you iterate the original array, check the correct location in the temparray and copy the value from the original array there, and when you're done, return the sorted array.

That is, of course, if you do want to do it manually and not use a built-in sort as suggested above.

Comments

0

Alternatively you could use Array.prototype.filter to filter out the items that have a '.' and the items that don't, and concatenate them:

var items = [ 'a', 'b.c', 'adf', 'd.d', 'a.a' ];

var sortedItems = items.filter( function( item ) {
    return item.indexOf( '.' ) !== -1;
  } ).concat( items.filter( function( item ) {
    return item.indexOf( '.' ) === -1;
  } )
);

// you get: ["b.c", "d.d", "a.a", "a", "adf"]

Comments

0

There are many ways to do this, but here's one using Array.prototype.sort.

var filesAndFolders = ['file1.txt', 'folder2', 'folder1', 'file2.txt'];

function sortFilesAndFolders(arr) {
    return arr.sort(function (a, b) {
        var aIsFolder = isFolder(a),
            bIsFolder = isFolder(b);

        return aIsFolder !== bIsFolder? 
            sortByType(aIsFolder) : 
            sortByName(a, b);
    });

    function sortByType(aIsFolder) { return aIsFolder? 1 : -1; }
    function sortByName(a, b) { return a < b? -1 : +(a > b); }
    function isFolder(item) { return item.indexOf('.') === -1; }
}

sortFilesAndFolders(filesAndFolders);
//["file1.txt", "file2.txt", "folder1", "folder2"]

Comments

0

If original order of files and folders matters to you, you could use this function:

function sortFilesAndFolders (originalArray) {
    var files = [], folders = [];

    originalArray.forEach(function (item) {
        if (item.indexOf(".") === -1) {
            folders.push(item);
        } else {
            files.push(item);
        }
    });

    return folders.concat(files);
}

demo: http://jsfiddle.net/4Lnwz4jh/

Comments

0

There can be many approaches for this task. If you just want to correct your code then here's the solution: When you use splice, the whole array to the right of the deleted element is shifted left, and as you are increasing i, the element that comes at the current position is missed. Therefore, you should not increase i in that case. Another issue: It should be z in place of z-1 and array.splice(i, 1) instead of array.splice(n, 1)

function sortArray(array) {
    console.log('Original array: ' + array);
    var z = array.length;
    console.log('Array length is ' + z);
    var i = 0;
    var sortedCount = 0;      // Count of the number of elements already sorted.
    for(sortedCount = 0 = 0; sortedCount < z; sortedCount++)
    { 
        var n = array.indexOf(array[i]);
        if (array[i].indexOf('.') === -1)
        {
            console.log('Item is folder');
            var fldr = array.splice(i, 1);
            array.push(fldr);
        }
        else i++;
    }
    console.log('Sorted array: ' + array);
}

Another approach could be to sort the array using sort method with a custom comparison function:

array.sort(function(filePathOne, filePathTwo) {
    var oneIsFile = FilePathOne.indexOf('.') == -1 ? 1 : 0;
    var twoIsFile = FilePathTwo.indexOf('.') == -1 ? 1 : 0;
    return twoIsFile - oneIsFile;      // File comes before folder
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.