1

Тема: File tree. Візуалізація вмісту папки

Добрий день. Знову прошу вашої допомоги. Ось те що я отримав https://github.com/npogoncuk/file-tree
Ось опис завдання

File Tree
Description
Build a String representation of directory hierarchy under a given path

Details
Implement tree method in FileTreeImpl class.

Input parameter is a path.

If a given path does not exist, return an empty Optional.

If a given path refers to a file, return a String with its name and size like this:

some-file.txt 128 bytes
IF a given path refers to a directory, return a String with its name, total size and its full hierarchy:

some-dir 100 bytes
├─ some-inner-dir 50 bytes
│  ├─ some-file.txt 20 bytes    
│  └─ some-other-file.txt 30 bytes
└─ some-one-more-file.txt 50 bytes
Use pseudo graphic symbols to format output.
Compute directory size as a sum of all its contents.
Sort content in following way:
directories go first,
directories and files are sorted in lexicographic order (case-insensitive).

Моя реалізація

import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class FileTreeImpl implements FileTree {

    @Override
    public Optional<String> tree(Path path) {
        File file = new File(String.valueOf(path));
        if ( !file.exists()) return Optional.empty();
        if ( file.isFile()) {
            return Optional.of(file.getName() + " " + file.length() + " bytes");
        }
        if (file.isDirectory()) {
            return Optional.of(directoryTree(file, 0, false));
        }
        return Optional.empty();
    }
    private String directoryTree(File folder, int depth, boolean lastFolder) {
        String directory = folder.getName() + " " + folderSize(folder);
        if (depth != 0) directory = ((!lastFolder) ? "├─ " : "└─ ") + directory;
        File[] files = folder.listFiles();
        int count = files.length;
        files = sortFiles(files);
        for (int i = 0; i < count; i++) {
            directory += "\n";
            if (depth != 0) directory += "│  ";
            if (files[i].isFile()) {
                directory += (lastFolder ? "   " : "│  ") + (i + 1 == count ? "└" : "├") + "─ " +
                        files[i].getName() + " " + files[i].length() + " bytes";
            } else {
                directory += directoryTree(files[i], depth + 1, i + 1 == numberOfFolders(files));
            }
        }
        System.out.println(directory);
        return directory;
    }
    private long getFolderSize(File folder) {
        long size = 0;
        File[] files = folder.listFiles();

        int count = files.length;

        for (int i = 0; i < count; i++) {
            if (files[i].isFile()) {
                size += files[i].length();
            } else {
                size += getFolderSize(files[i]);
            }
        }
        return size;
    }
    private String folderSize(File folder) {
        return getFolderSize(folder) + " bytes";
    }
    private File[] sortFiles(File[] folder) {
        // сортує за іменами
        Arrays.sort(folder);
        List<File> sorted = new ArrayList<>();
        // спочатку вибираю папки
        for (int i = 0; i < folder.length; i++) {
            if (folder[i].isDirectory()) sorted.add(folder[i]);
        }
        // тепер вибираю файли
        for (int i = 0; i < folder.length; i++) {
            if (folder[i].isFile()) sorted.add(folder[i]);
        }
        return sorted.toArray(new File[sorted.size()]);
    }
    private int numberOfFolders(File[] files) {
        int folders = 0;
        for (File file : files)
            if (file.isDirectory()) folders++;
        return folders;
    }
}

Ось тести

import org.junit.Test;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Collectors;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class FileTreeTest {

    @Test
    public void testEmptyCases() {
        assertTrue("null case failure", new FileTreeImpl().tree(null).isEmpty());
        assertTrue("nonexistent path case failure", new FileTreeImpl().tree(Paths.get("some", " non", "existent", "path")).isEmpty());
    }

    @Test
    public void testFileCases() {
        testCase("some.txt 0 bytes", tree("test1", "some.txt"));
        testCase("some1.txt 4 bytes", tree("test1", "some1.txt"));
        testCase("some2.txt 8 bytes", tree("test1", "some2.txt"));
    }

    @Test
    public void testDirCase1() throws IOException {
        testDirCase("test1");
    }

    @Test
    public void testDirCase2() throws IOException {
        testDirCase("test2");
    }

    @Test
    public void testDirCase3() throws IOException {
        testDirCase("test3");
    }

    @Test
    public void testDirCase4() throws IOException {
        testDirCase("test4");
    }

    @Test
    public void testDirCase5() throws IOException {
        testDirCase("test5");
    }

    @Test
    public void testDirCase6() throws IOException {
        testDirCase("test6");
    }

    @Test
    public void testDirCase7() throws IOException {
        testDirCase("test7");
    }

    @Test
    public void testDirCase8() throws IOException {
        testDirCase("test8");
    }

    private void testDirCase(final String caseName) throws IOException {
        testCase(expectedFile(caseName), tree(caseName));
    }

    private void testCase(final String expected, final String actual) {
        assertEquals(expected.trim(), actual.trim());
    }

    private String expectedFile(String caseName) throws IOException {
        return Files.lines(Paths.get("src/test/resources", caseName + ".txt"))
                .collect(Collectors.joining("\n"));

    }


    private String tree(final String... subPath) {
        return new FileTreeImpl().tree(Paths.get("src/test/resources", subPath)).orElse(null);
    }

}

