A 회사는 테스트와 운영으로 시스템을 분리하여 프로그램을 개발하고 있다.
테스트는 Test라는 디렉토리, 운영은 Real이라는 디렉토리로 시작한다.
그 하위 파일이나 디렉토리 구조는 서로 동일하다.
(디렉토리 구조 및 파일은 테스트와 운영이 처음에는 동일했으나 시간이 지나면서 조금씩 변경되었다고 한다.)
A 회사는 Test디렉토리에서 프로그램을 만들고 테스트 한 후에 이상이 없으면
Real디렉토리로 이동시킨 후 사용자에게 배포한다고 한다.
이런식으로 시스템을 운영하던 어느날 사용자에게 배포한 프로그램에 치명적인 오류가 발생하여 프로그램이 오동작한다는 보고가 올라오고 있다고 한다.
하지만 Test 쪽 프로그램은 이상이 없다고 한다.
자, 이제 여러분은 A 회사를 도와줄 수 있는 다음과 같은 프로그램을 만들어야 한다.
두 디렉토리(Test와 Real)의 차이점을 알 수 있는 다음과 같은 프로그램을 작성하시오. (단, 하위 폴더 및 파일을 모두 포함해야 함)
Test에만 있고 Real에는 없는 파일은 다음과 같이 출력
T: 파일명
Real에만 있고 Test에는 없는 파일은 다음과 같이 출력
R: 파일명
Test와 Real에 모두 동일한 파일명으로 존재하지만 내용이 서로 다른 경우는 다음과 같이 출력
X: 파일명
15개의 풀이가 있습니다.
파이썬 3.4부터 도입된 pathlib을 이용해봤습니다.
from pathlib import Path
T = Path(input())
R = Path(input())
tf = set([x.relative_to(T) for x in T.rglob('*') if x.is_file()])
rf = set([x.relative_to(R) for x in R.rglob('*') if x.is_file()])
for x in tf.difference(rf):
print('R:', x)
for x in tf.difference(rf):
print('T:', x)
for x in tf.intersection(rf):
try:
with Path(T, x).open('rb') as f1:
with Path(R, x).open('rb') as f2:
if f1.read() != f2.read():
print('X:', x)
except PermissionError:
pass
루비입니다.
test_files = Dir.glob("test/**/*").select{ |p| File.file?(p) }.map { |path| path.sub("test/", "") }
real_files = Dir.glob("real/**/*").select{ |p| File.file?(p) }.map { |path| path.sub("real/", "") }
puts "T : #{test_files - real_files}"
puts "R : #{real_files - test_files}"
puts "X : #{(real_files & test_files).select{|path| `cat test/#{path}` != `cat real/#{path}` }}"
#cmpfile.py
import os
def search(dirname,dir):
arr=[]
flist = os.listdir(dirname)
for f in flist:
next = os.path.join(dirname, f)
if os.path.isdir(next):
arr+=search(next,dir)
else:
arr.append(next.replace(dir+"\\", ""))
return arr
def cmpFile(A, B, flag) :
retS=[]
retN=[]
for f in A :
if f in B :
retS.append(f)
else :
retN.append(f)
if flag == True : return retS
else : return retN
def cmpFile_S(A, B, list) :
ret=[]
for path in list :
pathA = os.path.join(A, path)
pathB = os.path.join(B, path)
fA=open(pathA, "r")
fB=open(pathB, "r")
A_Read=fA.read()
B_Read=fB.read()
fA.close()
fB.close()
if A_Read != B_Read :
ret.append(path)
return ret
dirT=".\\Test"
dirR=".\\Real"
listT=search(dirT, dirT)
listR=search(dirR, dirR)
print("T : " + str(cmpFile(listT, listR, False)))
print("R : " + str(cmpFile(listR, listT, False)))
print("X : " + str(cmpFile_S(dirT,dirR, cmpFile(listR, listT, True))))
소스 파일이 조금 더럽지만 결과는 출력되네요 ㅎ
펄입니다
#!/usr/bin/perl -w
use latest;
use File::Spec;
my ($test,$real)=@ARGV;
my (@tests,@reals,$i);
sub bfs{
my $path=$_[0];
push(my @queue,$path,$current,@output);
while(@queue){
$path=shift(@queue);
if($path=~/\/\.{1,2}$/){next}
if(-d $path){
opendir($current,$path);
push @queue,map {File::Spec->catfile($path,$_)} readdir $current;
closedir $current
}
elsif(-f $path){
if(-p $path){next}
push @output,$path;
}
}
@output;
}
@tests=bfs($test);@reals=bfs($real);
for $i (@tests){
unless(grep {$_ eq $i} @reals){say "T:$i"};
else{@reals=grep {$_ ne $i} @reals;say "X:$i"}
}
for $i (@reals){
unless(grep {$_ eq $i} @reals);say "R:$i"};
}
static void Main(string[] args)
{
var test = new List<string>();
var real = new List<string>();
test.AddRange(Array.ConvertAll(new DirectoryInfo("Test").GetFiles(), f => f.Name));
real.AddRange(Array.ConvertAll(new DirectoryInfo("Real").GetFiles(), f => f.Name));
Console.WriteLine("Test에만 있고 Real에는 없는 파일");
foreach (string n in test.Where(t => !real.Contains(t)))
Console.WriteLine(n);
Console.WriteLine("Real에만 있고 Test에는 없는 파일");
foreach (string n in real.Where(t => !test.Contains(t)))
Console.WriteLine(n);
Console.WriteLine("Test와 Real에 모두 동일한 파일명으로 존재하지만 내용이 서로 다른 파일");
foreach (string n in real.Where(t => test.Contains(t) && !Compare(new FileInfo(@"Test\" + t), new FileInfo(@"Real\" + t))))
Console.WriteLine(n);
Console.ReadLine();
}
static bool Compare(FileInfo a, FileInfo b)
{
return (a.OpenText().ReadToEnd() == b.OpenText().ReadToEnd());
}
Ruby
require 'fileutils'
ls = ->d { Dir.glob(d+"**/*").reduce([]) {|a,e| File.file?(e)? a<<e.sub(d,""):a }}
trx = ->d1,d2 { lst,lsr=ls[d1],ls[d2]; %w(T R X).zip([lst-lsr, lsr-lst,
(lsr&lst).select {|f| !FileUtils.cmp(d1+f, d2+f) }]).to_h }
Output
#=> test에 /bin이 없고, real에 /spec이 없으며, same파일은 내용만 다를 때.
#=> p trx["test/", "real/"]
{"T"=>["spec/redis_spec.rb", "spec/spec_helper.rb"],
"R"=>["bin/bundler", "bin/htmldiff", "bin/ldiff", "bin/respec", "bin/rspec"],
"X"=>["same"] }
from glob import glob
from os import sep
from os.path import isdir, join, normpath
#
path_T = normpath(r'경로')
path_R = normpath(r'경로')
len_sep = len(sep)
#
if __name__ == '__main__':
T_list, R_list = glob(join(path_T,'**\\*'), recursive = True), glob(join(path_R,'**\\*'), recursive = True)
len_T, len_R = len(path_T)+len_sep, len(path_R)+len_sep
T_list, R_list = [*(x[len_T:] for x in T_list if not isdir(x))], [*(x[len_R:] for x in R_list if not isdir(x))]
print('T:'+', '.join(set(T_list)-set(R_list)))
print('R:'+', '.join(set(R_list)-set(T_list)))
print('X:', end = ' ')
for name in (set(T_list)&set(R_list)):
t, r = open(join(path_T,name), 'r'), open(join(path_R,name), 'r')
if t.read() != r.read():
print(name, end = ', ')
t.close(), r.close()
glob 에 **\* 사용으로 전체탐색하고 앞부분 경로 삭제 뒤 비교. 파이썬 3.5.2 64
출력 결과는 조금 다르게 바꿨습니다.
< name> namex nameC 풀이가 없길래 하나 만들어 올립니다.
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <limits.h>
typedef const char* elem_t;
typedef struct vector vector;
struct vector {
elem_t* elems;
size_t capa;
size_t size;
};
vector* new_vector() {
vector* v = (vector*)malloc(sizeof(vector));
v->capa = 2;
v->elems = (elem_t*)calloc(v->capa, sizeof(elem_t));
v->size = 0;
return v;
}
void vec_free(vector* v) {
free(v->elems);
free(v);
}
void vec_add(vector* v, elem_t e) {
if (v->size == v->capa) {
v->capa *= 2;
v->elems = (elem_t*)realloc(v->elems, v->capa * sizeof(elem_t));
}
v->elems[v->size++] = e;
}
void vec_each(vector* v, void (*f)(elem_t)) {
for (size_t i=0; i<v->size; i++) {
f(v->elems[i]);
}
}
elem_t vec_get(vector* v, size_t i) {
return v->elems[i];
}
int elemcmp(const void *a, const void* b) {
return strcmp(*(const char**)a, *(const char**)b);
}
void vec_sort(vector* v) {
qsort(v->elems, v->size, sizeof(elem_t), elemcmp);
}
// . 나 ..
int isdot(struct dirent* dp) {
return (dp->d_namlen == 1 && dp->d_name[0] == '.')
|| (dp->d_namlen == 2 && dp->d_name[0] == '.' && dp->d_name[1] == '.');
}
void dir_(const char* base, vector* v) {
DIR* dirp = opendir(base);
struct dirent *dp;
char p[NAME_MAX];
while (dirp) {
if ((dp = readdir(dirp)) != NULL) {
sprintf(p, "%s/%s", base, dp->d_name);
if (isdot(dp)) {
// skip
} else if (dp->d_type == DT_DIR) {
dir_(p, v);
} else {
vec_add(v, strdup(p));
}
} else {
closedir(dirp);
break;
}
}
}
vector* dir(const char* base) {
vector* v = new_vector();
dir_(base, v);
vec_sort(v);
return v;
}
int filecmp(const char* left, const char* right) {
FILE* f1 = fopen(left, "r");
FILE* f2 = fopen(right, "r"); // f1, f2 에 대한 NULL 처리가 빠졌네요.
int cmp = 0;
while (!cmp) {
int c1 = fgetc(f1);
int c2 = fgetc(f2);
if (c1 == EOF && c2 == EOF)
break;
cmp = c1 == EOF || c2 == EOF || c1 != c2;
}
if (f1) fclose(f1);
if (f2) fclose(f2);
return cmp;
}
void compare(const char* left, const char* right) {
vector* ls1 = dir(left);
vector* ls2 = dir(right);
size_t len1 = strlen(left) + 1, len2 = strlen(right) + 1;
size_t i = 0, j = 0;
while (i < ls1->size && j < ls2->size) {
const char* rp1 = vec_get(ls1, i) + len1;
const char* rp2 = vec_get(ls2, j) + len2;
int comp = strcmp(rp1, rp2);
if (comp == 0) {
if (filecmp(vec_get(ls1, i), vec_get(ls2, j)) != 0) {
printf("x %s\n", rp1);
}
i++; j++;
} else if (comp < 0) {
printf("< %s\n", rp1);
i++;
} else {
printf("> %s\n", rp2);
j++;
}
}
while (i < ls1->size) {
printf("< %s\n", vec_get(ls1, i) + len1);
i++;
}
while (j < ls2->size) {
printf("> %s\n", vec_get(ls2, j) + len2);
j++;
}
vec_each(ls1, free); vec_free(ls1);
vec_each(ls2, free); vec_free(ls2);
}
int main(int argc, const char * argv[]) {
compare("test", "real");
return 0;
}
. .. notsame.txt same.txt test.txt test2.txt
. .. notsame.txt real.txt same.txt
T : notsame.txt test.txt test2.txt
R : notsame.txt real.txt
S : same.txt 계속하려면 아무 키나 누르십시오 . . .
링크드리스트를 이용한 비교
효율성 제로, 극심한 메모리 낭비 ㅠ,.ㅠ
최적화로 풀고 싶었지만 손이가는데로~~~
#include <stdio.h>
#include <string.h>
#include <io.h>
#include <time.h>
#include <stdlib.h>
#define MAX_COLS 100000
typedef struct _finddata_t FILE_SEARCH;
struct fList {
char name[260];
fList* next;
}* head1, * head2;
bool compare_content(char* fn1, char* fn2);
fList* GetfileList(char* path);
int main() {
head1 = NULL;
head2 = NULL;
fList* T = NULL;
fList* R = NULL;
fList* S = NULL;
char path1[100] = "C:/Users/wooye/Documents/Visual Studio 2010/Projects/20170103/20170103/T";
head1 = GetfileList(path1);
char path2[100] = "C:/Users/wooye/Documents/Visual Studio 2010/Projects/20170103/20170103/R";
head2 = GetfileList(path2);
fList* tempT = head1;
fList* tempR = head2;
while(tempT!=NULL) {
bool add = true;
tempR = head2;
while(tempR!=NULL) {
if(compare_content(tempT->name, tempR->name)) {
fList *fl = (fList*) malloc (sizeof(fList));
memcpy(fl->name, tempT->name, sizeof(char) * 260);
fl->next = S;
S = fl;
add = false;
}
tempR = tempR->next;
}
if (add){
fList *fl = (fList*) malloc (sizeof(fList));
memcpy(fl->name, tempT->name, sizeof(char) * 260);
fl->next = T;
T = fl;
}
tempT = tempT->next;
}
tempT = head1;
tempR = head2;
while(tempR!=NULL) {
bool add = true;
tempT = head1;
while(tempT!=NULL) {
if(compare_content(tempT->name, tempR->name))
add = false;
tempT = tempT->next;
}
if (add){
fList *fl = (fList*) malloc (sizeof(fList));
memcpy(fl->name, tempR->name, sizeof(char) * 260);
fl->next = R;
R = fl;
}
tempR = tempR->next;
}
printf("\n\nT : \n");
while(T!= NULL) {
printf("%s\n", T->name);
T = T->next;
}
printf("\n\nR : \n");
while(R!= NULL) {
printf("%s\n", R->name);
R = R->next;
}
printf("\n\nS : \n");
while(S!= NULL) {
printf("%s\n", S->name);
S = S->next;
}
//bool r = compare_content("T/notsame.txt", "R/notsame.txt");
}
fList* GetfileList(char* path){
fList* head = NULL;
long h_file;
char search_Path[100];
FILE_SEARCH file_search;
sprintf_s(search_Path, "%s/*.*", path);
if((h_file = _findfirst(search_Path, &file_search)) == -1L) {
printf( "No files in current directory!\n" );
} else {
do {
printf("%s\n", file_search.name);
if(file_search.name[0] != '.') {
fList *fl = (fList*) malloc (sizeof(fList));
memcpy(fl->name, file_search.name, sizeof(char) * 260);
fl->next = head;
head = fl;
}
} while (_findnext(h_file, &file_search) == 0);
_findclose(h_file);
}
return head;
}
bool compare_content(char* fn1, char* fn2) {
char t[260] = "T/";
strcat(t, fn1);
FILE* f1 = fopen(t,"rt");
char s1[MAX_COLS];
char r[260] = "R/";
strcat(r, fn2);
FILE* f2 = fopen(r,"rt");
char s2[MAX_COLS];
char c1, c2;
while(1) {
if (feof(f1) == 0 && feof(f2) == 0){
c1 = fgetc(f1);
c2 = fgetc(f2);
if(c1 == c2)
continue;
else
return false;
}
else if (feof(f1) != 0 && feof(f2) == 0)
return false;
else if (feof(f1) == 0 && feof(f2) != 0)
return false;
else
return true;
}
return true;
}
import os
import filecmp
test_file_arr = []
real_file_arr = []
all_file_arr = []
def search(dir_nm, arr):
try:
file_nms = os.listdir(dir_nm)
for file_nm in file_nms:
full_file_nm = os.path.join(dir_nm, file_nm)
if os.path.isdir(full_file_nm):
search(full_file_nm)
else:
arr.append(file_nm)
except PermissionError:
pass
search('d:\Test',test_file_arr)
search('d:\Real',real_file_arr)
print('T : ' + ', '.join([x for x in test_file_arr if ('|'.join(real_file_arr)).find(x)<0]))
print('R : ' + ', '.join([x for x in real_file_arr if ('|'.join(test_file_arr)).find(x)<0]))
all_file_arr = [x for x in real_file_arr if ('|'.join(test_file_arr)).find(x)>=0]
for nm in all_file_arr:
if not filecmp.cmp('d:\Test\\' + nm,'d:\Real\\' + nm):
print('X : ' + nm)
Swift입니다.
import Foundation
func compareFolders(leftFolderPath: String, rightFolderPath: String) -> (leftOnly:[String], rightOnly:[String], different:[String]) {
let fileManager = FileManager.default
do {
let leftFileArray = try fileManager.contentsOfDirectory(atPath: leftFolderPath)
let rightFileArray = try fileManager.contentsOfDirectory(atPath: rightFolderPath)
let leftSet:Set<String> = Set(leftFileArray.map {$0})
let rightSet:Set<String> = Set(rightFileArray.map {$0})
let leftOnly = leftSet.subtracting(rightSet)
let rightOnly = rightSet.subtracting(leftSet)
let commonOnly = leftSet.intersection(rightSet)
var differentOnly = [String]()
for file in commonOnly {
let leftContent = try String(contentsOfFile: leftFolderPath + "/" + file)
let rightContent = try String(contentsOfFile: rightFolderPath + "/" + file)
if leftContent != rightContent {
differentOnly.append(file)
}
}
return (Array(leftOnly), Array(rightOnly), Array(differentOnly))
} catch {
print("Error happend! \(error)")
}
return ([],[],[])
}
let (leftOnly, rightOnly, different) = compareFolders(leftFolderPath: "/Test/A", rightFolderPath: "/Test/B")
for file in leftOnly {
print("T: \(file)")
}
for file in rightOnly {
print("R: \(file)")
}
for file in different {
print("X: \(file)")
}
실행결과는...
T: a_only.swift
R: b_only.swift
X: test.swift
test_files = Dir.glob("test//*").select{ |p| File.file?(p) }.map { |path| path.sub("test/", "") } real_files = Dir.glob("real//*").select{ |p| File.file?(p) }.map { |path| path.sub("real/", "") }
puts "T : #{test_files - real_files}"
puts "R : #{real_files - test_files}"
puts "X : #{(real_files & test_files).select{|path| cat test/#{path} != cat real/#{path} }}"
import glob, os
path_T = '...'
path_R = '...'
search_T = set(i[len(path_T):] for i in glob.iglob(path_T+'/**', recursive=True) if os.path.isfile(i))
search_R = set(i[len(path_R):] for i in glob.iglob(path_R+'/**', recursive=True) if os.path.isfile(i))
print('T: {}'.format(str(search_T-search_R)[1:-1]))
print('R: {}'.format(str(search_R-search_T)[1:-1]))
print('X:', end=' ')
for i in (search_T & search_R):
with open(path_T+i, 'r') as read_T:
with open(path_R+i, 'r') as read_R:
if read_T.read() != read_R.read(): print(i, end=', ')
from pathlib import Path
Test = Path(input('Test dir: '))
Real = Path(input('Real dir: '))
test_files = {path.relative_to(Test) for path in Test.rglob('*') if path.is_file()}
real_files = {path.relative_to(Real) for path in Real.rglob('*') if path.is_file()}
for file in test_files - real_files:
print('T:', file)
print()
for file in real_files - test_files:
print('R:', file)
print()
for file in test_files & real_files:
t = Path(Test / file)
r = Path(Real / file)
if t.stat().st_size != r.stat().st_size and t.read_bytes() != r.read_bytes():
print('X:', file)
JAVA입니다. trie 구조를 이용하여 구현하였습니다.
package question3.두_디렉토리_비교;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Hashtable;
import java.util.List;
public class Main {
public static void main(String[] args) throws IOException {
Hashtable<Object, Object> testTable = new Hashtable<Object, Object>();
Hashtable<Object, Object> realTable = new Hashtable<Object, Object>();
File test = new File(Main.class.getResource("Test").getPath());
File real = new File(Main.class.getResource("Real").getPath());
addToDirectory(test, testTable);
addToDirectory(real, realTable);
DirectoryComparer dc = new DirectoryComparer();
dc.testTable = testTable;
dc.realTable = realTable;
dc.compareDirectory();
printFileNames("t", dc.t);
printFileNames("r", dc.r);
printFileNames("x", dc.x);
}
static void addToDirectory(File file, Hashtable<Object, Object> parentDirectory)
throws IOException {
if(file.isFile()) {
parentDirectory.put(file.getName(), readFile(file));
}
else {
Hashtable<Object, Object> thisDirectory = new Hashtable<Object, Object>();
if(file.getName().equals("Test") || file.getName().equals("Real")) {
parentDirectory.put("*", thisDirectory);
}
else {
parentDirectory.put(file.getName(), thisDirectory);
}
for (File childFile : file.listFiles()) {
addToDirectory(childFile, thisDirectory);
}
}
}
static void printFileNames(String kind, List<String> files) {
System.out.print(kind + ": ");
for (int i = 0; i < files.size(); i++) {
System.out.print(files.get(i));
if(i != files.size() - 1) {
System.out.print(", ");
}
}
System.out.println("");
}
static String readFile(File file) throws IOException {
String fileStr = "";
BufferedReader br = new BufferedReader(new FileReader(file));
while(true) {
String line = br.readLine();
if(line == null) {
break;
}
fileStr = fileStr + line;
}
br.close();
return fileStr;
}
}
package question3.두_디렉토리_비교;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class DirectoryComparer {
Hashtable<Object, Object> testTable;
Hashtable<Object, Object> realTable;
List<String> t;
List<String> r;
List<String> x;
public DirectoryComparer() {
testTable = new Hashtable<Object, Object>();
realTable = new Hashtable<Object, Object>();
t = new ArrayList<String>();
r = new ArrayList<String>();
x = new ArrayList<String>();
}
boolean compareDirectory() {
return compareDirectory(this.testTable, this.realTable);
}
boolean compareDirectory
(Hashtable<Object, Object> testTable, Hashtable<Object, Object> realTable) {
Set<Object> testKeys = testTable.keySet();
Iterator<Object> testIterator = testKeys.iterator();
Set<Object> realKeys = realTable.keySet();
Iterator<Object> realIterator = realKeys.iterator();
boolean same = true;
while(testIterator.hasNext()) {
String nextTest = (String)testIterator.next();
boolean hasSameChildren = true;
if(realTable.containsKey(nextTest)) {
if((testTable.get(nextTest) instanceof String)) {
//trie의 말단
hasSameChildren =
testTable.get(nextTest).equals(realTable.get(nextTest));
}
else {
hasSameChildren =
compareDirectory
((Hashtable<Object, Object>)testTable.get(nextTest),
(Hashtable<Object, Object>)realTable.get(nextTest));
}
//trie의 말단이면 같은 키가 존재하기만 해도 같은 디렉터리임
if(!hasSameChildren && (!nextTest.equals("*"))) {
same = false;
x.add(nextTest);
}
}
else {
//test에만 있고 real에는 없음
same = false;
t.add(nextTest);
}
}
while(realIterator.hasNext()) {
String nextReal = (String) realIterator.next();
if(!testTable.containsKey(nextReal)) {
//real에만 있고 test에는 없음
same = false;
r.add(nextReal);
}
}
return same;
}
}