24

Context

In R2016b, MATLAB introduced a new string datatype, in addition to the usual char datatype. So far, so good, but it is now giving me a lot of issues with the JSONlab toolbox I'm using.

For instance, in R2015b, loadjson returns a 1x3 cell character array:

dd = loadjson('["Titi", "Toto", "Tata"]')

dd = 

    'Titi'    'Toto'    'Tata'

But in R2018a, loadjson returns a 1x3 string array:

dd = loadjson('["Titi", "Toto", "Tata"]')

dd =

  1×3 cell array

    {["Titi"]}    {["Toto"]}    {["Tata"]}

Problem

For not having to change my code everywhere, I'd like to patch the loadjson routine to replace all string types it may return with char types. For instance, in the following cell array:

test = { 'hello', "world", 0.3; 'how', 'are', "you"}

test =

  2×3 cell array

    {'hello'}    {["world"]}    {[0.3000]}
    {'how'  }    {'are'    }    {["you" ]}

I'd like to replace all strings:

cellfun(@isstring, test)

ans =

  2×3 logical array

   0   1   0
   0   0   1

Is there a way I can do it quickly (i.e. without looping through all elements) ?

PS: I know of jsondecode and jsonencode to replace JSONLab in the future, but so far I just want to quickly patch things.

CitizenInsane
  • 4,755
  • 1
  • 25
  • 56

4 Answers4

16

You can use cellstr (confusingly, despite "str" suggesting string) to convert strings to character arrays without looping or cellfun... the docs state the following:

C = cellstr(A) converts A to a cell array of character vectors. The input array A can be a character array, a categorical array, or, starting in R2016b, a string array.

test = {'hello', "world", 0.3; 'how', 'are', "you"}; % multi-type test cell array
ind = cellfun(@isstring, test);                      % indexing for string type items
test(ind) = cellstr(test(ind))                       % char-ify the strings!

A cellfun performance note for class checks:

In both mine and Luis' answers, cellfun is used to determine which elements are strings. You can improve the performance of cellfun for this task...

Per the cellfun docs, there are some character array options which are much quicker than their function-handle counterparts. For the isstring indexing, it's likely a lot faster to run the first of these:

% rapid
ind = cellfun('isclass', test, 'string');
% akin to looping
ind = cellfun(@isstring, test);

They have the same output, in a simple test I see a 4x speed improvement:

% Create large test array of random strings
c = cell(100,1000);
c = cellfun(@(x) string(char(randi([65,122],1,10))), c, 'uni', 0);

% Create functions for benchmarking 
f=@()cellfun('isclass', c, 'string');
g=@()cellfun(@isstring,c);

% Timing on MATLAB R2017b
timeit( f ) % >> 0.017sec
timeit( g ) % >> 0.066sec 
Wolfie
  • 27,562
  • 7
  • 28
  • 55
  • I already validated for @LuisMendo, but that's a good one too ! :) – CitizenInsane Jul 05 '18 at 14:13
  • @CitizenInsane Acceptance marks are never set in stone ;) In fairness some quick tests show that the `cellfun` option is approx. the same speed as `cellstr`, so the only advantage is readability. – Wolfie Jul 05 '18 at 14:19
  • Thanks for extra edit and deep inspection in speed improvement for checking if element is a `string` type ... you won :) – CitizenInsane Jul 05 '18 at 14:44
11

You can use cellfun, but it has more or less the same performance as a loop:

test = {'hello', "world", 0.3; 'how', 'are', "you"};
ind = cellfun(@isstring, test);
test(ind) = cellfun(@char, test(ind), 'UniformOutput', false)
Luis Mendo
  • 110,752
  • 13
  • 76
  • 147
11

As of MATLAB R2017b you can use convertstringstochars:

[test{:}] = convertStringsToChars(test{:});
Wolfie
  • 27,562
  • 7
  • 28
  • 55
rahnema1
  • 15,264
  • 3
  • 15
  • 27
  • 1
    I removed the typo additional spaces and tested this on R2017b, it works as expected (nicely leaves numeric elements as numeric too without complaint) – Wolfie Jul 05 '18 at 14:27
8

Another solution, discussed in the UndocumentedMATLAB blog, is the "semi-documented" function of controllib.internal.util.hString2Char. This is how you use it:

test = { 'hello', "world", 0.3; 'how', 'are', "you"};
fixed_test = controllib.internal.util.hString2Char(test);

fixed_test =

  2×3 cell array

    {'hello'}    {'world'}    {[0.3000]}
    {'how'  }    {'are'  }    {'you'   }

According to the blog post, this function goes recursively through the input, so it works even in a situation like this:

test = {"target1", struct('field',{123,'456',"789"})};
ft = controllib.internal.util.hString2Char(test);
{ft{2}.field}

ans =

  1×3 cell array

    {[123]}    {'456'}    {'789'}

Take a look at the blog post for some caveats.

Dev-iL
  • 23,742
  • 7
  • 57
  • 99
  • 1
    I would never have thought of so many good and various answer to just converting string to char ! :) – CitizenInsane Jul 05 '18 at 14:48
  • 1
    @CitizenInsane growing pains from the introduction of a new data class to a decades-old language :) – sco1 Jul 05 '18 at 15:17