使用MATLAB进行DJ(低级I / O)

时间:2014-10-10 00:36:37

标签: matlab loops file-io text-files cell-array

好的,每个人,这是一周的时间,我学习如何用MATLAB做奇怪的事情。本周它的DJ。我需要做的是弄清楚如何使我的函数输出长度最接近剩余时间的歌曲的名称。例如,如果我正在炫耀我的DJ技巧并且还剩下3:22,我必须选择一首长度最接近剩余时间的歌曲(可以更短或更长)。我给出了一个.txt文件供您选择。

Test Case
song1 = pickSong('Funeral.txt', '3:13')
song1 => 'Neighborhood #2 (Laika)'


The file for this looks like:
1. Neighborhood #1 (Tunnels) - 4:48
2. Neighborhood #2 (Laika) - 3:33
3. Une annee sans lumiere - 3:40
4. Neighborhood #3 (Power Out) - 5:12
5. Neighborhood #4 (7 Kettles) - 4:49
6. Crown of Love - 4:42
7. Wake Up - 5:39
8. Haiti - 4:07
9. Rebellion (Lies) - 5:10
10. In the Backseat - 6:21

我已经计划了大部分,我遇到的问题是填充我的单元格数组。它只放入最后一首歌,然后在我的循环运行后将其改为-1。我尝试过三种不同的方式,最后一种是最复杂的(而且看起来很遗憾)。一旦我将单元格数组放入其正确的形式(作为完整的歌曲列表而不仅仅是-1),我应该是清楚的。

function[song] = pickSong(file_name,time_remain)

Song_list = fopen(file_name, 'r'); %// Opens the file
Song_names = fgetl(Song_list); %// Retrieves the lines, or song names here
Songs_in = ''; %// I had this as a cell array first, but tried to populate a string this time
while ischar(Songs) %// My while loop to pull out the song names
Songs_in = {Songs_in, Songs}; 
Songs = fgetl(Song_list);
if ischar(Songs_in) %//How I was trying to populate my string
    song_info = [];
    while ~isempty(Songs_in)
    [name, time] = strtok(Songs_in);
    song_info = [song_info {name}]; 
        end
    end
end

[songs, rest] = strtok(Songs, '-');
[minutes, seconds] = strtok(songs, ':');
[minutes2, seconds2] = strtok(time_remain, ':')

all_seconds = (minutes*60) + seconds; %// Converting the total time into seconds
all_seconds2 = (minutes2*60) + seconds2;
song_times = all_seconds;
time_remain = all_seconds2

time_remain = min(time_remain - song_times);



fclose(file_name);

end

请感谢您的帮助:)

一个棘手的案例:

song3 = pickSong('Resistance.txt', '3:57') 
song3 => 'Exogenesis: Symphony Part 2 (Cross-Pollination)'

