2
$\begingroup$

Please can someone help with this question, I'm a bit stuck.

Write a function which will accept a list corresponding to a mark list, where each entry in the mark list has the form {test number, first name, family name, mark} or, if a mark is missing, {test number, first name, family name} and which will return a new list of consolidated marks where each list member has the form {first name, family name, test1_mark, test2_mark, ...} in alphabetical order of first names, for example, {{Barak, Obama, 22}, {..., ..., ...}, ...}.

Each list member should only contain test marks for tests attempted for each person (and so each member may have a different length). Your function should make use of a rule using appropriate patterns, and will probably need to use the Map and Rest functions and a function to sort by the first name.

marks = {{1, David, Cameron, 18}, {2, Donald, Trump, 8}, {3, David, Cameron, 11}, 
         {1, Barak, Obama, 22}, {1, Boris, Johnson}, {2, Boris, Johnson},
         {3, Boris, Johnson}, {4, David, Cameron, 17}};

So far I have created a rule, but it does not work on cases where there is no mark.

 list = {z_, x_, y_, v_} -> {x, y, v};
$\endgroup$
1
  • $\begingroup$ I think you can define two functions with the same name and with different numbers of arguments. For example: uFun[{z_,x_,y_,v_}]:={x,y,v} and uFun[{z_,x_,y_}]:={x,y}. Then uFun @ mark should make the trick. $\endgroup$ Commented Feb 6, 2020 at 23:43

4 Answers 4

1
$\begingroup$

Not 100% clear what you want, but this will get you going.

Note that I use strings in the data.

marks = {{1, "David", "Cameron", 18}, {2, "Donald", "Trump", 8}, {3, 
"David", "Cameron", 11}, {1, "Barak", "Obama", 22}, {1, "Boris", 
"Johnson"}, {2, "Boris", "Johnson"}, {3, "Boris", "Johnson"}, {4, 
"David", "Cameron", 17}};

I assign a 'zero' if the test is listed but no grade given.

load[{tn_, fn_, ln_, grade_}] := {fn, ln, {tn, grade}};
load[{tn_, fn_, ln_}] := {fn, ln, {tn, 0}};

Run the load function, sort it, and GatherBy the first and last name.

db = GatherBy[Sort@Map[load,marks], #[[1]] && #[[2]] &];

{{{"Barak", "Obama", {1, 22}}}, {{"Boris", "Johnson", {1, 0}}, {"Boris", "Johnson", {2, 0}}, {"Boris", "Johnson", {3, 0}}}, {{"David", "Cameron", {1, 18}}, {"David", "Cameron", {3, 11}}, {"David", "Cameron", {4, 17}}}, {{"Donald", "Trump", {2, 8}}}}

Now group things together so it is {first_name, last_name,{{test,score},{test,score},...}

Map[{#[[1, 1]], #[[1, 2]], Transpose@# // Last} &, db]

{{"Barak", "Obama", {{1, 22}}}, {"Boris", "Johnson", {{1, 0}, {2, 0}, {3, 0}}}, {"David", "Cameron", {{1, 18}, {3, 11}, {4, 17}}}, {"Donald", "Trump", {{2, 8}}}}

$\endgroup$
3
$\begingroup$

Using marks from MikeY's answer:

assoc = KeySort @ GroupBy[PadRight[marks][[All, {2, 3, 1, 4}]],
   #[[;;2]] &, #[[All, 3 ;;]] &]

<|{"Barak", "Obama"} -> {{1, 22}},
{"Boris", "Johnson"} -> {{1, 0}, {2, 0}, {3, 0}},
{"David", "Cameron"} -> {{1, 18}, {3, 11}, {4, 17}},
{"Donald", "Trump"} -> {{2, 8}}|>

KeyValueMap[{## & @@ #, #2} &] @ assoc

{{"Barak", "Obama", {{1, 22}}},
{"Boris", "Johnson", {{1, 0}, {2, 0}, {3, 0}}},
{"David", "Cameron", {{1, 18}, {3, 11}, {4, 17}}},
{"Donald", "Trump", {{2, 8}}}}

Grid[%, Alignment -> {Left, Center}, Dividers -> {{True, False, {True}}, All}]

enter image description here

KeyValueMap[Apply[Join]@*List]@assoc // 
 Grid[#, Alignment -> {Left, Center}, Dividers -> {{True, False, {True}}, All}] &

enter image description here

Perhaps Dataset is a better approach to organize this sort of data:

ds1 = KeySort @ Dataset @ 
      GroupBy[PadRight[marks, Automatic, Missing[]][[All, {2, 3, 1, 4}]], 
       StringRiffle[#[[;; 2]]] &, 
       Association["tests" -> Association[ToString[#3] -> #4 & @@@ #]] &]

enter image description here

or, perhaps,

ds2 = KeySort /* Transpose /* Map[KeySort] @* Transpose @
  Dataset[GroupBy[PadRight[marks, Automatic, "n/a"][[All, {2, 3, 1, 4}]], 
     StringRiffle[#[[;; 2]]] &, 
     Association["test" <> ToString[#3] -> #4 & @@@ #] &]]

enter image description here

$\endgroup$
1
  • $\begingroup$ I’ve never gotten my head around Associations. Thanks for the answer. $\endgroup$ Commented Feb 7, 2020 at 11:59
2
$\begingroup$
marks =
  {{1, "David", "Cameron", 18}, {2, "Donald", "Trump", 8}, {3, 
    "David", "Cameron", 11}, {1, "Barak", "Obama", 22}, {1, "Boris", 
    "Johnson"}, {2, "Boris", "Johnson"}, {3, "Boris", "Johnson"}, {4, 
    "David", "Cameron", 17}};

Using Merge and MapApply (new in 13.1)

KeySort @ Merge[# &] @ MapApply[{{#2, #3} -> {#1, #4}} &] @ PadRight[marks]

<|{"Barak", "Obama"} -> {{1, 22}}, {"Boris", "Johnson"} -> {{1, 0}, {2, 0}, {3, 0}}, {"David", "Cameron"} -> {{1, 18}, {3, 11}, {4, 17}}, {"Donald", "Trump"} -> {{2, 8}}|>

KeyValueMap[List][%] // Column

{{{"Barak", "Obama"}, {{1, 22}}},
{{"Boris", "Johnson"}, {{1, 0}, {2, 0}, {3, 0}}},
{{"David", "Cameron"}, {{1, 18}, {3, 11}, {4, 17}}},
{{"Donald", "Trump"}, {{2, 8}}}}

$\endgroup$
1
$\begingroup$

If you want a rule replacement, you could use this:

list = marks /. {{tn_, fn_, ln_, grade_} :> {fn, ln, {tn, grade}}, {tn_, fn_,
     ln_} :> {fn, ln, {tn, 0}}}

All other gathering and sorting as per @MikeY could then be applied.

Note that you want RuleDelayed ":>" rather than rule "->" because each time the replacement is evaluated, the values must change.

$\endgroup$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.