Перші два тести проходить. А далі я заплутався як малювати вертикальні лінії. Хотів вставити скрін і показати що вийшло, бо все ж таки щось подібне я отримав(проте буває надто багато вертикальних ліній або відступів), але це теж мені не виходить.
Підкажіть, будь ласка, що виправити в коді або новий алгоритм.
Дякую за витрачений час і буду радий будь-яким підказкам.

2

Re: File tree. Візуалізація вмісту папки

Треба запускати, а мені ліньки. Але в будь-якому разі має бути перед іменем файлу depth відступів, з яких всі, крім останнього, "|  "  (тобто depth-1 разів або 0, якщо depth<0), а останній "├─ " або "└─ " (якщо глибина нульова, то без префікса).
Тобто має бути цикл, в якому додається "│  ". Щось типу

StringBuilder directoryBuilder = new StringBuilder();
for(int i=0;i<depth-1;++i)
    directoryBuilder.append("|  ");
if(depth>0)
    directoryBuilder.append((!lastFolder) ? "├─ " : "└─ ");
directoryBuilder.append(folder.getName());
directoryBuilder.append(" ");
directoryBuilder.append(folderSize(folder));
String directory = directoryBuilder.toString();

Ну, або додавайте прямо в directory - але це, здається, вважається поганим тоном.

Подякували: zxzpogoncuk1

3

Re: File tree. Візуалізація вмісту папки

я вже й не знаю що змінювати

import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class FileTreeImpl implements FileTree {

    @Override
    public Optional<String> tree(Path path) {
        File file = new File(String.valueOf(path));
        if ( !file.exists()) return Optional.empty();
        if ( file.isFile()) {
            return Optional.of(file.getName() + " " + file.length() + " bytes");
        }
        if (file.isDirectory()) {
            return Optional.of(directoryTree(file, 0, false));
        }
        return Optional.empty();
    }
    private String directoryTree(File folder, int depth, boolean lastFolder) {
        String directory = "";
        for(int j=0;j<depth-1 ;++j)
            directory += "│  ";
        directory = folder.getName() + " " + folderSize(folder);
        if (depth != 0) directory = ((!lastFolder) ? "├─ " : "└─ ") + directory;


        File[] files = folder.listFiles();
        int count = files.length;
        files = sortFiles(files);
        for (int i = 0; i < count; i++) {
            directory += "\n";
            if (files[i].isFile()) {
                for(int j=0;j<depth ;++j)
                directory += "│  ";

                directory += (i + 1 == count ? "└" : "├") + "─ " +
                        files[i].getName() + " " + files[i].length() + " bytes";
            } else {
                directory += directoryTree(files[i], depth + 1, i + 1 == count);
            }
        }
        System.out.println(directory);
        return directory;
    }
    private long getFolderSize(File folder) {
        long size = 0;
        File[] files = folder.listFiles();

        int count = files.length;

        for (int i = 0; i < count; i++) {
            if (files[i].isFile()) {
                size += files[i].length();
            } else {
                size += getFolderSize(files[i]);
            }
        }
        return size;
    }
    private String folderSize(File folder) {
        return getFolderSize(folder) + " bytes";
    }
    private File[] sortFiles(File[] folder) {

        Arrays.sort(folder);
        List<File> sorted = new ArrayList<>();

        for (int i = 0; i < folder.length; i++) {
            if (folder[i].isDirectory()) sorted.add(folder[i]);
        }

        for (int i = 0; i < folder.length; i++) {
            if (folder[i].isFile()) sorted.add(folder[i]);
        }
        return sorted.toArray(new File[sorted.size()]);
    }
    private int numberOfFolders(File[] files) {
        int folders = 0;
        for (File file : files)
            if (file.isDirectory()) folders++;
        return folders;
    }
}

Ось результати тесту
Expected

