Notifications
Clear all

[Solved] 2-D ragged array to 1-D array with null separators?


Posts: 5
Customer
Topic starter
Eminent Member
Joined: 5 months ago

I have a 2-D array indexed by Group and Event. Each Group has a different number of Events, so each Group-Slice is null-padded. From this I want to compute a 1-D array, where the values for all events are listed but with a single Null cell between each Group's values. In other words, the values for all of Group 1's events appear, followed by Null, followed by the value for all of group 2's events, followed by a Null, followed by the value for all of group 3's events, etc.

I actually came up with some code to compute this, but it is pretty ugly.  The starting array is named Attendees. My solution looks like this:

Local temp := ConcatRows(Attendees, Group, Event);
temp[.ConcatIndex=subset(temp<>null or temp[.ConcatIndex=temp.ConcatIndex-1]<>null,ResultIndex:Event_num)]

The first line changes it from a 2-D to 1-D, but with all the original nulls present. The second line uses subset to decide which of those cells to keep.

Is there a more elegant way of doing this transformation?

I've attached a simple model to show the starting array and my solution.  I'm looking for a replacement for my expression in Flattened_attendees.

7 Replies
Posts: 5
Customer
Topic starter
Eminent Member
Joined: 5 months ago

Sorry, I forgot to attach my example model to my previous post. Here it is.

Reply
Posts: 9
Customer
Eminent Member
Joined: 5 months ago

Jessie - this is a timely example for me, since I actually just starting to learn about array functions!  I studied your solution and get it. Seems pretty good to me, although I am still going to see if I can come up with my own solution. However, I think I found a problem with your solution.  See what you think...

In your Attendees table, I added data to every cell in Group 2 (so there are 20 events with data, and no null-padding at the end). When you do that, there is no Null value between your Group 2 and Group 3 in the Flattened attendees result.  I guess if you never fill every cell in a group you'll be okay.

Reply
1 Reply
Customer
Joined: 5 months ago

Eminent Member
Posts: 5

Thanks Pat. You are right, I do have a bug where a null is missing when a group uses every event slot.

My original definition just got even a bit more archane:

LocalIndex E2 := Concat(Event,['extra']);
Local temp := ConcatRows(Attendees[Event=E2], Group, E2);
temp[.ConcatIndex=subset(temp<>null or temp[.ConcatIndex=temp.ConcatIndex-1]<>null,ResultIndex:Event_num)]
Reply
Posts: 9
Customer
Eminent Member
Joined: 5 months ago

I came up with a different solution. I think it isn't as elegant as yours, but it does fix the bug I mentioned in my previous post with a Null after "full" group.  Here is what I came up with:

Local result[Event_num];
(
   Local firstForGroup[] := 
       Cumulate(Num_events_with_atte+1,Group) [Group=Group-1,defVal:0]+1;
   Local numEvents[] := Num_events_with_atte;
   Local valsForGroup[Event] := Attendees;

   For i := 1..numEvents Do
         result[Event_num=firstForGroup+i-1] := valsForGroup[Event=i];
);
result

firstForGroup is the position where each group starts in the result array.

I know that more elegant solutions would do this with array operations and without resorting to using FOR and Assignment, like your original expression. I'm going to keep trying, but hope that someone shows their solution.

 

Reply
2 Replies
Customer
Joined: 5 months ago

Eminent Member
Posts: 5

@patstevens That's a very different approach. Interesting, and thanks for sharing it. As you said, I'd still like to see another solution that uses a purely functional style with array operations.

Reply
Admin
Joined: 11 years ago

Member
Posts: 12

@patstevens - This looks efficient, but yes, it takes more effort to decode than a purely functional expression. One thing I would say is to not be too worried about "algorithmic" solutions like this when they are efficient, but when you have a bit of code that is predominantly algorithmic, put it into a User-Defined function, so that your variable definitions focus on domain logic and not algorithmic logic.  I.e., good style practice: variable definitions short & make domain logic clear, algorithmic logic belongs in UDFs.  

Your firstForGroup is pretty clever. With that, you could easily create a map from the result index, Event_num, to the Group of the source cell, for example using a lookup:

StepInterp(firstForGroup,Group,Event_num,Group,'Left')

Similarly, you could use it to create a map from Event_num to the Event of your source cell, e.g.,

Event_num - StepInterp(firstForGroup,firstForGroup,Event_num,Group,'Left')+1;

These maps could be placed in global variables, since you might want to use them multiple places to relate event_num to group and event. Call these mappings en2g and en2e.  Once you have those, this leads to an especially elegant solution:

Attendees[Group=en2g, Event=en2e, defVal:null]

 

 

Reply
Posts: 12
Admin
Member
Joined: 11 years ago

@jessiesandoval - Your original definition (with the bug Pat found) is equivalent to:

Flatten(Attendees, Group, Event, ResultIndex:Event_num, condition:@Event<=Num_events_with_atte+1)

To address the case where there is no trailing null event, I think I would reindex the same way you did in your update, i.e.,

LocalIndex E2 := Concat(Event,'extra');
Flatten(Attendees[@Event=@E2], Group, E2, ResultIndex:Event_num, condition:@E2<=Num_events_with_atte+1);

If you encapsulate this as a User-Defined function, where Event is an index parameter, then use a local index like this so it adjusts to any index length. (You would also have to compute Num_events_with_atte in a local inside the UDF). If you are only doing this for the global event index, then using a global index, Event_with_extra, would make your expression simpler, and avoid the (fairly small) overhead of creating a local index.

 

Reply
Share:
Scroll to Top