1. Uprising - 5:02
2. Resistance - 5:46
3. Undisclosed Desires - 3:56
4. United States of Eurasia (+Collateral Damage) - 5:47
5. Guiding Light - 4:13
6. Unnatural Selection - 6:54
7. MK ULTRA - 4:06
8. I Belong to You (+Mon Coeur S'ouvre a Ta Voix) - 5:38
9. Exogenesis: Symphony Part 1 (Overture) - 4:18
10. Exogenesis: Symphony Part 2 (Cross-Pollination) - 3:57
11. Exogenesis: Symphony Part 3 (Redemption) - 4:37

3 个答案:

答案 0 :(得分:2)

这是我的实施:

function song = pickSong(filename, time_remain)
    % read songs file into a table
    t = readSongsFile(filename);

    % query song length (in seconds)
    len = str2double(regexp(time_remain, '(\d+):(\d+)', ...
        'tokens', 'once')) * [60;1];

    % find closest match
    [~,idx] = min(abs(t.Duration - len));

    % return song name
    song = t.Title(idx);
end

function t = readSongsFile(filename)
    % read the whole file (as a cell array of lines)
    fid = fopen(filename,'rt');
    C = textscan(fid, '%s', 'Delimiter',''); C = C{1};
    fclose(fid);

    % parse lines of the form: "0. some name - 00:00"
    C = regexp(C, '^(\d+)\.\s+(.*)\s+-\s+(\d+):(\d+)$', 'tokens', 'once');
    C = cat(1, C{:});

    % extract columns and create a table
    t = table(str2double(C(:,1)), ...
        strtrim(C(:,2)), ...
        str2double(C(:,3:4)) * [60;1], ...
        'VariableNames',{'ID','Title','Duration'});
    t.Properties.VariableUnits = {'', '', 'sec'};
end

我们应该在测试文件上得到预期的结果:

>> pickSong('Funeral.txt', '3:13')
ans = 
    'Neighborhood #2 (Laika)'

>> pickSong('Resistance.txt', '3:57')
ans = 
    'Exogenesis: Symphony Part 2 (Cross-Pollination)'

注意:上面的代码使用MATLAB表来存储数据,这样可以轻松操作。例如:

>> t = readSongsFile('Funeral.txt');
>> t.Minutes = fix(t.Duration/60);       % add minutes column
>> t.Seconds = rem(t.Duration,60);       % add seconds column

>> sortrows(t, 'Duration', 'descend')    % show table sorted by duration
ans = 
    ID                Title                Duration    Minutes    Seconds
    __    _____________________________    ________    _______    _______
    10    'In the Backseat'                381         6          21     
     7    'Wake Up'                        339         5          39     
     4    'Neighborhood #3 (Power Out)'    312         5          12     
     9    'Rebellion (Lies)'               310         5          10     
     5    'Neighborhood #4 (7 Kettles)'    289         4          49     
     1    'Neighborhood #1 (Tunnels)'      288         4          48     
     6    'Crown of Love'                  282         4          42     
     8    'Haiti'                          247         4           7     
     3    'Une annee sans lumiere'         220         3          40     
     2    'Neighborhood #2 (Laika)'        213         3          33    

% find songs that are at least 5 minutes long
>> t(t.Minutes >= 5,:)

% songs with the word "Neighborhood" in the title
>> t(~cellfun(@isempty, strfind(t.Title, 'Neighborhood')),:)

答案 1 :(得分:1)

您可以使用textscan进行管理,如下所示:

function[song,len] = pickSong(file_name,time_remain)

fid = fopen(filename);

toks = textscan(fid,'%[^-] - %d:%d');
songs = toks{1};
song_len = double(toks{2}*60 + toks{3});

[min_rem, sec_rem] = strtok(time_remain, ':');
time_rem = str2double(min_rem)*60 + str2double(sec_rem(2:end));

[len,i] = min(abs(time_rem - song_len));
song = songs{i};

请注意,只有当您的歌曲名称都没有“' - '他们中的人物。

编辑:这是一个(应该)适用于任何歌曲标题的解决方案:

function[song,len] = pickSong(file_name,time_remain)

file = fileread(file_name);
toks = regexp(file,'\d+. (.*?) - (\d+):(\d+)\n','tokens');
songs = cell(1,length(toks));
song_lens = zeros(1,length(toks));
for i=1:length(toks)
    songs{i} = toks{i}{1};
    song_lens(i) = str2double(toks{i}{2})*60 + str2double(toks{i}{3});
end

[min_rem, sec_rem] = strtok(time_remain, ':');
time_rem = str2double(min_rem)*60 + str2double(sec_rem(2:end));

[len,i] = min(abs(time_rem - song_lens));
song = songs{i};

regexp是一个MATLAB函数,它在字符串上运行正则表达式(在本例中是您的歌曲名称文件)。字符串'\d+. (.*?) - (\d+):(\d+)\n'扫描每一行,提取每首歌曲的名称和长度。 \d+匹配一个或多个数字,而.*?匹配任何数字。括号用于分组输出。所以,我们有:

match n digits, followed by a (string), followed by (n-digits):(n-digits)

括号中的每个内容都作为单元格数组返回到toks变量。 for循环只是从结果单元格数组中提取歌曲名称和长度。

答案 2 :(得分:1)

我将使用您已编写的大部分内容撰写答案,而不是建议完全不同的内容。虽然regexp也是一个强大的(我喜欢正则表达式),但我发现它对于你到目前为止学到的东西来说太先进了,所以现在让我们废弃它。

这样,你就可以了解你的代码出了什么问题,以及我是多么出色的调试器(只是开玩笑)。阅读文本文件时的内容几乎可以正常工作。您在创建单元格数组时可以选择存储所有字符串。

我还要借用strtok(真棒工作顺便说一句)来计算时间(以秒为单位),借用MrAzzaman的逻辑。


另外,我会稍微改变你的逻辑,这样我就会怎么做才有意义。这是基本算法:

  1. 打开文件并像在代码中一样阅读第一行(歌曲)
  2. 初始化包含文本文件
  3. 中第一首歌曲的单元格数组
  4. 在我们到达文本文件的末尾之前,请读入整行并将其添加到单元格数组中。您还注意到,只要您点击-1,我们就没有更多的歌曲可供阅读,因此请远离循环。
  5. 现在我们将歌曲放在一个单元格数组中,其中包括每首歌曲的曲目编号,歌曲和时间,我们将创建另外两个单元格数组。第一个将仅将歌曲的时间存储为字符串,其中分钟和秒由:分隔。下一个将只包含歌曲本身的名称。现在,我们遍历从步骤#3创建的单元格数组中的每个元素。

    (a)要填充第一个单元格数组,我使用strfind查找-字符出现位置的所有次出现。找到这些问题后,我会选择-出现位置的最后位置。我用它来索引我们的歌曲字符串,跳过2个字符来跳过-字符和空格字符。我们从这一点提取所有字符,直到行结束,以提取我们的时间。

    (b)为了填充第二个单元格数组,我再次使用strfind,然后我找出空格出现的位置,并选择第一个空格发生位置的索引。这对应于歌曲编号和歌曲曲目之间的间隙。使用我从(a)索引的结果,我通过跳过第一个空格的索引中的一个字符到索引最后-个字符之前的两个字符来提取歌曲标题得到这首歌。这是因为在-字符之前的歌曲标题的最后一个单词之间可能存在空格,因此我们要删除该空格。

  6. 接下来,对于步骤#4中计算的第一个单元格数组中的每个歌曲时间,我使用strtok,就像您使用的一样,并按:分割字符串。 MrAzzaman也使用了这个,我将借用他的逻辑来计算每次所用的总秒数。

  7. 最后,我们确定哪个时间最接近剩余时间。请注意,我们还需要像在步骤#5中那样将剩余时间转换为秒。正如MrAzzaman所说,您可以在MATLAB中使用min函数,并使用函数的第二个输出。这会告诉您数组中 where 的最小值。因此,我们只搜索每首歌曲的剩余时间和经过的时间之间的最小差异。请注意,您说您不关心是否过了或超过时间。您只需要最近时间。在这种情况下,您需要采用时差的绝对值。假设你有一首歌曲花了3点59分,另一首歌曲是6点,剩下的时间是4点。假设您的曲目中没有4:00长的歌曲,您可能希望选择3:59的歌曲。但是,如果您只是从较长的音轨(6:00)中减去剩余时间,您将获得差异,并且min将返回此音轨...而不是3:59。这就是为什么你需要取绝对值,所以这将忽略你是否超过剩余时间。

  8. 一旦我们确定要选择哪首歌曲,请返回给我们最少的歌曲名称。确保你也关闭了文件!


  9. 不用多说,这是代码:

    function [song] = pickSong(file_name, time_remain)
    
    % // Open up the file
    fid = fopen(file_name, 'r');
    
    %// Read the first line
    song_name = fgetl(fid);
    
    %// Initialize cell array
    song_list = {song_name};
    
    %// Read in the song list and place
    %// each entry into a cell array
    while ischar(song_name)
        song_name = fgetl(fid);
        if song_name == -1
            break;
        end
        song_list = [song_list {song_name}];
    end
    
    %// Now, for each entry in our song list, find all occurrences of the '-'
    %// with strfind, and choose the last index that '-' occurs at
    %// Make sure you skip over by 2 spaces to remove the '-' and the space
    song_times = cell(1,length(song_list));
    song_names = cell(1,length(song_list));
    for idx = 1 : length(song_list)
        idxs = strfind(song_list{idx}, '-');
        song_times{idx} = song_list{idx}(idxs(end)+2:end);
    
        idxs2 = strfind(song_list{idx}, ' ');
        %// Figure out the index of where the first space is, then extract
        %// the string that starts from 1 over, to two places before the 
        %// last '-' character
        song_names{idx} = song_list{idx}(idxs2(1)+1 : idxs(end)-2);
    end
    
    %// Now we have a list of times for each song.  Tokenize by the ':' to 
    %// separate the minutes and times, then calculate the number of seconds
    %// Logic borrowed by MrAzzaman
    song_seconds = zeros(1,length(song_list));
    for idx = 1 : length(song_list)
        [minute_str, second_str] = strtok(song_times{idx}, ':');
        song_seconds(idx) = str2double(minute_str)*60 + str2double(second_str(2:end));
    end
    
    %// Now, calculate how much time is remaining from the input
    [minute_str, second_str] = strtok(time_remain, ':');
    seconds_remain = str2double(minute_str)*60 + str2double(second_str(2:end));
    
    %// Now, choose the song that is closest to the amount of time
    %// elapsed
    [~,song_to_choose] = min(abs(seconds_remain - song_seconds));
    
    %// Return the song you want
    song = song_names{song_to_choose};
    
    %// Close the file
    fclose(fid);
    
    end
    

    通过上面显示的两个示例案例,这是我得到的输出。我冒昧地用你的(很棒的品味)音乐创建我自己的文本文件:

    >> song1 = pickSong('Funeral.txt', '3:13')
    
    song1 =
    
    Neighborhood #2 (Laika)
    
    >> song2 = pickSong('Resistance.txt', '3:57')
    
    song2 =
    
    Exogenesis: Symphony Part 2 (Cross-Pollination)