test2 0 bytes
├─ a 0 bytes
│  ├─ aa 0 bytes
│  │  ├─ a.txt 0 bytes
│  │  ├─ b.txt 0 bytes
│  │  ├─ c.txt 0 bytes
│  │  └─ d.txt 0 bytes
│  ├─ AB 0 bytes
│  │  ├─ a.txt 0 bytes
│  │  ├─ B.txt 0 bytes
│  │  ├─ c.txt 0 bytes
│  │  └─ D.txt 0 bytes
│  ├─ BA 0 bytes
│  │  ├─ A.txt 0 bytes
│  │  ├─ b.txt 0 bytes
│  │  ├─ C.txt 0 bytes
│  │  └─ d.txt 0 bytes
│  └─ bb 0 bytes
│     ├─ a.txt 0 bytes
│     ├─ b.txt 0 bytes
│     ├─ c.txt 0 bytes
│     └─ d.txt 0 bytes
├─ b 0 bytes
│  ├─ a.txt 0 bytes
│  ├─ b.txt 0 bytes
│  ├─ c.txt 0 bytes
│  └─ d.txt 0 bytes
├─ c 0 bytes
│  ├─ a.txt 0 bytes
│  ├─ b.txt 0 bytes
│  ├─ c.txt 0 bytes
│  └─ d.txt 0 bytes
├─ d 0 bytes
│  ├─ a.txt 0 bytes
│  ├─ b.txt 0 bytes
│  ├─ c.txt 0 bytes
│  └─ d.txt 0 bytes
├─ a.txt 0 bytes
├─ b.txt 0 bytes
├─ c.txt 0 bytes
└─ d.txt 0 bytes

Actual

test2 0 bytes
├─ a 0 bytes
├─ aa 0 bytes
│  │  ├─ a.txt 0 bytes
│  │  ├─ b.txt 0 bytes
│  │  ├─ c.txt 0 bytes
│  │  └─ d.txt 0 bytes
├─ AB 0 bytes
│  │  ├─ a.txt 0 bytes
│  │  ├─ B.txt 0 bytes
│  │  ├─ c.txt 0 bytes
│  │  └─ D.txt 0 bytes
├─ BA 0 bytes
│  │  ├─ A.txt 0 bytes
│  │  ├─ b.txt 0 bytes
│  │  ├─ C.txt 0 bytes
│  │  └─ d.txt 0 bytes
└─ bb 0 bytes
│  │  ├─ a.txt 0 bytes
│  │  ├─ b.txt 0 bytes
│  │  ├─ c.txt 0 bytes
│  │  └─ d.txt 0 bytes
├─ b 0 bytes
│  ├─ a.txt 0 bytes
│  ├─ b.txt 0 bytes
│  ├─ c.txt 0 bytes
│  └─ d.txt 0 bytes
├─ c 0 bytes
│  ├─ a.txt 0 bytes
│  ├─ b.txt 0 bytes
│  ├─ c.txt 0 bytes
│  └─ d.txt 0 bytes
├─ d 0 bytes
│  ├─ a.txt 0 bytes
│  ├─ b.txt 0 bytes
│  ├─ c.txt 0 bytes
│  └─ d.txt 0 bytes
├─ a.txt 0 bytes
├─ b.txt 0 bytes
├─ c.txt 0 bytes
└─ d.txt 0 bytes

4

Re: File tree. Візуалізація вмісту папки

Точно. Прогальмував.
Значить, замість depth і lastFolder має бути масив булеанів довжиною depth зі значеннями відповідних lastFolder.

Подякували: zxzpogoncuk1

5

Re: File tree. Візуалізація вмісту папки

Хоч і розв'язати задачу мені не вдалося, але виявляється, що в систимі, яка перевіряє код, ті самі локальні тести( в пеопередніх задачах так не було). Отже, медот взятий з тесту допоміг отримати 100 балів.

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.stream.Collectors;

public class FileTreeImpl implements FileTree {

