6
$\begingroup$

I have a list of daily measurements taken on given dates, where the measurements follow the dates:

lis = {"a", "b", "October", "01", "2021", "c", "d", "October", "1", "2021","e", "October", "2", "2021", "f"}

I would like to organize these measurements as discrete list members as follows:

res = {{DateObject[{2021,10,1}],"c","d"},{DateObject[{2021,10,1}],"e"},{DateObject[{2021,10,2}],"f"}}

Thanks for any ideas!

$\endgroup$
3
  • $\begingroup$ Sounds like an awful format. Do you know that there will always be two entries at the front to discard? Can you get the data in a better format to start with? $\endgroup$
    – MarcoB
    Oct 5 at 17:29
  • $\begingroup$ It is a horrible format, I totally agree (that's why I'm here :) There can be any number of entries to discard at the front. $\endgroup$
    – Suite401
    Oct 5 at 17:42
  • 3
    $\begingroup$ Based on the subject, maybe take one to a movie, another bowling,...? $\endgroup$ Oct 5 at 19:55
3
$\begingroup$

Mathematica solutions fit into many categories and take their readers through different abstract states (pause, admiration, disbelief, bewilderment etc). I present a step by step solution mostly for new learners. Given:

lis = {"a", "b", "October", "01", "2021", "c", "d", "October", "1", 
  "2021", "e", "October", "2", "2021", "f"}

Define months; Remember that month strings could be shortened as "Jan", "Feb" etc or these could be lower case or in another language, but let's work with standard English strings for now:

months = {"January", "February", "March", "April", "May", "June", 
  "July", "August", "September", "October", "November", "December"}

morules = MapThread[Rule, {months, Range[12]}]
{"January" -> 1, "February" -> 2, "March" -> 3, "April" -> 4, 
 "May" -> 5, "June" -> 6, "July" -> 7, "August" -> 8, 
 "September" -> 9, "October" -> 10, "November" -> 11, 
 "December" -> 12}

Define a monthQ function to see if an input string is a month. Test the function:

monthQ[s_String] := MemberQ[months, s]
monthQ[#] & /@ {"Banana", "October", "April", "Carrot"}
{False, True, True, False}

Main solution starts here-----------

Since "October" is the marker where things need to be split, find positions where it (or another month name string) occurs.

pos = Position[MemberQ[months, #] & /@ lis, True]

{{3}, {8}, {12}}

Now insert a marker at these positions where split will happen, (* U2702 = ✂ *)

lis2 = Insert[lis, "✂", pos]
{"a", "b", "✂", "October", "01", "2021", "c", "d", "✂", "October", \
"1", "2021", "e", "✂", "October", "2", "2021", "f"}
lis2a = SequenceSplit[lis2, {"✂"}]

{{"a", "b"}, {"October", "01", "2021", "c", "d"}, {"October", "1", 
  "2021", "e"}, {"October", "2", "2021", "f"}}

The first list could be without a month marker. I will pretend that there could be others as a result of some other processing. Select only those lists that have a month-string as their first element.

lis3 = Select[lis2a, monthQ[First[#]] &]

{{"October", "01", "2021", "c", "d"}, {"October", "1", "2021", 
  "e"}, {"October", "2", "2021", "f"}}

Now write a utility function to parse the first three members of each list as a date object. Be as simple and modular as possible, as such juxtapositions can change.

dobjparse[k_List] := Module[{t1, t2, t3},
  t1 = ToExpression[(k[[1]] /. morules)];
  (*Echo[t1];*)
  t2 = ToExpression[k[[2]]];
  (*Echo[t2];*)
  t3 = ToExpression[k[[3]]];
  (*Echo[t3];*)
  DateObject[{t3, t1, t2}]
  ]

Now apply this utility function to appropriate sections of lis3 and keep the rest of the sublist elements:

{dobjparse[#[[1 ;; 3]]], Sequence @@ #[[4 ;; -1]]} & /@ lis3

This solution has added nothing new compared to Bob Hanlon's elegant and concise solution; however, the advantage of this solution is that it is modular and can be modified by users at various Mma skill levels should they need to.

enter image description here

$\endgroup$
7
$\begingroup$
Clear["Global`*"]

$Version

(* "12.3.1 for Mac OS X x86 (64-bit) (June 19, 2021)" *)

months = {"January", "February", "March", "April", "May", "June", 
   "July", "August", "September", "October", "November", "December"};

monthRule = Thread[months -> Range[12]];

monthQ[mo_String] := MemberQ[months, mo]

lis = {"a", "b", "October", "01", "2021", "c", "d", "October", "1", 
   "2021", "e", "October", "2", "2021", "f"};

Use SequenceCases to select the groupings. Then format the groupings.

lis2 = {DateObject[#[[1 ;; 3]] /. monthRule // ToExpression // 
      RotateRight], Sequence @@ #[[4 ;;]]} & /@ 
  SequenceCases[lis, { _?monthQ, _?(! monthQ[#] &) ..}]

enter image description here

$\endgroup$
4
  • $\begingroup$ Thanks @Bob Hanlon and @kglr! For these solutions to work, I need to first clean up the list by somehow deleting all list elements that appear before the first occurrence of the pattern: {,"Saturday","October","02","2021",} in the list. Again thx for ideas. $\endgroup$
    – Suite401
    Oct 5 at 21:10
  • $\begingroup$ @Suite401 - If you mean you want to delete all items prior to Oct 2, 2021 use Select[lis2, DateDifference[DateObject[{2021, 10, 2}], #[[1]]] > Quantity[-1, "day"] &] $\endgroup$
    – Bob Hanlon
    Oct 5 at 22:10
  • $\begingroup$ @Suite401, In the solution I have posted, it is quite easy to filter the first sublist if it doesn't contain the desired pattern. $\endgroup$
    – Syed
    Oct 6 at 9:50
  • $\begingroup$ Thank you @Syed $\endgroup$
    – Suite401
    Oct 6 at 15:29
5
$\begingroup$
ClearAll[reOrg]
reOrg = {DateObject @ DateString @ StringRiffle[{#, #2, #3}, "/"], ##4} &;

ipdQ = Internal`PossibleDateQ;

SequenceCases[lis, 
 p : {_?ipdQ, _, _?ipdQ, Except[_?ipdQ] ..} :> reOrg @@ p]

enter image description here

Alternatively,

reOrg @@@ SequenceCases[lis, {_?ipdQ, _, _?ipdQ, Except[_?ipdQ] ..}]

enter image description here

$\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.