1

g'morning!

i fill a dictionary TDictionary<String, TStringlist> (delphi-collections-unit) with strings as values and several strings as values. something like:

  • names = john, lisa, stan
  • skills = read, write, speak
  • ages = 12, 14, 16

(without "," of course). what i need is to iterate this dictionary and to multiply out the values with the keys. output should be like

  • names = john skills = read ages = 12
  • names = john skills = read ages = 14
  • names = john skills = read ages = 16
  • names = john skills = write ages = 12
  • names = john skills = write ages = 14
  • names = john skills = write ages = 16
  • ...
  • names = lisa skills = read ages = 12
  • ...
  • names = stan skills = speak ages = 16

so every combination. how can i do so? the number of keys is dynamic and so is the size of the tstringlist. thanks! SOLVED by now...

now the problem with the scope. following is the procedure that fills the dict. the subsplits and the splitstring are stringlists, that get freed at the end of the procedure. the dict is created after the procedures-block (in main? how is it called?), the fill-method is called and then i want to do recursion like in the code-example but there are no values in the dict....

while not Eof(testfile) do
  begin
    ReadLn(testfile, text);
    if AnsiContainsStr(text, '=') then
    begin
      Split('=', text, splitarray);
      splitarray[0] := trim(splitarray[0]);
      splitarray[1] := DeleteSpaces(splitarray[1]);
      if AnsiStartsStr('data', splitarray[0]) then
      begin
        split(' ', splitarray[0], subsplit1);
        splitarray[0]:=subsplit1[1];
        split(',', splitarray[1], subsplit2);
        dict.Add(splitarray[0], subsplit2);
        for ValueName in dict.Values do
        begin
          for i := 0 to Valuename.Count - 1 do
          write('Values are : '+ Valuename[i]);
        writeln;
        end;//
      end;//
    end;//
  end;//
soulbrother
  • 57
  • 2
  • 2
  • 7
  • 4
    @stanislav, I edited your question to make it readable. Next time, please do it yourself. When you're writing your question you've got a help screen to your right that explains the markdown AND a live preview at the bottom so you know how your question is going to look like once posted. – Cosmin Prund Apr 07 '11 at 07:47
  • you are right. i also wanted to edit it, but you were faster. thanks! – soulbrother Apr 07 '11 at 07:49
  • @stanislav, your example doesn't match your question. You're not doing "every combination", your last example line is with "lisa", "read" and age "12". If that example was `names=stan, skills=speak, ages=16` the solution would be simple; As it stands I'm not sure what you want! – Cosmin Prund Apr 07 '11 at 07:50
  • ah, ok! i wrote "and so on" to make it clear, sorry! yes, the last line should be names=stan, skills=speak, ages=16, yes. – soulbrother Apr 07 '11 at 07:52
  • Is this homework? If so, please use the `homework` tag. – Cosmin Prund Apr 07 '11 at 07:54
  • this is not really homework but academic, yes. does this change anything? i am not really used to delphi... – soulbrother Apr 07 '11 at 07:55
  • It's customary here at SO to NOT give out complete solutions if it's homework. If by academic you mean you're learning by yourself, I can safely give you a full solution because I know you're not going to use it to cheat on your professor. – Cosmin Prund Apr 07 '11 at 07:59
  • that is what i defintely don't. i have to do this in delphi because there is a former project written in delphi (historically grown...) and nobody wants to migrate the existing code (totally uncommented...). – soulbrother Apr 07 '11 at 08:02
  • I assume this code: `split(',', splitarray[1], subsplit2);` clears `subsplit2` and fills it with the result of splitting `splitarray[1]`; When you do `dict.Add(splitarray[0], subsplit2);` you're adding `subsplit2` as a value to the dictionary. The problem is, it's *always* the same `subsplit2`, because TStringList is a class, so `subsplit2` is a pointer. At the end of the procedure you'll have a dictionary with many keys all pointing to the same TStringList. To top it off you say you're then freeing the TStringList, so you end up with a dict full of invalid pointers. [...] – Cosmin Prund Apr 07 '11 at 11:54
  • [...] You should create a new `TStringList`, one that's not shared between keys, and lives as long as the dictionary itself lives. But there might be other errors hidden beneath the surface. For one thing I have no idea how your `Split()` works. If this is not enough to get you ever the bump, you should ask a new question and this time post a *full working example* (console application), and please mention the programming languages you're comfortable with, to help us explain things. – Cosmin Prund Apr 07 '11 at 12:00
  • And just so you know, you committed a "SO" sin: Your question is now two questions. I thought you're going to ask something related to the current question, something related to iterators, but what you've asked is entirely different. You're acknowledging that yourself when you say the initial question is "solved". Those rules aren't made to annoy people, but on SO answers are supposed to be rated (up/down votes) by how good they address the question. When the question is actually two questions, this doesn't work any more. – Cosmin Prund Apr 07 '11 at 12:04
  • i think i ask another question. this is not readable anymore – soulbrother Apr 07 '11 at 12:04

