# HG changeset patch # User David Bateman # Date 1205280294 -3600 # Node ID b4aa9ef3d3ef253fca8b4cdfbee9de8bf2f1c167 # Parent a74f9679535fcd9c03f7dbe47ecfe5446e1a5b73 Port dlmread, dlmwrite, csvread and csvwrite from octave-forge diff --git a/doc/ChangeLog b/doc/ChangeLog --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,8 @@ +2008-03-12 David Bateman + + * interpreter/io.txi: Document dlmread, dlmwrite, csvread and + csvwrite. + 2008-02-25 Ben Abbott * interpreter/geometryimages.m, interpreter/interpimages.m, diff --git a/doc/interpreter/io.txi b/doc/interpreter/io.txi --- a/doc/interpreter/io.txi +++ b/doc/interpreter/io.txi @@ -200,6 +200,17 @@ @DOCSTRING(fdisp) +Octave can also read and write matrices text files such as comma +separated lists. + +@DOCSTRING(dlmwrite) + +@DOCSTRING(dlmread) + +@DOCSTRING(csvwrite) + +@DOCSTRING(csvread) + @menu * Saving Data on Unexpected Exits:: @end menu diff --git a/scripts/ChangeLog b/scripts/ChangeLog --- a/scripts/ChangeLog +++ b/scripts/ChangeLog @@ -1,3 +1,9 @@ +2008-03-12 David Bateman + + * io/dlmwrite.m, io/csvread.m, io/csvwrite.m: Files ported from + octave-forge. + * io/Makefile.in (SOURCES): Add them here. + 2008-03-07 John W. Eaton * plot/contourf.m: Set axes layer property to "top". diff --git a/scripts/io/Makefile.in b/scripts/io/Makefile.in --- a/scripts/io/Makefile.in +++ b/scripts/io/Makefile.in @@ -32,7 +32,7 @@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_DATA = @INSTALL_DATA@ -SOURCES = beep.m +SOURCES = beep.m csvread.m csvwrite.m dlmwrite.m DISTFILES = $(addprefix $(srcdir)/, Makefile.in $(SOURCES)) diff --git a/scripts/io/csvread.m b/scripts/io/csvread.m new file mode 100644 --- /dev/null +++ b/scripts/io/csvread.m @@ -0,0 +1,30 @@ +## Copyright (C) 2001, 2008 Paul Kienzle +## +## This file is part of Octave. +## +## Octave is free software; you can redistribute it and/or modify it +## under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or (at +## your option) any later version. +## +## Octave is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Octave; see the file COPYING. If not, see +## . + +## -*- texinfo -*- +## @deftypefn {Function File} {x} = csvread (@var{filename}) +## Read the matrix @var{x} from a file. +## +## This function is equivalent to dlmread (@var{filename}, "," , ...) +## +## @seealso{dlmread, dlmwrite, csvwrite} +## @end deftypefn + +function m = csvread (f, varargin) + m = dlmread (f, ',', varargin{:}); +endfunction diff --git a/scripts/io/csvwrite.m b/scripts/io/csvwrite.m new file mode 100644 --- /dev/null +++ b/scripts/io/csvwrite.m @@ -0,0 +1,30 @@ +## Copyright (C) 2001, 2008 Paul Kienzle +## +## This file is part of Octave. +## +## Octave is free software; you can redistribute it and/or modify it +## under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or (at +## your option) any later version. +## +## Octave is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Octave; see the file COPYING. If not, see +## . + +## -*- texinfo -*- +## @deftypefn {Function File} {x} = csvwrite (@var{filename}, @var{x}) +## Write the matrix @var{x} to a file. +## +## This function is equivalent to dlmwrite(@var{filename},@var{x},",",...) +## +## @seealso{dlmread, dlmwrite, csvread} +## @end deftypefn + +function csvwrite(f, m, varargin) + dlmwrite (f, m, ',', varargin{:}); +endfunction diff --git a/scripts/io/dlmwrite.m b/scripts/io/dlmwrite.m new file mode 100644 --- /dev/null +++ b/scripts/io/dlmwrite.m @@ -0,0 +1,201 @@ +## Copyright (C) 2002, 2008 Paul Kienzle +## +## This file is part of Octave. +## +## Octave is free software; you can redistribute it and/or modify it +## under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or (at +## your option) any later version. +## +## Octave is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Octave; see the file COPYING. If not, see +## . + +## -*- texinfo -*- +## @deftypefn {Function File} {} dlmwrite (@var{file}, @var{a}) +## @deftypefnx {Function File} {} dmlwrite (@var{file}, @var{a}, @var{delim}, @var{r}, @var{c}) +## @deftypefnx {Function File} {} dmlwrite (@var{file}, @var{a}, 'attrib1', @var{value1}, 'attrib2', @var{value2}, @dots{}) +## @deftypefnx {Function File} {} dmlwrite (@var{file}, @var{a}, '-append', @dots{}) +## +## Write the matrix @var{a} to the text @var{file} using delimiters. +## +## @table @var +## @item delim +## the delimiter to use to separate values on a row +## +## @item r +## the number of delimiter-only lines to add to the start of the file +## +## @item c +## the number of delimiters to prepend to each line of data. +## +## @item '-append' +## append to the end of the @var{file}. +## +## @item 'append', state +## Either @samp{'on'} or @samp{'off'}. See @samp{'-append'} above. +## +## @item 'delimiter', d +## See @var{delim} above. +## +## @item 'newline', os +## The character(s) to use to separate each row. Three special cases +## exist for this option. @samp{'unix'} is changed into "\n", @samp{'pc'} is +## changed into "\r\n", and @samp{'mac'} is changed into "\r". Other +## values for this option are kept as is. +## +## @item 'roffset', r +## See @var{r} above. +## +## @item 'coffset', c +## See @var{c} above. +## +## @item 'precision', p +## The precision to use when writing the file. It can either be a +## format string (as used by fprintf) or a number of significant digits. +## @end table +## +## @example +## @var{A} = reshape(1:16,4,4); +## dlmwrite(@code{"file.csv"}, @var{A}) +## @end example +## +## Note the extra escaping of the backslashes necessary in using the +## latex delimiter of "\\" with a unix style newline. +## +## @example +## dlmwrite(@code{"file.tex"}, @var{a}, 'delimiter', '&', 'newline', '\\\\\n') +## @end example +## +## @seealso{dlmread, csvread, csvwrite} +## @end deftypefn + +## Author: Paul Kienzle +## +## This program was originally granted to the public domain +## +## 2002-03-08 Paul Kienzle +## * Initial revision +## 2005-11-27 Bill Denney +## * Significant modifications of the input arguements for additional +## functionality. + +function dlmwrite (file, a, varargin) + + if (nargin < 2 || ! ischar( file)) + ptint_usage (); + endif + + ## set defaults + delim = ","; + r = 0; + c = 0; + newline = "\n"; + precision = "%.16g"; + opentype = "wt"; + + ## process the input arguements + i = 0; + while (i < length (varargin)) + i = i + 1; + if (strcmpi (varargin{i}, "delimiter")) + i = i + 1; + delim = varargin{i}; + elseif (strcmpi (varargin{i}, "newline")) + i = i + 1; + newline = varargin{i}; + if (strcmpi (newline, "unix")) + newline = "\n"; + elseif (strcmpi (newline, "pc")) + newline = "\r\n"; + elseif (strcmpi (newline, "mac")) + newline = "\r"; + endif + elseif (strcmpi (varargin{i}, "roffset")) + i = i + 1; + r = varargin{i}; + elseif (strcmpi (varargin{i}, "coffset")) + i = i + 1; + c = varargin{i}; + elseif (strcmpi (varargin{i}, "precision")) + i = i + 1; + precision = varargin{i}; + if (! strcmpi (class (precision), "char")) + precision = sprintf ("%.%gg", precision); + endif + elseif (strcmpi (varargin{i}, "-append")) + opentype = "at"; + elseif (strcmpi (varargin{i}, "append")) + i = i + 1; + if (strcmpi (varargin{i}, "on")) + opentype = "at"; + elseif (strcmpi (varargin{i}, "off")) + opentype = "wt"; + else + error ("dlmwrite: append must be \"on\" or \"off\"."); + endif + else + if (i == 1) + delim = varargin{i}; + elseif (i == 2) + r = varargin{i}; + elseif (i == 3) + c = varargin{i}; + else + print_usage(); + endif + endif + endwhile + + [fid, msg] = fopen (file, opentype); + if (fid < 0) + error (msg); + else + if (r > 0) + fprintf (fid, "%s", repmat ([repmat(delim, 1, c + columns(a) - 1), ... + newline], 1, r)); + endif + if (iscomplex (a)) + cprecision = regexprep (precision, '^%([-\d.])','%+$1'); + template = [precision, cprecision, "i", ... + repmat([delim, precision, cprecision, "i"], 1, ... + columns(a) - 1), newline ]; + else + template = [precision, repmat([delim, precision], 1, columns(a) - 1),... + newline]; + endif + if (c > 0) + template = [repmat(delim, 1, c), template]; + endif + if (iscomplex (a)) + a = a.'; + b = zeros (2*rows(a), columns (a)); + b(1: 2 : end, :) = real (a); + b(2: 2 : end, :) = imag (a); + fprintf (fid, template, b); + else + fprintf (fid, template, a.'); + endif + fclose (fid); + endif +endfunction + +%!test +%! f = tmpnam(); +%! dlmwrite(f,[1,2;3,4],'precision','%5.2f','newline','unix','roffset',1,'coffset',1); +%! fid = fopen(f,"rt"); +%! f1 = char(fread(fid,Inf,'char')'); +%! fclose(fid); +%! dlmwrite(f,[5,6],'precision','%5.2f','newline','unix','coffset',1,'delimiter',',','-append'); +%! fid = fopen(f,"rt"); +%! f2 = char(fread(fid,Inf,'char')'); +%! fclose(fid); +%! unlink(f); +%! +%! assert(f1,",,\n, 1.00, 2.00\n, 3.00, 4.00\n"); +%! assert(f2,",,\n, 1.00, 2.00\n, 3.00, 4.00\n, 5.00, 6.00\n"); diff --git a/src/DLD-FUNCTIONS/dlmread.cc b/src/DLD-FUNCTIONS/dlmread.cc new file mode 100644 --- /dev/null +++ b/src/DLD-FUNCTIONS/dlmread.cc @@ -0,0 +1,421 @@ +/* + +Copyright (C) 2008 Jonathan Stickel + +This file is part of Octave. + +Octave is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 3 of the License, or (at your +option) any later version. + +Octave is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with Octave; see the file COPYING. If not, see +. + +*/ + +/* + Adapted from previous version of dlmread.occ as authored by Kai Habel, + but core code has been completely re-written. +*/ + +#include + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "lo-ieee.h" + +#include "defun-dld.h" +#include "error.h" +#include "oct-obj.h" +#include "utils.h" + +static bool +isletter (char c) +{ + return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); +} + +static bool +read_cell_spec(std::istream& is, unsigned long& row, unsigned long& col) +{ + + bool stat = false; + + if (is.peek () == std::istream::traits_type::eof()) + stat = true; + else + { + if (isletter (is.peek ())) + { + + col = 0; + while (is && isletter (is.peek ())) + { + char ch = is.get (); + col *= 26; + if (ch >= 'a') + col += ch - 'a'; + else + col += ch - 'A'; + } + + if (is) + { + is >> row; + row --; + if (is) + stat = true; + } + } + } + + return stat; +} + +static bool +parse_range_spec(const octave_value& range_spec, + unsigned long& rlo, unsigned long& clo, + unsigned long& rup, unsigned long& cup) +{ + bool stat = true; + + if (range_spec.is_string ()) + { + std::istringstream is (range_spec.string_value ()); + char ch = is.peek (); + + if (ch == '.' || ch == ':') + { + rlo = 0; + clo = 0; + ch = is.get (); + if (ch == '.') + { + ch = is.get (); + if (ch != '.') + stat = false; + } + } + else + { + stat = read_cell_spec (is, rlo, clo); + + if (stat) + { + ch = is.peek (); + + if (ch == '.' || ch == ':') + { + ch = is.get (); + if (ch == '.') + { + ch = is.get (); + if (!is || ch != '.') + stat = false; + } + + rup = ULONG_MAX - 1; + cup = ULONG_MAX - 1; + } + else + { + rup = rlo; + cup = clo; + if (!is || !is.eof ()) + stat = false; + } + } + } + + if (stat && is && !is.eof ()) + stat = read_cell_spec (is, rup, cup); + + if (! is || !is.eof ()) + stat = false; + } + else if (range_spec.is_real_matrix () && range_spec.numel () == 4) + { + ColumnVector range(range_spec.vector_value()); + // double --> unsigned int + rlo = static_cast (range(0)); + clo = static_cast (range(1)); + rup = static_cast (range(2)); + cup = static_cast (range(3)); + } + else + stat = false; + + return stat; +} + +DEFUN_DLD (dlmread, args, , + "-*- texinfo -*-\n\ +@deftypefn {Loadable Function} {@var{data} =} dlmread (@var{file})\n\ +@deftypefnx {Loadable Function} {@var{data} =} dlmread (@var{file}, @var{sep})\n\ +@deftypefnx {Loadable Function} {@var{data} =} dlmread (@var{file}, @var{sep}, @var{R0}, @var{C0})\n\ +@deftypefnx {Loadable Function} {@var{data} =} dlmread (@var{file}, @var{sep}, @var{range})\n\ +Read the matrix @var{data} from a text file. If not defined the separator\n\ +between fields is determined from the file itself. Otherwise the\n\ +separation character is defined by @var{sep}.\n\ +\n\ +Given two scalar arguments @var{r0} and @var{c0}, these define the starting\n\ +row and column of the data to be read. These values are indexed from zero,\n\ +such that the first row corresponds to an index of zero.\n\ +\n\ +The @var{range} parameter must be a 4 element vector containing the upper\n\ +left and lower right corner @code{[@var{R0},@var{C0},@var{R1},@var{C1}]} or\n\ +a spreadsheet style range such as 'A2..Q15'. The lowest index value is zero.\n\ +@end deftypefn") +{ + octave_value_list retval; + int nargin = args.length(); + bool sepflag = 0; + if (nargin < 1 || nargin > 4) + { + print_usage (); + return retval; + } + + if ( !args (0).is_string() ) + { + error ("dlmread: 1st argument must be a string"); + return retval; + } + + std::string fname (args(0).string_value()); + std::ifstream file (fname.c_str()); + if (!file) + { + error("dlmread: could not open file"); + return retval; + } + + // set default separator + std::string sep; + if (nargin > 1) + { + if (args(1).is_sq_string ()) + sep = do_string_escapes (args(1).string_value()); + else + sep = args(1).string_value(); + } + + unsigned long i = 0, j = 0, r = 1, c = 1, rmax = 0, cmax = 0; + std::string line; + std::string str; + Matrix rdata; + ComplexMatrix cdata; + bool iscmplx = false; + size_t pos1, pos2; + + // take a subset if a range was given + unsigned long r0 = 0, c0 = 0, r1 = ULONG_MAX-1, c1 = ULONG_MAX-1; + if (nargin > 2) + { + if (nargin == 3) + { + if (! parse_range_spec(args (2), r0, c0, r1, c1)) + error ("dlmread: error parsing range"); + } + else if (nargin == 4) + { + r0 = args(2).ulong_value(); + c0 = args(3).ulong_value(); + } + } + + if (!error_state) + { + unsigned long maxrows = r1 - r0; + + // Skip the r0 leading lines as these might be a header + for (unsigned long m = 0; m < r0; m++) + getline (file, line); + r1 -= r0; + + + // read in the data one field at a time, growing the data matrix as needed + while (getline (file, line)) + { + // skip blank lines for compatibility + if (line.find_first_not_of (" \t") == NPOS) + continue; + + //to be compatible with matlab, blank separator should correspond + //to whitespace as delimter; + if (! sep.length ()) + { + size_t n = line.find_first_of (",:; \t", + line.find_first_of ("0123456789")); + if (n == NPOS) + { + sep = " \t"; + sepflag = 1; + } + else + { + char ch = line.at (n); + + switch (line.at (n)) + { + case ' ': + case '\t': + sepflag = 1; + default: + sep = ch; + } + } + } + + r = (r > i + 1 ? r : i + 1); + j = 0; + pos1 = 0; + do { + pos2 = line.find_first_of (sep, pos1); + str = line.substr (pos1, pos2 - pos1); + + if (sepflag && pos2 != NPOS) + // treat consecutive separators as one + pos2 = line.find_first_not_of (sep, pos2) - 1; + + c = (c > j + 1 ? c : j + 1); + if (r > rmax || c > cmax) + { + // use resize_and_fill for the case of not-equal length rows + if (iscmplx) + cdata.resize_and_fill (r, c, 0); + else + rdata.resize_and_fill (r, c, 0); + rmax = r; + cmax = c; + } + + std::istringstream tmp_stream (str); + double x = octave_read_double (tmp_stream); + if (tmp_stream) + { + if (tmp_stream.eof()) + if (iscmplx) + cdata (i, j++) = x; + else + rdata (i, j++) = x; + else + { + double y = octave_read_double (tmp_stream); + + if (!iscmplx && y != 0.) + { + iscmplx = true; + cdata = ComplexMatrix (rdata); + } + + if (iscmplx) + cdata (i, j++) = Complex (x, y); + else + rdata (i, j++) = x; + } + } + else if (iscmplx) + cdata (i, j++) = 0.; + else + rdata (i, j++) = 0.; + + if (pos2 != NPOS) + pos1 = pos2 + 1; + else + pos1 = NPOS; + + } while ( pos1 != NPOS ); + if (nargin == 3 && i == maxrows) + break; + i++; + } + + if (nargin > 2) + { + if (nargin == 3) + { + if (r1 >= r) + r1 = r - 1; + if (c1 >= c) + c1 = c - 1; + } + else if (nargin == 4) + { + // if r1 and c1 are not given, use what was found to be the maximum + r1 = r - 1; + c1 = c - 1; + } + + // now take the subset of the matrix + if (iscmplx) + { + cdata = cdata.extract (0, c0, r1, c1); + cdata.resize (r1 + 1, c1 - c0 + 1); + } + else + { + rdata = rdata.extract (0, c0, r1, c1); + rdata.resize (r1 + 1, c1 - c0 + 1); + } + } + + if (iscmplx) + retval(0) = octave_value(cdata); + else + retval(0) = octave_value(rdata); + } + + return retval; +} + +/* + +%!shared file +%! file = tmpnam (); +%! fid = fopen (file, "wt"); +%! fwrite (fid, "1, 2, 3\n4, 5, 6\n7, 8, 9\n10, 11, 12"); +%! fclose (fid); + +%!assert (dlmread (file), [1, 2, 3; 4, 5, 6; 7, 8, 9;10, 11, 12]); +%!assert (dlmread (file, ","), [1, 2, 3; 4, 5, 6; 7, 8, 9; 10, 11, 12]); +%!assert (dlmread (file, ",", [1, 0, 2, 1]), [4, 5; 7, 8]); +%!assert (dlmread (file, ",", "B1..C2"), [2, 3; 5, 6]); +%!assert (dlmread (file, ",", "B1:C2"), [2, 3; 5, 6]); +%!assert (dlmread (file, ",", "..C2"), [1, 2, 3; 4, 5, 6]); +%!assert (dlmread (file, ",", 0, 1), [2, 3; 5, 6; 8, 9; 11, 12]); +%!assert (dlmread (file, ",", "B1.."), [2, 3; 5, 6; 8, 9; 11, 12]); +%!error (dlmread (file, ",", [0 1])) + +%!test +%! unlink (file); + +%!shared file +%! file = tmpnam (); +%! fid = fopen (file, "wt"); +%! fwrite (fid, "1, 2, 3\n4+4i, 5, 6\n7, 8, 9\n10, 11, 12"); +%! fclose (fid); + +%!assert (dlmread (file), [1, 2, 3; 4 + 4i, 5, 6; 7, 8, 9; 10, 11, 12]); +%!assert (dlmread (file, ","), [1, 2, 3; 4 + 4i, 5, 6; 7, 8, 9; 10, 11, 12]); +%!assert (dlmread (file, ",", [1, 0, 2, 1]), [4 + 4i, 5; 7, 8]); +%!assert (dlmread (file, ",", "A2..B3"), [4 + 4i, 5; 7, 8]); +%!assert (dlmread (file, ",", "A2:B3"), [4 + 4i, 5; 7, 8]); +%!assert (dlmread (file, ",", "..B3"), [1, 2; 4 + 4i, 5; 7, 8]); +%!assert (dlmread (file, ",", 1, 0), [4 + 4i, 5, 6; 7, 8, 9; 10, 11, 12]); +%!assert (dlmread (file, ",", "A2.."), [4 + 4i, 5, 6; 7, 8, 9; 10, 11, 12]); +%!error (dlmread (file, ",", [0 1])) + +%!test +%! unlink (file); + +*/ diff --git a/src/Makefile.in b/src/Makefile.in --- a/src/Makefile.in +++ b/src/Makefile.in @@ -64,8 +64,8 @@ DLD_XSRC := balance.cc besselj.cc betainc.cc bsxfun.cc cellfun.cc chol.cc \ ccolamd.cc colamd.cc colloc.cc conv2.cc convhulln.cc daspk.cc \ - dasrt.cc dassl.cc det.cc dispatch.cc dmperm.cc eig.cc expm.cc \ - fft.cc fft2.cc fftn.cc fftw.cc filter.cc find.cc fsolve.cc \ + dasrt.cc dassl.cc det.cc dispatch.cc dlmread.cc dmperm.cc eig.cc \ + expm.cc fft.cc fft2.cc fftn.cc fftw.cc filter.cc find.cc fsolve.cc \ gammainc.cc gcd.cc getgrent.cc getpwent.cc getrusage.cc \ givens.cc hess.cc inv.cc kron.cc lsode.cc \ lu.cc luinc.cc matrix_type.cc md5sum.cc minmax.cc pinv.cc qr.cc \