I would like to read multiple text files and build a sparse array by concatenating row-wise one after the other. The text files are ordered in the following format:
\begin{matrix} 0, & 0, & 0, &\dots & 0, \\ 0, & 1, & 3, & \dots & 0, \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 0, & 1, & 0, & \dots & 0, \end{matrix}
I read the text file as a sparse array by using the function from this question:
ClearAll[spart, getIC, getJR, getSparseData, getDefaultElement, makeSparseArray];
HoldPattern[spart[SparseArray[s___], p_]] := {s}[[p]];
getIC[s_SparseArray] := spart[s, 4][[2, 1]];
getJR[s_SparseArray] := Flatten@spart[s, 4][[2, 2]];
getSparseData[s_SparseArray] := spart[s, 4][[3]];
getDefaultElement[s_SparseArray] := spart[s, 3];
makeSparseArray[dims : {_, _}, jc : {__Integer}, ir : {__Integer},
data_List, defElem_: 0] :=
SparseArray @@ {Automatic, dims, defElem, {1, {jc, List /@ ir}, data}};
Clear[readSparseTable];
readSparseTable[file_String?FileExistsQ, chunkSize_: 100] :=
Module[{stream, dataChunk, start, ic = {}, jr = {}, sparseData = {}, getDataChunkCode, dims},
stream = StringToStream[Import[file, "String"]];
getDataChunkCode := If[# === {}, {}, SparseArray[#]] &@
ImportString[
StringJoin[Riffle[ReadList[stream, "String", chunkSize], "\n"]], "Table", "FieldSeparators" -> ","];
Internal`WithLocalSettings[Null,(*main code*)
start = getDataChunkCode;
ic = getIC[start];
jr = getJR[start];
sparseData = getSparseData[start];
dims = Dimensions[start];
While[True, dataChunk = getDataChunkCode;
If[dataChunk === {}, Break[]];
ic = Join[ic, Rest@getIC[dataChunk] + Last@ic];
jr = Join[jr, getJR[dataChunk]];
sparseData = Join[sparseData, getSparseData[dataChunk]];
dims[[1]] += First[Dimensions[dataChunk]];],(*clean-up*)
Close[stream]];
makeSparseArray[dims, ic, jr, sparseData]]
To build a bigger complete sparse array (Mat
), by concatenation of the imported data as sparse arrays, I use Table
and iterate over n
text files:
n = 200;
MatTab = Table[readSparseTable["C:/drive/textFILE" <> ToString[i] <> ".txt"], {i, 0, n, 1}]];
Mat = Flatten[MatTab,1];
Using Table
seems to turn the sparse array back to a normal dense matrix and as a result, my system runs out of memory. Using SparseArray
function again also does not help:
n = 200;
MatTab = SparseArray[Table[readSparseTable["C:/drive/textFILE" <> ToString[i] <> ".txt"], {i, 0, n, 1}]]];
Mat = Flatten[MatTab,1];
How can I solve this problem?.
UPDATE:
A possible solution using code from Leonid Shifrin's answer, but slow:
Clear[accumulateSparseArray];
accumulateSparseArray[Hold[getDataChunkCode_]] :=
Module[{start, ic, jr, sparseData, dims, dataChunk},
start = getDataChunkCode;
ic = getIC[start];
jr = getJR[start];
sparseData = getSparseData[start];
dims = Dimensions[start];
While[True, dataChunk = getDataChunkCode;
If[dataChunk === {}, Break[]];
ic = Join[ic, Rest@getIC[dataChunk] + Last@ic];
jr = Join[jr, getJR[dataChunk]];
sparseData = Join[sparseData, getSparseData[dataChunk]];
dims[[1]] += First[Dimensions[dataChunk]];];
makeSparseArray[dims, ic, jr, sparseData]];
Clear[sparseArrayFlatten]
sparseArrayFlatten[m_?(MatrixQ[#, MatchQ[#, _SparseArray] &] &)] :=
Module[{joinRow, code},
joinRow[row_List] :=
Module[{i = 1}, With[{l = Append[row, {{}}]}, code := l[[i++]]];
accumulateSparseArray[Hold[Transpose[code]]]];
joinRow[joinRow /@ m]]
Remove[Mat, i, temp]
Mat = readSparseTable["C:/drive/textFILE" <> ToString[0] <> ".txt"];
For[i = 1, i <= n, i++,
temp = Mat;
Mat = sparseArrayFlatten[{{temp},{readSparseTable["C:/drive/textFILE"<> ToString[i]<>".txt"]}}];]
ic = Join[ic, Rest@getIC[dataChunk] + Last@ic]; jr = Join[jr, getJR[dataChunk]];
involves a copy operation of all data that has already been read for each chunk being read. It is better to gather the chunks in an expandable data structure such asInternal`Bag
,Association
(or withSow
andReap
) and joining only once in the end. $\endgroup$ic = getIC[A]
,jr = getJR[A]
,getSparseData[A]
, andgetDefaultElement[A]
can be replaced by theSparseArray
propertiesA["ColumnIndices"]
,A["RowPointers"]
,A["NonzeroValues"]
, andA["Background"]
, respectively. $\endgroup$For
-loop is awkward, not only because it is aFor
-loop, but also because it also involves a copy operation of all preceding data; this forces the computational complexity of loop to beO(n^2)
whileO(n)
would be sufficient (see also my comment two comments above) . Btw.: The argument pattern forsparseArrayFlatten
can be simplified tosparseArrayFlatten[m_SparseArray?MatrixQ] := ...
$\endgroup$