/MATLAnswer.m Secret
Last active
January 22, 2017 20:55
This is a class for gathering information about the actual usage of MATL functions on https://codegolf.stackexchange.com
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
classdef MATLAnswer < handle | |
% MATLAnswer - class to represent a MATL answer | |
% | |
% This class works in conjunction with the Stack Exchange API | |
% to download MATL answers that were used to solve challenges | |
% on https://codegolf.stackexchange.com. Using this class, you | |
% can gather usage statistics of various MATL functions. | |
% Including input/output argument specification. | |
% | |
% EXAMPLES: | |
% | |
% Fecthing All Answers | |
% answers = MATLAnswer.fetch(); | |
% | |
% Fetching Answers After a Date | |
% recentAnswers = MATLAnswer.fetch('fromdate', '2016-03-01'); | |
% | |
% Create Plots of Usage | |
% MATLAnswer.plots(); | |
% | |
% Using the MATLAnswer Object | |
% answers = MATLAnswer.fetch(); | |
% | |
% % Get a list of functions that are used | |
% funcs = functions(answers); | |
% | |
% % Get a list of input arguments used | |
% [inputargs, funcs] = specifiedInputs(answers); | |
% | |
% % Get a list of output arguments used | |
% [outputargs, funcs] = specifiedOutputs(answers); | |
% | |
% % Create a histogram of function usage | |
% h = functionHistogram(answers); | |
% | |
% % Create a histogram of meta function usage | |
% h = metaHistogram(answers); | |
% | |
% % Check which answers use a given function | |
% hasMod = uses(answers, '\'); | |
% answers_with_mod = answers(hasMod); | |
% | |
% % Check which answers use a function with a specific input spec | |
% hasSpec = uses(answers, '2$l'); | |
% two_input_ones_answers = answers(hasSpec); | |
% | |
% % Check which answers use a function with a specific out spec | |
% hasSpec = uses(answers, '2#f'); | |
% two_output_find_answers = answers(hasSpec); | |
% | |
% % Open a browser to one of the answers | |
% open(answers(1)) | |
% | |
% % Open a tab for each of the first four answers | |
% open(answers(1:4)) | |
properties | |
MetaData % additional metadata returned by the API | |
Source % MATL source code for the answer | |
Valid = true % Flag to indicate whether this is valid MATL | |
end | |
properties (Dependent) | |
Accepted % Whether an answer was accepted or not | |
Hyperlink % Link to the original answer | |
ID % ID of the answer | |
Score % Score of the answer | |
QuestionID % ID of the question | |
CreationDate % Date an answer was initially created | |
LastActivity % Date an answer was last edited | |
Owner % ID of the owner of the question | |
URL % URL of the answer | |
end | |
properties (Hidden, Constant) | |
% The URL to the stackexchange API | |
API_URL = 'https://api.stackexchange.com/2.2'; | |
end | |
properties (Hidden) | |
Parts % Results of matl_parse on the source code | |
end | |
methods | |
function self = MATLAnswer(metadata) | |
% MATLAnswer - constructor for the MATLAnswer class | |
% | |
% USAGE: | |
% A = MATLAnswer(metadata) | |
% | |
% INPUTS: | |
% metadata: Struct, information returned from the stack | |
% exchange API. | |
% | |
% | |
% OUTPUTS: | |
% A: Handle, Handle to the MATLAnswer object. | |
% If an array of metadata structs are passed in, create a | |
% MATLAnswer object for each of them | |
if numel(metadata) > 1 | |
objs = arrayfun(@MATLAnswer, metadata, 'uni', 0); | |
self = reshape(cat(1, objs{:}), size(metadata)); | |
return; | |
end | |
% Store the metadata | |
self.MetaData = metadata; | |
% Extract the source code component. For this we use | |
% <pre><code> in the HTML rather than the Markdown just because | |
% code can be posted as either indented or surrounded by ``. We | |
% use the HTML so SO can deal with that conversion for us. | |
% We also grab the first code block (with no newlines). This | |
% prevents us from grabbing explanation text if it is in a | |
% separate or the same codeblock. | |
pattern = '(?<=<pre><code>[\s\n]*)[^\n]*'; | |
code = regexp(metadata.body, pattern, 'match', 'once'); | |
% Now convert any HTML encoded symbols (<, >, &, etc.) | |
import org.apache.commons.lang.StringEscapeUtils; | |
self.Source = char(StringEscapeUtils.unescapeHtml(code)); | |
% Now use the MATL parser to actually parse the source code | |
try | |
self.Parts = matl_parse(self.Source, false); | |
catch ME | |
% If there was a parse error, then just mark as invalid | |
if strncmpi(ME.identifier, 'MATL:parser', 11) | |
self.Valid = false; | |
else | |
rethrow(ME); | |
end | |
end | |
end | |
function [f, ind] = functions(self) | |
% functions - List of functions that are used by the answers | |
% | |
% This listing excludes all literal numbers and literal | |
% strings. | |
% | |
% USAGE: | |
% [F, ind] = functions(self) | |
% | |
% INPUTS: | |
% self: Handle, Scalar or array of MATLAnswer objects | |
% | |
% OUTPUTS: | |
% F: [1 x N] Cell Array, Strings representing all | |
% functions that were used by any of the MATLAnswer | |
% objects passed to the function. | |
% | |
% ind: [1 x N] Array, Indices into the input object | |
% indicating which object used a particular function. | |
% If an array was passed in, parse each entry and concatenate | |
% the results. | |
if numel(self) > 1 | |
f = arrayfun(@functions, self, 'uni', 0); | |
% Figure out how many outputs were from each object | |
ind = repelem(1:numel(f), cellfun(@numel, f)); | |
f = cat(2, f{:}); | |
return | |
end | |
% This is going to exclude number literals and string literals | |
toexclude = {'literal.number', 'literal.string'}; | |
isliteral = ismember({self.Parts.type}, toexclude); | |
f = {self.Parts(~isliteral).source}; | |
ind = ones(size(f)); | |
end | |
function [inputs, funcs, inds] = specifiedInputs(self) | |
% specifiedInputs - Get all instances where $ was used | |
% | |
% This function returns all instances where $ was used to | |
% indicate the number of input arguments. The result is a | |
% cell array indicating what the preceding value for $ was as | |
% well as the function for which $ was being used. | |
% | |
% [inputs, functions, inds] = specifiedInputs(self) | |
% | |
% INPUTS: | |
% self: Handle, Scalar or array of MATLAnswer objects | |
% | |
% OUTPUTS: | |
% inputs: [1 x N] Cell Array, Cell array containing what | |
% the specified number of inputs actually was. | |
% All values (even if numeric) are returned as | |
% strings for consistency. | |
% | |
% functions: [1 x N] Cell Array, Strings corresponding to | |
% the previous output variable which indicate | |
% which MATL function the input specification was | |
% applied to. | |
% | |
% inds: [1 x N] Array, Index values corresponding to | |
% which of the input objects each of the outputs | |
% belongs to. | |
% If an array of answers were provided, compute this for all of | |
% them and concatenate the results | |
if numel(self) > 1 | |
[I, F] = arrayfun(@specifiedInputs, self, 'uni', 0); | |
inds = repelem(1:numel(I), cellfun(@numel, I)); | |
inputs = cat(2, I{:}); | |
funcs = cat(2, F{:}); | |
return | |
end | |
% Find all usages of $ (metaFunction.inSpec) | |
isInput = ismember({self.Parts.type}, 'metaFunction.inSpec'); | |
% Find the numbers that preceded $ | |
inputs = self.findNumericInputs(self.Parts, isInput, self); | |
% Find the first function call after each $ usage | |
funcs = self.findNextFunction(self.Parts, isInput); | |
% Remove empty results (ones which failed validation) | |
toremove = cellfun('isempty', inputs); | |
inputs(toremove) = []; | |
funcs(toremove) = []; | |
% Filler for the indices | |
inds = ones(size(inputs)); | |
end | |
function [outputs, funcs, inds] = specifiedOutputs(self) | |
% specifiedOutputs - Get all instances where # was used | |
% | |
% This function returns all instances where $ was used to | |
% indicate the number of input arguments. The result is a | |
% cell array indicating what the preceding value for $ was as | |
% well as the function for which $ was being used. | |
% | |
% [outputs, functions, inds] = specifiedOutputs(self) | |
% | |
% INPUTS: | |
% self: Handle, Scalar or array of MATLAnswer objects | |
% | |
% OUTPUTS: | |
% ouptuts: [1 x N] Cell Array, Cell array containing what | |
% the specified number of outputs actually was. | |
% All values (even if numeric) are returned as | |
% strings for consistency. | |
% | |
% functions: [1 x N] Cell Array, Strings corresponding to | |
% the previous output variable which indicate | |
% which MATL function the output specification | |
% was applied to. | |
% | |
% inds: [1 x N] Array, Index values corresponding to | |
% which of the input objects each of the outputs | |
% belongs to. | |
% If an array of answers were provided, compute this for all of | |
% them and concatenate the results | |
if numel(self) > 1 | |
[outs, F] = arrayfun(@specifiedOutputs, self, 'uni', 0); | |
inds = repelem(1:numel(outs), cellfun(@numel, outs)); | |
outputs = cat(2, outs{:}); | |
funcs = cat(2, F{:}); | |
return | |
end | |
% Find all usages of # (metaFunction.outSpec) | |
isOutput = ismember({self.Parts.type}, 'metaFunction.outSpec'); | |
% Find the number that preceded # | |
outputs = self.findNumericInputs(self.Parts, isOutput, self); | |
% Find the first function call after each # usage | |
funcs = self.findNextFunction(self.Parts, isOutput); | |
% Remove empty results (ones which failed validation) | |
toremove = cellfun('isempty', outputs); | |
outputs(toremove) = []; | |
funcs(toremove) = []; | |
% Filler for the indices | |
inds = ones(size(outputs)); | |
end | |
function stat = open(self, browser, new) | |
% open - Opens a given answer in the MATLAB browser | |
% | |
% USAGE: | |
% stat = open(self, browser, new) | |
% | |
% INPUTS: | |
% browser: Logical, Indicates whether to use the system | |
% browser (true) or not (false) [Default = false] | |
% | |
% new: Logical, Indicates whether to open the answer | |
% in a new tab (true) or not (false). If an array | |
% of answers are provided, this open is ignored | |
% and a tab is opened per answer. [Default = | |
% true] | |
% | |
% OUTPUTS: | |
% stat: Integer, Indicates the status of the 'web' | |
% command in MATLAB. See built-in help for 'web' | |
% for more information | |
flags = {}; | |
system_browser = exist('browser', 'var') && browser; | |
if system_browser | |
flags{end+1} = '-browser'; | |
elseif (~exist('new', 'var') || new) || numel(self) > 1 | |
flags{end+1} = '-new'; | |
end | |
% Find the answers with unique URLs | |
stat = cellfun(@(x)web(x, flags{:}), {self.URL}); | |
end | |
function bool = uses(self, func) | |
% uses - Determines whether the answer uses a specific function | |
% | |
% This method determines whether a given MATL answer uses a | |
% particular function. In addition to a standard function | |
% name, you can specify the input/output spec to filter the | |
% result to only those answers that use that function AND use | |
% the specified input/output spec. | |
% | |
% USAGE: | |
% bool = uses(self, func) | |
% | |
% INPUTS: | |
% func: String, Function to check usage for. This can | |
% either be a plain function call (i.e. 'f') or a | |
% function with a specific input spec (i.e. '2$f') or | |
% output spec (i.e. '2#f') | |
% | |
% OUTPUTS: | |
% bool: Logical, A logical array where the value is true if | |
% the function was used in a particular answer or | |
% false if it was not. This is an array the same size | |
% as the object array passed to this function. | |
if numel(self) > 1 | |
bool = arrayfun(@(x)uses(x, func), self); | |
return; | |
end | |
% First check to see if input or output spec is here. | |
parts = matl_parse(func, false); | |
% Remove the implicit ones | |
parts = parts(~[parts.implicit]); | |
% First check for the function | |
isFunction = ismember({parts.type}, 'function'); | |
if sum(isFunction) > 1 | |
error(sprintf('%s:InvalidInput', mfilename), ... | |
'You can only specify one function'); | |
elseif ~any(isFunction) | |
error(sprintf('%s:InvalidInput', mfilename), ... | |
'You must specify one function'); | |
end | |
func = parts(isFunction).source; | |
% Now look at functions | |
funcs = functions(self); | |
bool = ismember(func, funcs); | |
if ~bool; return; end | |
% Check for input spec | |
[tf, ind] = ismember('metaFunction.inSpec', {parts.type}); | |
if tf | |
% Then filter the results | |
nInputs = parts(ind-1).source; | |
[inSpec, funcs] = specifiedInputs(self); | |
% Look for where the function and spec match | |
ismatch = ismember(inSpec, nInputs) & ... | |
ismember(funcs, func); | |
bool = bool && any(ismatch); | |
if ~bool; return; end | |
end | |
[tf, ind] = ismember('metaFunction.outSpec', {parts.type}); | |
if tf | |
nOutputs = parts(ind-1).source; | |
[outSpec, funcs] = specifiedOutputs(self); | |
ismatch = ismember(outSpec, nOutputs) & ... | |
ismember(funcs, func); | |
bool = bool && any(ismatch); | |
if ~bool; return; end | |
end | |
end | |
function h = functionHistogram(self) | |
% functionHistogram - Create an image showing function usage | |
% | |
% USAGE: | |
% h = functionHistogram(self) | |
% | |
% INPUTS: | |
% self: Handle, Scalar or array of MATLAnswer objects | |
% | |
% OUTPUTS: | |
% h: Graphics Handle, handle to the pcolor plot | |
% object showing the histogram. | |
% Get a list of all functions that are used | |
F = functions(self); | |
% All possible functions and modifiers | |
rows = ['!"#$%&''()*+,-./0123456789:;<=>?@', ... | |
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', ... | |
'[\]^_`abcdefghijklmnopqrstuvwxyz{|}~']; | |
rows = num2cell(rows); | |
modifiers = {'', 'X', 'Y', 'Z'}; | |
% Compute the number of occurances for each one | |
results = zeros(numel(rows) + 1, numel(modifiers) + 1); | |
for k = 1:numel(modifiers) | |
combos = strcat(modifiers{k}, rows); | |
[~, inds] = ismember(F, combos); | |
for m = 1:numel(combos) | |
results(m,k) = sum(inds == m); | |
end | |
end | |
% Show the histogram image | |
h = self.histogramImage(results, modifiers, rows); | |
% Update the title | |
title('MATL Function Usage') | |
end | |
function h = metaHistogram(self, varargin) | |
% metaHistogram - Create an image showing metafunction usage | |
% | |
% USAGE: | |
% h = metaHistogram(self) | |
% | |
% INPUTS: | |
% self: Handle, Scalar or array of MATLAnswer objects | |
% | |
% OUTPUTS: | |
% h: Graphics Handle, handle to the pcolor plot | |
% object showing the histogram. | |
% Get all input specifications | |
[inputs, inputfuncs] = specifiedInputs(self); | |
% Get all output specifications | |
[outputs, outputfuncs] = specifiedOutputs(self); | |
% Add $ to all input specs and # to all output specs and sort | |
[inputs, iind] = sort(strcat(inputs, '$')); | |
[outputs, oind] = sort(strcat(outputs, '#')); | |
% Apply the sorting to the function names as well | |
inputfuncs = inputfuncs(iind); | |
outputfuncs = outputfuncs(oind); | |
% Combine the input/output data | |
nargs = cat(1, inputs(:), outputs(:)); | |
funcs = cat(1, inputfuncs(:), outputfuncs(:)); | |
% Now compute the histogram | |
[uniqueArgs, ~, ib] = unique(nargs, 'stable'); | |
[uniqueFunctions, ~, fb] = unique(funcs); | |
results = zeros(numel(uniqueFunctions)+1, numel(uniqueArgs)+1); | |
for k = 1:numel(uniqueFunctions) | |
for m = 1:numel(uniqueArgs) | |
results(k,m) = sum(ib == m & fb == k); | |
end | |
end | |
% Display the histogram | |
h = self.histogramImage(results, uniqueArgs, uniqueFunctions); | |
% Now draw a thick vertical line between inputs/outputs | |
xpos = numel(unique(inputs(:))) + 1; | |
ylims = get(gca, 'ylim'); | |
axis('manual') | |
plot([xpos, xpos], ylims, 'Color', 'k', 'linewidth', 3) | |
% Update title/labels | |
title('MATL Meta-Function Usage') | |
xlabel('Explicit Number Inputs/Outputs') | |
ylabel('Function') | |
end | |
end | |
%--- Getters and Setters ---% | |
methods | |
function res = get.Hyperlink(self) | |
% Convert the hyperlink to a clickable link | |
url = self.URL; | |
res = sprintf('<a href="%s">%s</a>', url, url); | |
end | |
function res = get.URL(self) | |
format = 'http://codegolf.stackexchange.com/a/%d'; | |
res = sprintf(format, self.MetaData.answer_id); | |
end | |
function res = get.ID(self) | |
res = self.MetaData.answer_id; | |
end | |
function res = get.Score(self) | |
res = self.MetaData.score; | |
end | |
function res = get.QuestionID(self) | |
res = self.MetaData.question_id; | |
end | |
function res = get.CreationDate(self) | |
dt = self.MetaData.creation_date; | |
res = datetime(dt, 'ConvertFrom', 'epochtime'); | |
end | |
function res = get.LastActivity(self) | |
dt = self.MetaData.last_activity_date; | |
res = datetime(dt, 'ConvertFrom', 'epochtime'); | |
end | |
function res = get.Accepted(self) | |
res = self.MetaData.is_accepted; | |
end | |
function res = get.Owner(self) | |
res = self.MetaData.owner.user_id; | |
end | |
end | |
methods (Static, Access = 'protected') | |
function h = histogramImage(data, xlabels, ylabels) | |
% histogramImage - Creates a histogram-based image w/ labels | |
% | |
% This helper function creates a pcolor instance showing the | |
% 2D histogram that is passed to it. The color scales from 1 | |
% (white) to the max (dark green). Any zeros are shown as | |
% gray. | |
% | |
% USAGE: | |
% h = MATLAnswer.histogramImage(data, xlabels, ylabels) | |
% | |
% INPUTS: | |
% data: [M x N] Matrix, Contains the number of counts for | |
% each 2D bin | |
% | |
% xlabels: [1 x N] Cell Array, Labels for the x dimension | |
% of the histogram | |
% | |
% ylabels: [1 x M] Cell Array, Labels for the y dimension | |
% of the histogram | |
% | |
% OUTPUTS: | |
% h: Graphics Handle, handle to the pcolor plot that | |
% was generated. | |
% Determine the default width/height of the figure window | |
height = max(300, size(data, 1) * 20); | |
width = max(300, size(data, 2) * 30); | |
figure('Position', [0 0 width height]); | |
% Create the pcolor plot | |
h = pcolor(data); | |
% Compute the colormap to use | |
N = max(data(:)); | |
CMAP = [linspace(1, 0, N); ... | |
linspace(1, 0.5, N); ... | |
linspace(1, 0, N)].'; | |
% Use gray for zero usages | |
CMAP = cat(1, [0.8 0.8 0.8], CMAP); | |
% Apply the colormap | |
colormap(CMAP); | |
% Place x/y labels as requested | |
xpos = (1:size(data, 2)) + 0.5; | |
ypos = (1:size(data, 1)) + 0.5; | |
[xx,yy] = meshgrid(xpos, ypos); | |
set(gca, 'xtick', xpos, 'xticklabels', xlabels, ... | |
'ytick', ypos, 'yticklabels', ylabels, ... | |
'LooseInset', [0 0 0 0], ... | |
'OuterPosition', [0 0 1 1]); | |
hold on | |
% Default text properties | |
args = {'VerticalAlignment', 'middle', ... | |
'HorizontalAlignment', 'center', ... | |
'FontWeight', 'bold'}; | |
% Place all numeric labels (when non-zero) | |
for k = 1:numel(data) | |
if data(k) | |
text(xx(k), yy(k), num2str(data(k)), args{:}); | |
end | |
end | |
% Polish up the axes and figure | |
set(gca, 'ydir', 'reverse', 'fontweight', 'bold') | |
set(gcf, 'PaperPositionMode', 'auto') | |
end | |
function funcs = findNextFunction(data, functionIndex) | |
% findNextFunction - Finds the next available function call | |
% | |
% Given an index, this function seeks to find the next | |
% function call and returns the name of that function. | |
% | |
% USAGE: | |
% funcs = MATLAnswer.findNextFunction(data, index) | |
% | |
% INPUTS: | |
% data: [1 x N] Struct, An array of structs where each | |
% entry is an entry from matl_parse. This is used to | |
% find the next function. | |
% | |
% index: [1 x N] Logical, or [1 x M] Integer, Reference | |
% index which indicates which entry in data we should | |
% use as the reference point for finding the next | |
% function. | |
% | |
% OUTPUTS: | |
% funcs: [1 x M] Cell Array, An array of function names that | |
% were found to follow the specified indices. | |
% Convert logical values to index-based values | |
if islogical(functionIndex) | |
functionIndex = find(functionIndex); | |
end | |
% Figure out which of the parse values are actually functions | |
isFunction = find(ismember({data.type}, 'function')); | |
funcs = cell(1, numel(functionIndex)); | |
% Find the next closest function after the current one | |
for k = 1:numel(functionIndex) | |
nextind = min(isFunction(isFunction > functionIndex(k))); | |
funcs{k} = data(nextind).source; | |
end | |
end | |
function inputs = findNumericInputs(data, functionIndex, obj) | |
% findNumericInputs - Find the number that precedes a function | |
% | |
% This function is used to identify the number which precedes | |
% a function, specifically $ or #. If it encounters a | |
% function such as q, Q, or t instead of an actual number, an | |
% attempt is made to look back in the stack for the number | |
% literal. | |
% | |
% USAGE: | |
% inputs = findNumericInputs(data, index, obj) | |
% | |
% INPUTS: | |
% data: [1 x N] Struct, An array of structs where each | |
% entry is an entry from matl_parse. This is used to | |
% find the preceding number literal. | |
% | |
% index: [1 x N Logical or [1 x M] Integer, Reference | |
% index which indicates which entry in data we should | |
% use as the reference for finding the preceding | |
% number literal. | |
% | |
% OUTPUTS: | |
% inputs: [1 x M] Cell Array, The number literal (as a | |
% string) that was found to preceded each entry | |
% specified by index. | |
% Convert logical indices into actual index values | |
if islogical(functionIndex) | |
functionIndex = find(functionIndex); | |
end | |
% Now locate the previous entry before the function | |
previous = data(functionIndex - 1); | |
inputs = cell(1, numel(previous)); | |
for k = 1:numel(previous) | |
switch previous(k).type | |
% For number literals and logicals, use as-is | |
case {'literal.number', 'literal.logicalRowArray'} | |
inputs{k} = previous(k).source; | |
case 'function' | |
% Check to see if this function is a pre-defined | |
% number (H, I, K, N, etc.) | |
num = MATLAnswer.predefinedNumber(previous(k).source); | |
% If it was a pre-defined literal go to the next | |
if ~isempty(num) | |
inputs{k} = num; | |
continue; | |
end | |
% Otherwise we have a stack manipulation that we | |
% will try to work with | |
if ismember(previous(k).source, {'q', 't', 'Q'}) | |
% Now we can go get the previous value | |
prevPrevious = data(functionIndex(k) - 2); | |
% Convert to a number (if possible) | |
num = MATLAnswer.predefinedNumber(prevPrevious.source); | |
if ~isempty(num) | |
switch previous(k).source | |
case 'q' % Decrement by 1 | |
increment = -1; | |
case 't' % Duplicate | |
increment = 0; | |
case 'Q' % Increment by 1 | |
increment = 1; | |
end | |
% Special case when input is `N` | |
if isequal(num, 'N') | |
inputs{k} = [num, previous(k).source]; | |
continue; | |
end | |
% Attempt to apply the increment | |
num = str2double(num) + increment; | |
% If there was a NaN something went wrong | |
if ~isnan(num) | |
inputs{k} = num2str(num); | |
continue; | |
end | |
end | |
end | |
% If we haven't parsed it, warn and move on | |
throwWarning(previous(k).source, obj) | |
otherwise | |
% If this is something we didn't account for, warn | |
% and move onto the next | |
throwWarning(previous(k).source, obj) | |
end | |
end | |
function throwWarning(src, obj) | |
% throws a warning with helpful debug info | |
warning('Unexpected input argument: %s', src); | |
disp(obj) | |
end | |
end | |
function num = predefinedNumber(src) | |
% Converts the source to a number literal (if possible) | |
% | |
% USAGE: | |
% num = MATLAnswer.predefinedNumber(src) | |
% | |
% INPUTS: | |
% src: String, Source that we want to attempt to convert | |
% to a number literal. | |
% | |
% OUTPUTS: | |
% num: String, Number literal (if possible), otherwise an | |
% empty array is returned. | |
% Map all functions that are pre-defined number litearls | |
mapper = {'O', '0'; | |
'l', '1'; | |
'H', '2'; | |
'I', '3'; | |
'K', '4'; | |
'N', 'N'}; | |
% First try to convert if already a number literal | |
if ~isnan(str2double(src)) | |
num = src; | |
return | |
end | |
% Now look to map pre-defined functions to number literals | |
[tf, ind] = ismember(src, mapper(:,1)); | |
if tf | |
num = mapper{ind, 2}; | |
else | |
% Return an empty string if this fails | |
num = []; | |
end | |
end | |
function data = getData(url, varargin) | |
% getData - Makes a call to the API URL to retrieve data | |
% | |
% USAGE: | |
% data = MATLAnswer.getData(url, varargin) | |
% | |
% INPUTS: | |
% url: String, URL to query, expects a JSON response | |
% varargin: Any additional parameters to pass to webread | |
% to perform the query. | |
% | |
% OUTPUTS: | |
% data: [M x 1] Cell Array, Each element contains a | |
% single object returned by webread. | |
% Default parameters to pass to the API | |
params = {'order', 'desc', ... | |
'sort', 'creation', ... | |
'pagesize', 100, ... | |
'site', 'codegolf'}; | |
% Loop until we have all records | |
resp.has_more = true; | |
page = 1; | |
data = {}; | |
while resp.has_more | |
resp = webread(url, params{:}, 'page', page, varargin{:}); | |
% If an array of structs was returned, convert to a cell | |
% array for consistency | |
if ~iscell(resp.items) | |
resp.items = num2cell(resp.items); | |
end | |
% Concatenate all data | |
data = cat(1, data, resp.items(:)); | |
page = page + 1; | |
end | |
end | |
end | |
methods (Static) | |
function plots(varargin) | |
% plots - Create (and save) histogram plots | |
% | |
% USAGE: | |
% MATLAnswer.plots(varargin) | |
% | |
% INPUTS: | |
% varargin: ..., Same inputs as to MATLAnswer.fetch() | |
answers = MATLAnswer.fetch(varargin{:}); | |
functionHistogram(answers); | |
print(gcf, 'MATL.Functions.png', '-dpng', '-r300'); | |
metaHistogram(answers); | |
print(gcf, 'MATL.Meta.Usage.png', '-dpng', '-r300'); | |
end | |
function res = fetch(ignore, varargin) | |
% fetch - Fetch all current answers and return MATLAnswer's | |
% | |
% This static method requests all current MATL answers using | |
% the stack exchange API and parses the result into an array | |
% of MATLAnswer objects. These objects can then be used to | |
% understand and visualize the way that MATL functions are | |
% currently being used. | |
% | |
% USAGE: | |
% R = MATLAnswer.fetch(toignore, pvpairs) | |
% R = MATLAnswer.fetch(pvpairs) | |
% | |
% INPUTS: | |
% toignore: [1 x N] Array, Array of question IDs to ignore. | |
% By default we ignore cops/robbers threads as | |
% those never parse correctly due to the usage of | |
% # as an unknown character place-holder. Any | |
% specific question ID can be placed here. | |
% | |
% pvpairs: Parameter/value pairs, Any additional inputs | |
% that should be passed to the API to filter the | |
% result. | |
% | |
% OUTPUTS: | |
% R: [M x 1] Object, Array of MATLAnswer objects | |
% which can be used to gather current MATL usage | |
% statistics. | |
if exist('ignore', 'var') && ischar(ignore) | |
% Then no ignores were specified | |
varargin = cat(2, ignore, varargin); | |
clear ignore | |
end | |
if ~exist('ignore', 'var') | |
% Ignore all of the answers that were posted in | |
% cops/robbers questions due to stray # | |
ignore = 77419; | |
end | |
% Look in search/excerpts for MATL occurances | |
url = strcat(MATLAnswer.API_URL, '/search/excerpts'); | |
answers = MATLAnswer.getData(url, 'q', 'MATL', varargin{:}); | |
% Determine which ones are *actual* answers (start with MATL) | |
isAnswer = cellfun(@(x)strcmp(x.item_type, 'answer'), answers); | |
answers = answers(isAnswer); | |
% Convert the cell array of structs into an array of structs | |
% now that they are all answers | |
answers = cat(1, answers{:}); | |
isMATL = ~cellfun(@isempty, regexp({answers.body}, '^MATL')); | |
answers = answers(isMATL); | |
% Now we want the actual content from the answers | |
% The URL should have a semi-colon separated list of all | |
% answers IDs that we care about. We only want to pass 100 at a | |
% time though | |
allanswers = {}; | |
chunksize = 100; | |
for k = 1:ceil(numel(answers) / chunksize) | |
chunk = answers(((k - 1) * chunksize + 1) : ... | |
(min(k * chunksize - 1, numel(answers)))); | |
ids = sprintf('%d;', chunk.answer_id); | |
url = strcat(MATLAnswer.API_URL, '/answers/', ids(1:end-1)); | |
data = MATLAnswer.getData(url, 'filter', '!9YdnSM64y'); | |
allanswers = cat(1, allanswers, data); | |
end | |
% Remove entries with last_edit_date and community_owned_date | |
% since it's not present in all results | |
for k = 1:numel(allanswers) | |
if isfield(allanswers{k}, 'last_edit_date') | |
allanswers{k} = rmfield(allanswers{k}, 'last_edit_date'); | |
end | |
if isfield(allanswers{k}, 'community_owned_date') | |
allanswers{k} = rmfield(allanswers{k}, 'community_owned_date'); | |
end | |
end | |
answers = cat(1, allanswers{:}); | |
% Ignore answers that were in the ignored list | |
toremove = ismember([answers.question_id], ignore); | |
answers(toremove) = []; | |
% Now create an object instance for each answer | |
res = MATLAnswer(answers); | |
% Remove entries which were considered to be invalid | |
res = res([res.Valid]); | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment