4
$\begingroup$

I have a list that looks like this:

{{"a",},{2010,0},{2011,10},{2012,27},{"b",},{2011,11},{2012,66},{"c",},{2010,19},{2011,20},{2012,}}

I want to turn it into:

{{"a",2010,0},{"a",2011,10},{"a",2012,27},{"b",2011,11},{"b",2012,66},{"c",2010,19},
{"c",2011,20},{"c",2012,}}

You can see that the original list is sets of pairs, with the identifier always being a string, and the pairs following it are always a year followed by a number or a "".

So far the only way I can see to do this is via some loops that remember the identifier, say "a", and create triples using it until the next string comes along. I can't trust that each "group" will have the same number of pairs.

Can anyone think of a more Mathematica way to do this?

EDIT In "real life" there are about 50 groups, each headed by a pair beginning with a string. Also the years are 2009-2014, although I can't be guaranteed to have all years in every group.

$\endgroup$
2
$\begingroup$

slightly different approach

Block[{last, f},
 f[{s_String, ___}] := (last = s; ## &[]);
 f[y_] := Join[{last}, y];

 f /@ list
 ]
{{"a", 2010, 0}, {"a", 2011, 10}, {"a", 2012, 27}, {"b", 2011, 11}, 
   {"b", 2012, 66}, {"c", 2010, 19}, {"c", 2011, 20}, {"c", 2012, }}
$\endgroup$
  • $\begingroup$ +1 for a straightforward memoization approach. $\endgroup$ – Alexey Popkov Nov 6 '14 at 14:36
3
$\begingroup$

Here's one way:

L = {{"a",}, {2010, 0}, {2011, 10}, {2012, 27}, {"b",}, {2011, 
    11}, {2012, 66}, {"c",}, {2010, 19}, {2011, 20}, {2012, ""}};
Join @@ (Table[Prepend[#[[k]], #[[1, 1]]], {k, 2, Length[#]}] & /@ 
   Split[L, #2[[2]] =!= Null &])

which produces the desired result:

{{"a", 2010, 0}, {"a", 2011, 10}, {"a", 2012, 27}, {"b", 2011, 
  11}, {"b", 2012, 66}, {"c", 2010, 19}, {"c", 2011, 20}, {"c", 2012, 
  ""}}
$\endgroup$
  • $\begingroup$ You need an additional Flatten[..., 1] or Join@@... $\endgroup$ – Dr. belisarius Nov 5 '14 at 23:03
  • $\begingroup$ @belisarius: Oops, fixed. $\endgroup$ – DumpsterDoofus Nov 6 '14 at 0:08
  • $\begingroup$ Thanks - this works, but I have to make an adjustment to my original list. It wasn't clear from the way I had typed it, but in all cases, the second element of {xxxx,} is "". So I had to convert {x_Integer,""} to {x,0} and use "" instead of Null. Then it works great! Since I do have to transform my data to begin with, this isn't really a significant issue. I hadn't used Split[] before. I knew I couldn't use Partition[] though. $\endgroup$ – Mitchell Kaplan Nov 6 '14 at 14:57
3
$\begingroup$
l = {{"a"}, {2010, 0}, {2011, 10}, {2012, 27}, {"b"}, {2011, 11}, {2012, 66}, 
     {"c"}, {2010, 19}, {2011, 20}, {2012, 11}}; 

Join@@ReplaceList[ l, {___, {o_String}, u : Except[{_String}] .., 
                                           PatternSequence[{_String}, ___] | 
                                           PatternSequence[]} :> Thread[{o, {u}}]]
(*
{{"a", {2010, 0}}, {"a", {2011, 10}}, {"a", {2012, 27}}, 
 {"b", {2011, 11}}, {"b", {2012, 66}},
 {"c", {2010, 19}}, {"c", {2011, 20}}, {"c", {2012, 11}}}
*)

Edit: ReplaceList explained

It matches anything with the following chracteristics

 {___,                              (* Starts With anything *)
 {o_String},                        (* has a list containing only one String ... *)
                                    (*  and the name of that string is now "o"*)
 u : Except[{_String}] ..,          (* followed by a variable number of non-string lists ....*)
                                    (*   that lists are named  "u" *)
 PatternSequence[{_String}, ___] |   (* Followed by a string and any tail *)
 PatternSequence[]} :>               (*   OR just by the end of the list  *)
 Thread[{o, {u}}                     (* and transform it into {{o, u1},{o,u2}...}*)
$\endgroup$
  • $\begingroup$ This works with a minor adjustment, but I am having a lot of trouble understanding it. The minor adjustment is {o_String,""} instead of {o_String}. It's hard to see from the way it's printed but the leading strings are pairs, with the second element being equal to "". It looks like in your code that the LHS of the rule has choices in it, but I really don't understand how that works. I looked up ReplaceList[] but the examples are much simpler. Can you give me any hints as to how this works? $\endgroup$ – Mitchell Kaplan Nov 6 '14 at 15:21
  • $\begingroup$ @MitchellKaplan Done $\endgroup$ – Dr. belisarius Nov 6 '14 at 15:40
  • $\begingroup$ @MitchellKaplan It's a bad idea to keep null strings around if you can avoid it. $\endgroup$ – Dr. belisarius Nov 6 '14 at 15:42
  • 1
    $\begingroup$ @AlexeyPopkov It took me some time to figure out the use of PatternSequence[] to signal the end of the list. So I thought it's good to have it documented somewhere in the site in case it already isn't. $\endgroup$ – Dr. belisarius Nov 6 '14 at 16:12
  • $\begingroup$ Very good point! Didn't know this too. $\endgroup$ – Alexey Popkov Nov 6 '14 at 16:15
2
$\begingroup$
L2 = L /. Null -> Sequence[] /. {s_String} :>  s;

Join @@ (Flatten /@ Thread[{#, {##2}}] & @@@ Split[L2, !StringQ@#2 &])
(* {{"a", 2010, 0}, {"a", 2011, 10}, {"a", 2012, 27},
    {"b", 2011, 11},{"b", 2012, 66},
    {"c", 2010, 19}, {"c", 2011, 20}, {"c", 2012,  ""}} *)

or

Join @@ (With[{x = #, y = {##2}}, {x, ## & @@ #} & /@ y] & @@@ Split[L2, ! StringQ@#2 &])
(* {{"a", 2010, 0}, {"a", 2011, 10}, {"a", 2012, 27},
    {"b", 2011, 11},{"b", 2012, 66},
    {"c", 2010, 19}, {"c", 2011, 20}, {"c", 2012,  ""}} *)
$\endgroup$
  • 1
    $\begingroup$ +1 for clever use of SlotSequence and Slot. BTW replacing your first line with L2 = L /. {s_String,} :> s; will produce the output the OP explicitly requests. $\endgroup$ – Alexey Popkov Nov 6 '14 at 15:06
2
$\begingroup$

Straightforward Splitting approach:

l = {{"a",}, {2010, 0}, {2011, 10}, {2012, 27}, {"b",}, {2011, 
    11}, {2012, 66}, {"c",}, {2010, 19}, {2011, 20}, {2012,}};

Flatten /@ 
 Flatten[Thread[{#[[1, 1]], Rest@#}] & /@ 
   Split[l, MatchQ[{##}, {{__}, {_Integer, _}}] &], 1]
{{"a", 2010, 0}, {"a", 2011, 10}, {"a", 2012, 27}, {"b", 2011, 
  11}, {"b", 2012, 66}, {"c", 2010, 19}, {"c", 2011, 20}, {"c", 2012, Null}}

Or a little shorter:

Flatten /@ 
 Flatten[Thread[{#[[1, 1]], Rest@#}] & /@ 
   Split[l, IntegerQ[First@#2] &], 1]
{{"a", 2010, 0}, {"a", 2011, 10}, {"a", 2012, 27}, {"b", 2011, 
  11}, {"b", 2012, 66}, {"c", 2010, 19}, {"c", 2011, 20}, {"c", 2012, Null}}

The same even shorter and easier:

Flatten /@ 
 Flatten[Thread[{First@#, {##2}}] & @@@ 
   Split[l, IntegerQ[First@#2] &], 1]
{{"a", 2010, 0}, {"a", 2011, 10}, {"a", 2012, 27}, {"b", 2011, 
  11}, {"b", 2012, 66}, {"c", 2010, 19}, {"c", 2011, 20}, {"c", 2012, Null}}

Replace-based memoization approach:

Module[{t}, 
 Replace[l, {{s_String,} :> (t = s; ## &[]), {i_Integer, i2_} :> {t, i, i2}}, {1}]]
{{"a", 2010, 0}, {"a", 2011, 10}, {"a", 2012, 27}, {"b", 2011, 
  11}, {"b", 2012, 66}, {"c", 2010, 19}, {"c", 2011, 20}, {"c", 2012, Null}}
$\endgroup$
1
$\begingroup$

Using:

lst = {{"a",}, {2010, 0}, {2011, 10}, {2012, 27}, {"b",}, {2011, 
   11}, {2012, 66}, {"c",}, {2010, 19}, {2011, 20}, {2012,}}

then:

pos = {##, Length@lst + 1} & @@Flatten[p = Position[lst, {_?StringQ, ___}]];
ind = {#1 + 1, #2 - 1} & @@@ Partition[pos, 2, 1];
Flatten /@ (Join @@MapThread[Thread[{First@#1, #2}] &, {Extract[lst, p], 
     Take[lst, #] & /@ ind}])

yields:

(*{{"a", 2010, 0}, {"a", 2011, 10}, {"a", 2012, 27}, {"b", 2011, 
  11}, {"b", 2012, 66}, {"c", 2010, 19}, {"c", 2011, 20}, {"c", 2012, 
  Null}}*)
$\endgroup$
1
$\begingroup$

Here is yet another pattern matching approach.

{{"a",}, {2010, 0}, {2011, 10}, {2012, 27}, {"b",}, {2011, 11}, {2012,
    66}, {"c",}, {2010, 19}, {2011, 
   20}, {2012,}} //. {Shortest[x___], {a_String, Null}, 
   Longest[b__ /; FreeQ[{b}, _String]], Shortest[y___]} :> 
  Join[{x}, Prepend[#, a] & /@ {b}, {y}]

{{"a", 2010, 0}, {"a", 2011, 10}, {"a", 2012, 27}, {"b", 2011, 11}, {"b", 2012, 66}, {"c", 2010, 19}, {"c", 2011, 20}, {"c", 2012, Null}}

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.