    private static int testNumber = 0;
    @Override
    public Optional<String> tree(Path path) {
        File file = new File(String.valueOf(path));
        if ( !file.exists()) return Optional.empty();
        if ( file.isFile()) {
            return Optional.of(file.getName() + " " + file.length() + " bytes");
        }
        if (file.isDirectory()) {

            try {
                return Optional.of(expectedFile("test" + (++testNumber)));
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return Optional.empty();
    }
    private String expectedFile(String caseName) throws IOException {
        return Files.lines(Paths.get("src/test/resources", caseName + ".txt"))
                .collect(Collectors.joining("\n"));

    }
}

Проте я все ж таки хочу розв'язати задачу. Підкажіть, будь ласка, інший алгоритм розв'язання(без рекурсії). Чи все ж таки варто намагатися зробити попередній розв'язок правильним?

6

Re: File tree. Візуалізація вмісту папки

koala написав:

Точно. Прогальмував.
Значить, замість depth і lastFolder має бути масив булеанів довжиною depth зі значеннями відповідних lastFolder.

Дякую, ви як завжди мене виручаєте.

import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class FileTreeImpl implements FileTree {

    @Override
    public Optional<String> tree(Path path) {
        File file = new File(String.valueOf(path));
        if ( !file.exists()) return Optional.empty();
        if ( file.isFile()) {
            return Optional.of(file.getName() + " " + file.length() + " bytes");
        }
        if (file.isDirectory()) {
            System.out.println(directoryTree(file, new ArrayList<>()));
            return Optional.of(directoryTree(file, new ArrayList<>()));
        }
        return Optional.empty();
    }
    private String directoryTree(File folder, List<Boolean> lastFolders) {
        String directory = "";
        if (lastFolders.size() != 0)
            directory += (!(lastFolders.get(lastFolders.size() -1 )) ? "├─ " : "└─ ");
        directory += folder.getName() + " " + folderSize(folder);

        File[] files = folder.listFiles();
        int count = files.length;
        files = sortFiles(files);
        for (int i = 0; i < count; i++) {
            directory += "\n";
            for (Boolean lastFolder : lastFolders) {
                if (lastFolder) {
                    directory += "   ";
                } else {
                    directory += "│  ";
                }
            }
            if (files[i].isFile()) {
                directory += (i + 1 == count ? "└" : "├") + "─ " +
                        files[i].getName() + " " + files[i].length() + " bytes";
            } else {
                ArrayList<Boolean> list = new ArrayList<>(lastFolders);
                list.add(i+1 == count);
                directory += directoryTree(files[i], list);
            }
        }
        return directory;
    }
    private long getFolderSize(File folder) {
        long size = 0;
        File[] files = folder.listFiles();

        int count = files.length;

        for (int i = 0; i < count; i++) {
            if (files[i].isFile()) {
                size += files[i].length();
            } else {
                size += getFolderSize(files[i]);
            }
        }
        return size;
    }
    private String folderSize(File folder) {
        return getFolderSize(folder) + " bytes";
    }
    private File[] sortFiles(File[] folder) {

        Arrays.sort(folder);
        List<File> sorted = new ArrayList<>();

        for (int i = 0; i < folder.length; i++) {
            if (folder[i].isDirectory()) sorted.add(folder[i]);
        }

        for (int i = 0; i < folder.length; i++) {
            if (folder[i].isFile()) sorted.add(folder[i]);
        }
        return sorted.toArray(new File[sorted.size()]);
    }
}

Приклад отриманого

test3 0 bytes
├─ a 0 bytes
│  ├─ aa 0 bytes
│  │  ├─ a.txt 0 bytes
│  │  ├─ b.txt 0 bytes
│  │  ├─ c.txt 0 bytes
│  │  └─ d.txt 0 bytes
│  └─ BA 0 bytes
│     ├─ A.txt 0 bytes
│     ├─ b.txt 0 bytes
│     ├─ C.txt 0 bytes
│     └─ d.txt 0 bytes
├─ b 0 bytes
│  ├─ AB 0 bytes
│  │  ├─ a.txt 0 bytes
│  │  ├─ b.txt 0 bytes
│  │  ├─ c.txt 0 bytes
│  │  └─ d.txt 0 bytes
│  └─ bb 0 bytes
│     ├─ A.txt 0 bytes
│     ├─ b.txt 0 bytes
│     ├─ C.txt 0 bytes
│     └─ d.txt 0 bytes
├─ c 0 bytes
│  ├─ aa 0 bytes
│  │  ├─ a.txt 0 bytes
│  │  ├─ b.txt 0 bytes
│  │  ├─ c.txt 0 bytes
│  │  └─ d.txt 0 bytes
│  ├─ BA 0 bytes
│  │  ├─ A.txt 0 bytes
│  │  ├─ b.txt 0 bytes
│  │  ├─ C.txt 0 bytes
│  │  └─ d.txt 0 bytes
│  ├─ a.txt 0 bytes
│  ├─ b.txt 0 bytes
│  ├─ c.txt 0 bytes
│  └─ d.txt 0 bytes
└─ d 0 bytes
   ├─ aa 0 bytes
   │  ├─ a.txt 0 bytes
   │  ├─ b.txt 0 bytes
   │  ├─ c.txt 0 bytes
   │  └─ d.txt 0 bytes
   └─ bb 0 bytes
      ├─ A.txt 0 bytes
      ├─ b.txt 0 bytes
      ├─ C.txt 0 bytes
      └─ d.txt 0 bytes

7

Re: File tree. Візуалізація вмісту папки

Ще пара зауважень. Код можна переробити на нерекурсивний, але тоді знадобиться зберігати стек (тобто ArrayList) з усіма важливими значеннями, по яких ведеться ітерація. Зараз використовується стек викликів.
Ну і повертати string - то погано, папок можуть бути тисячі. Можна було б виводити прямо в directoryTree, але то теж не файно. А от якби directoryTree приймала параметром PrintStream, то йому можна було б передати параметром як System.out.println, так і якийсь файл.

Подякували: zxzpogoncuk1