5
$\begingroup$

Consider two lists of rules:

list1 = {"a" -> "apple", "b" -> "banana", "c" -> "cucumber"};
list2 = {"A" -> "pie", "B" -> "split", "C" -> "salad"};

I'm looking to come up with a way to "combine" these lists into a third list of rules Rule[key, val] in which key is a value from a rule taken from list1 and val is the value taken from the matching rule in list2. "Matching" here is subject to a custom criterion.

In this example, I want to get the list

{"apple" -> "pie", "banana" -> "split", "cucumber" -> "salad"}

where the combining criterion is that the keys consist of the same letter; that is, "a" and "A" satisfy some version of StringMatchQ[#1, #2, IgnoreCase -> True] &, for instance.

Is there a good way to do this?

For this particular example, this works (though I bet there's a neater way to do it):

With[
    {list1 = {"a" -> "apple", "b" -> "banana", "c" -> "cucumber"},
     list2 = {"A" -> "pie", "B" -> "split", "C" -> "salad"}},
    (Reverse /@ list1) /. (list2 /. s_String :> ToLowerCase[s])
]
(* {"apple" -> "pie", "banana" -> "split", "cucumber" -> "salad"} *)

But in my more general use case, I'll have list1 and list2 with keys like {"x1", "y1", "z1"} and {"X-one", "Y-one", "Z-one"}, with the matching criterion that values to-be-Rule-ified are the ones whose corresponding keys contain 1 and one.

P.S. Sorry if the title is confusing, I wasn't quite sure how to word it.

$\endgroup$

5 Answers 5

6
$\begingroup$
list1 /. Rule[a_, b_] :> Rule[b, ToUpperCase[a] /. list2]

{"apple" -> "pie", "banana" -> "split", "cucumber" -> "salad"}

or

list2 /. Rule[a_, b_] :> Rule[ToLowerCase[a] /. list1, b]

{"apple" -> "pie", "banana" -> "split", "cucumber" -> "salad"}

Also:

Values @ GroupBy[Join[list1, list2], ToLowerCase[#[[1]]] &,  Apply[Rule]@*Values]

{"apple" -> "pie", "banana" -> "split", "cucumber" -> "salad"}

and

Values @ Merge[Apply[Rule]][MapAt[ToLowerCase, #, {All, 1}] & /@ {list1, list2}]

{"apple" -> "pie", "banana" -> "split", "cucumber" -> "salad"}

For general cases like the second example in OP, you can define case-specific conversion rules and convert keys of one of the input lists before using Merge or GroupBy:

k2Tok1 = StringReplace[a__ ~~ "-" ~~ b__ :> 
  ToLowerCase[a] <> ToString[Interpreter["SemanticNumber"][b]]] ;

k1Tok2 = StringReplace[ a__ ~~ i : NumberString :>
   ToUpperCase[a] <> "-" <> IntegerName[FromDigits@i]] ;

Examples:

k1 = {"x1", "y1", "z5"};
k2 = {"X-one", "Y-one", "Z-six"};

k1Tok2 @ k1

{"X-one", "Y-one", "Z-five"}

k2Tok1 @ k2

{"x1", "y1", "z6"}

lst1 = {"x1" -> "apple", "y1" -> "banana", "z6" -> "cucumber"};
lst2 = {"X-one" -> "pie", "Y-one" -> "split", "Z-six" -> "salad"};

lst1 /. Rule[a_, b_] :> Rule[b, k1Tok2[a] /. lst2]

{"apple" -> "pie", "banana" -> "split", "cucumber" -> "salad"}

lst2 /. Rule[a_, b_] :> Rule[k2Tok1[a] /. lst1, b]

{"apple" -> "pie", "banana" -> "split", "cucumber" -> "salad"}

$\endgroup$
5
$\begingroup$
list1 = {"a" -> "apple", "b" -> "banana", "c" -> "cucumber"};
list2 = {"A" -> "pie", "B" -> "split", "C" -> "salad"};

MapThread[Rule, 
 Through[{Lookup[list1, #] &, Lookup[list2, ToUpperCase@#, Null] &}[
   Keys@list1]]
 ]

{"apple" -> "pie", "banana" -> "split", "cucumber" -> "salad"}


Borrow kglr's function k1Tok2 for a demo:

list3 = {"x1" -> "apple", "y1" -> "banana", "z6" -> "cucumber"};
list4 = {"X-one" -> "pie", "Y-one" -> "split", "Z-six" -> "salad"};

MapThread[Rule, 
 Through[{Lookup[list3, #] &, Lookup[list4, k1Tok2@#, Null] &}[
   Keys@list3]]
 ]

Another possibility:

Merge[{KeyMap[k1Tok2, Association@list3], 
    KeyMap[k1Tok2, Association@list4]}, Identity] // Values // 
 Map[Apply[Rule]]

{"apple" -> "pie", "banana" -> "split", "cucumber" -> "salad"}

$\endgroup$
4
$\begingroup$
lst1 = {"x1" -> "apple", "y1" -> "banana", "z6" -> "cucumber"};
lst2 = {"X-one" -> "pie", "Y-one" -> "split", "Z-six" -> "salad"};

An alternative to maintain the required order is to use SortBy as follows:

f = Rule @@@ (Map[SortBy[#, UpperCaseQ[StringTake[#[[1]], 1]] &] &]@
    GatherBy[Join[#1, #2], ToUpperCase[StringTake[#[[1]], 1]] &])[[All, All, 2]] &;

f[lst1, lst2] === f[lst2, lst1]

f[lst1, lst2]

True

{"apple" -> "pie", "banana" -> "split", "cucumber" -> "salad"}

Or using ReverseSortBy:

g = Rule @@@ (Map[ReverseSortBy[#, LowerCaseQ[StringTake[#[[1]], 1]] &] &]@
    GatherBy[Join[#1, #2], ToLowerCase[StringTake[#[[1]], 1]] &])[[All, All, 2]] &;

g[lst1, lst2] === g[lst2, lst1]

g[lst1, lst2]

True

{"apple" -> "pie", "banana" -> "split", "cucumber" -> "salad"}

$\endgroup$
3
$\begingroup$
MapAt[Replace[list1], MapAt[ToLowerCase, list2, {All, 1}], {All, 1}]

(* {"apple" -> "pie", "banana" -> "split", "cucumber" -> "salad"} *)
$\endgroup$
3
$\begingroup$

Two other ways to do this are

Thread[Values[list1] -> Values[list2]]

or

AssociationThread[Values[list1], Values[list2]] // Normal

Both give: (* {"apple" -> "pie", "banana" -> "split", "cucumber" -> "salad"} *)

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.