1 Answers1

6

What you want is made a bit complicated by the use of the TDictionary<string, TStringList>, because that implies variable number of keys. If it weren't for the variable number of keys, you wouldn't need a dictionary and you'd simply iterate over 3 TStringLists.

That said, you've got the classic "generate all permutations" problem. It can be solved using recursion or backtracking. Recursion is simpler to implement, backtracking uses less stack space. The choice is yours. Here's a complete console application that does the whole deal, from initializing the dictionary, populating the dictionary, generating all permutations using a recursive function.

program Project23;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes, Generics.Collections;

var
  Dict:TDictionary<string, TStringList>;
  L: TStringList;
  KeyName: string;
  KeysList: TStringList;

// Help procedure, adds a bunch of values to a "Key" in the dictionary
procedure QuickAddToDict(KeyName:string; values: array of string);
var L: TStringList;
    s: string;
begin
  // Try to get the TStringList from the dictionary. If we can't get it
  // we'll create a new one and add it to the dictionary
  if not Dict.TryGetValue(KeyName, L) then
  begin
    L := TStringList.Create;
    Dict.Add(KeyName, L);
  end;
  // Iterate over the values array and add stuff to the TStringList
  for s in values do
    L.Add(s);
end;

// Recursive routine to handle one KEY in the dictionary
procedure HandleOneKey(KeyIndex:Integer; PrevKeys:string);
var L:TStringList;
    i:Integer;
    Part: string;
    KeyName: string;
begin
  KeyName := KeysList[KeyIndex];
  L := Dict[KeyName];
  for i:=0 to L.Count-1 do
  begin
    Part := KeyName + '=' + L[i];
    if KeyIndex = (KeysList.Count-1) then
      WriteLn(PrevKeys + ' ' + Part) // This is a solution, we're at the last key
    else
      HandleOneKey(KeyIndex+1, PrevKeys + ' ' + Part); // Not at the last key, recursive call for the next key
  end;
end;

begin
  try
    Dict := TDictionary<string, TStringList>.Create;
    try

      // Add whatever you want to the Dict.
      // Using the helper routine to set up the dictionary faster.
      QuickAddToDict('names', ['john', 'lisa', 'stan']);
      QuickAddToDict('skills', ['read', 'write', 'speak']);
      QuickAddToDict('ages', ['12', '14', '16']);

      // Extract all the keys to a string list. Unfortunately the dictionary
      // doesn't offer a way to get a key name by index, so we have to use the
      // keys iterator to extract all keys first.
      KeysList := TStringList.Create;
      try
        for KeyName in Dict.Keys do
          KeysList.Add(KeyName);
        if KeysList.Count > 0 then
        begin
          // We got at least one key, we can start the recursive process.
          HandleOneKey(0, '');
        end;
      finally KeysList.Free;
      end;

      WriteLn;
      WriteLn('Press ENTER to make the window go away');
      ReadLn;

    finally
      // TDictionary doesn't own the keys or the values. Strings are managed types in
      // delphi, we don't need to worry about them, but we do need to free the TStringList's
      // We use the Values iterator for that!
      for L in Dict.Values do
        L.Free;
      Dict.Free;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
Cosmin Prund
  • 25,498
  • 2
  • 60
  • 104
  • works perfectly, thanks! the 3 for-loops was quite clear, yes. – soulbrother Apr 07 '11 at 08:48
  • As this is you first question, do not forget to accept the answer if you think it solved your problem and is the best answer given. – Runner Apr 07 '11 at 08:49
  • damn, yes. your code works. but mine doesn't... i fill the dictionary within a procedure. i tried your code within mine. if i iterate the values in the proc, they are right. if i do this after begin, the values are wrong. the code is: parsefile is a procedure, fills the dict (global variable). but the values seem to get lost when leaving the procedure... why? – soulbrother Apr 07 '11 at 10:04
  • The most likely causes for your problems are variable scoping (maybe you're referring to a different `dict`) and life cycle problems (maybe you're referring to `dict` after you free it). If you can't spot the error, try adding the code to the question so we can have a look. – Cosmin Prund Apr 07 '11 at 11:00