2010-07-20 15 views
8

Entiendo completamente que esta pregunta ha sido hecha mucho, pero estoy pidiendo una variación específica y mi búsqueda-foo se ha rendido, ya que solo he encontrado algoritmos que anexan uno vector existente a otro, pero no a uno devuelto desde una función.C++ agrega un vector a otro

que tienen esta función que enumera todos los archivos en un directorio:

vector<string> scanDir(const string& dir) 

que puede llamarse a sí mismo internamente (para los subdirectorios).

Necesito un corto manera de agregar el valor devuelto al vector de la persona que llama. Tengo en mi mente algo como esto (pero por supuesto que no existe :():

vector<string> fileList; 
//... 
fileList.append(scanDir(subdirname)); 

Temo que almacenar el valor de retorno e insertándola en fileList traería maldad rendimiento Lo que quiero decir es esto. :

vector<string> temp(scanDir(subdirname)); 
copy(temp.begin(), temp.end(), back_inserter(fileList)); 

Gracias

PS:!. no estoy forzando a mí mismo para el uso de vectores, cualquier otro recipiente que funciona igualmente bien y pueden prevenir el potencial operación de copia grande está bien por mí

+1

relacionadas: http: //stackoverflow.com/questions/2208293/what-is-the-most-efficient-way-to-append-one-stdvector-to-the-end-of-another – kennytm

+4

Cuando hablamos de rendimiento, la primera pregunta siempre es : ¿has medido? Luego, los triviales, ¿se está ejecutando en un ciclo cerrado, es crítico el rendimiento de la aplicación? ¿Cómo se compara la copia de un vector con la recuperación de una lista de archivos del sistema de archivos? –

Respuesta

8

Si estás en la posición de cambiar scanDir, lo convierten en una función (plantilla) aceptar un iterador de salida:

template <class OutIt> 
void scanDir(const std::string& dirname, OutIt it) { 
    // ... 
    // Scan subdir 
    scanDir(subdir, it); 
    // ... 
} 

usted tendrá el beneficio adicional de ser capaz de llenar todos tipo de estructuras de datos como

std::vector<string> vector; 
scanDir(dir1, std::back_inserter(vector)); 
std::set<string> fileset 
scanDir(dir1, std::inserter(fileset, fileset.begin())); 

etc.

EDITAR (véase el comentario ...)

Para poder utilizar esta función para la inicialización miembro de la clase, se puede llamar a cualquiera en el constructor como en

class MyClass { 
private: 
    std::vector<string> m_fileList; 
public: 
    MyClass(const std::string& dirname) { 
    scanDir(dirname, std::back_inserter(m_fileList); 
    } 
} 

o utilizando una función de contenedor

std::vector<string> scanDir(const std::string& dirname) { 
    std::vector<string> result; 
    scanDir(dirname, std::back_inserter(result); 
    return result; 
} 

class MyClass { 
// Same as above.. 
    MyClass(const std::string& dirname) : m_fileList(scanDir(dirname)) { } 
} 

Preferiría la primera versión por razones de rendimiento (y otras) ...

+0

¿Podría usar esa plantilla para inicializar un miembro de datos de clase (que contiene la lista de archivos?) ... oh, espera, el tuyo combinado con sukru es bastante poderoso (segunda función para crear el valor de retorno). – rubenvb

+0

Acabo de escribir una edición sobre esto. Sugeriría llamar a la función explícitamente en el constructor, no puedo ver ningún inconveniente de este enfoque ... – MartinStettner

+0

Generalmente se llama 'OutputIterator' en lugar de' InsertIterator'. –

1

En lugar de

vector<string> temp(scanDir(subdirname)); 

que puede hacer

vector<string> const& temp = scanDir(subdirname); 

y proceder con la copia:

fileList.insert(fileList.end(), temp.begin(), temp.end()); 
+0

Es poco probable que haga alguna diferencia, una vez que el constructor de copia elision termina de usarlo. No hay ningún requisito para la primera línea de código para hacer algo que la segunda línea de código no es obligatoria. –

15

Por qué no sólo tiene que pasar el vector como un argumento? Entonces, cada invocación puede agregarse al mismo vector, sin copiar. O crea una clase de implementación que acumula los elementos en un objeto miembro.

+0

+1. Pasar como argumento es lo que hago en el código sensible al rendimiento. – Dummy00001

+2

Eso es 'void scanDir (const string & dir, vector & result)'. – cape1232

+0

+1 por simplicidad. – Brian

0

La función recursiva tendrá que copiar todo varias veces, O (profundidad) para ser precisos (es decir: todo en el nivel de la hoja se copiará una y otra vez hasta que llegue a la raíz).

El mejor método sería dividiendo esto en dos funciones diferentes:

vector<string> scanDir(string path) 
{ 
    vector<string> retval; 

    scanDir(path, &retval); 

    return retval; 
} 

static void scanDir(string path, vector<string>& output) 
{ 
    .. scan 
    .. append to output 
} 
+0

en su primera sobrecarga, se garantiza que se realizará una copia completa de la devolución al regresar. –

+0

@alexandre: ¿cómo evitaría eso al usar esta función para inicializar un miembro de datos de clase? ¿No habría alguna magia del compilador evitando la copia? – rubenvb

+0

@rubenvb: la optimización del valor de retorno tiene lugar cuando regresas con un constructor. Use una clase en su lugar, vea el comentario de MartinStettner. –

8

PS: No estoy forzando a mí mismo para el uso de vectores, cualquier otro recipiente que funciona igualmente bien y puedo prevenir el potencial grande la operación de copia está bien por mí.

Bueno, si se utiliza un list y llamar a.splice(a.end(), b); podrá evitar por completo la operación de copia. A list generalmente va a ser una lista vinculada en lugar de una matriz, como es el caso de vector, por lo que tiene muchas implicaciones de rendimiento y uso. Pero el empalme se ejecuta en O (1), por lo que es un buen beneficio.

+0

http://stackoverflow.com/questions/1905417/array-vs-vector-vs-list – kennytm

0

¿Qué tal una función de ayuda?

template<class T> 
std::vector<T>& VectorAppend(std::vector<T> &target, const std::vector<T> &source) 
{ 
    size_t insertPos = target.size(); 
    target.resize(target.size() + source.size()); 
    std::copy(source.begin(), source.end(), target.begin() + insertPos); 
    return target; 
} 
+0

¿Qué pasa con un simple 'std :: copy (source.begin(), source.end(), std :: back_inserter (objetivo)); ¿en su lugar? Si está tratando de optimizar la asignación, siempre puede llamar 'target.reserve (target.size() + source.size());' before-hand. – sbi

2

Utilice std :: list y anexe usando std :: list :: splice.

De the docs for splice:

La operación no implica la construcción o destrucción de cualquier elemento objeto y, a excepción de la tercera versión, que se realiza en un tiempo constante.

2
vector<string> fileList; 
vector<string> temp(scanDir(subdirname)); 

fileList.insert(fileList.end(), temp.begin(), temp.end()); 

espero que ayudaron.

+0

inserto es una operación de tiempo lineal. La pregunta específicamente dijo que su sugerencia no era lo que estaba buscando. "Me temo que almacenar el valor de retorno e insertarlo en fileList traería mal desempeño". – cape1232

+0

"Temo", para mí significa que no está seguro si esto causará mal funcionamiento. Para ser sincero, no creo que el uso de insert() en este caso cause problemas de rendimiento. De todos modos, gracias por el signo negativo -, -. – virious

+0

Esto responde bien al título de la pregunta. Así que debería ser fácil encontrar esta solución, cuando gente como yo venga aquí en busca de una solución para eso. – kuester2000

-1

Puede que esta no sea la solución más simple posible, pero ¿qué tal hacer algo equivalente al StringBuilder de C#?

Haga un list<vector<string> > luego puede agregar todos los vectores que obtenga de sus llamadas a scanDir() a la lista.

Si absolutamente tiene que tener un solo vector al final, puede, una vez creado un nuevo vector, asignarlo lo suficientemente grande como para que no tenga que cambiar el tamaño y montar su producto final.

Alternativamente, se puede hacer una nueva clase (si es necesario que se deriva del vector <T>) e internamente usa el vector lista < <T> > para almacenar los elementos. Luego haría que sus iteradores iteren a través de los elementos en la primera lista, luego cuando llegue al final busque los elementos en la lista siguiente, solo devuelva container :: end cuando llegue al final de la última lista.

+0

Esta es la respuesta más complicada en la que nunca podría pensar ... – rubenvb

-1

Sé que esto no responde a su pregunta directamente, pero con respecto a su objetivo subyacente, es posible que desee simplemente volver a implementar su función en términos de boost :: filesystem. El iterador de directorio ya es recursivo, por lo que no es necesario que realice sus propias llamadas recursivas. Simplemente podría completar una lista en un bucle sobre el iterador.Hay un ejemplo de implementación de ls: http://www.boost.org/doc/libs/1_43_0/libs/filesystem/example/simple_ls.cpp

También tendrá la ventaja añadida de (teórica) independencia de la plataforma, la adopción relativamente amplio (insectos se descubrían más rápidamente con una mayor adopción), etc

Cuestiones relacionadas