4

I have two lists of strings.

list_one = ["c11", "a78", "67b"]
list_two = ["a", "b", "c"]

What is the shortest way of sorting list_one using strings from list_two to get the following output?

["a78", "67b", "c11"]

Edit 1: There is a similar question Sorting list based on values from another list?, but in that question he already has the list of required indexes for resulting string, while here I have just the list of substrings.

Edit 2: Since the example of list above might be not fully representative, I add another case.

list_one is ["1.cde.png", "1.abc.png", "1.bcd.png"] list_two is ["abc", "bcd", "cde"]. The output is supposed to be [ "1.abc.png", "1.bcd.png", "1.cde.png"]

If, for example, list_one is shorter than list_two, it should still work:

list_one is ["1.cde.png", "1.abc.png"] list_two is ["abc", "bcd", "cde"] The output is supposed to be [ "1.abc.png", "1.cde.png"]

  • 2
    Possible duplicate of [Sorting list based on values from another list?](https://stackoverflow.com/questions/6618515/sorting-list-based-on-values-from-another-list) – Tom Wojcik Jul 19 '18 at 09:07
  • Your expected output doesn't make sense. Why is `67b` second? – blhsing Jul 19 '18 at 09:09
  • what is the relation between list_one and list_two? It seems list_one's objects are included list_two's values. Is it the criteria? – Ali Yesilli Jul 19 '18 at 09:11
  • @TomWojcik in https://stackoverflow.com/questions/6618515/sorting-list-based-on-values-from-another-list he already has the list of required order for resulting string, while here I have just the list of substrings – Aliaksei Laurynovich Jul 19 '18 at 09:11
  • @blhsing because in the list_two, substring "b" is the second – Aliaksei Laurynovich Jul 19 '18 at 09:12
  • @AliYesilli, well, for example if list_one was ["c11", "a78"], while list_two is ["a", "b", "c"], then the sorted list should be ["a78", "c11"]. All strings of list_one should have substrings from list_two. – Aliaksei Laurynovich Jul 19 '18 at 09:19
  • Will each element always contain exactly one of the elements from the other list? And will this always be the only letter in that string, or could it be any character? – tobias_k Jul 19 '18 at 09:28
  • @tobias_k well, the substrings in the list_two can be of any length. There possibly can be two elements in the list_one with the same combination of substrings from the list_two, but for me it doesn't actually matter, as in my problem this will not be the case – Aliaksei Laurynovich Jul 19 '18 at 09:37
  • @AliakseiLaurynovich I don't understand: So, can there be n:m matches, or can there not? The substrings being longer would not be a problem, as long as they are distinct from the other characters in the string, as they are in your example. If your example is not representative of the problem, please add another one. – tobias_k Jul 19 '18 at 09:42
  • @tobias_k Hmm, I understand your point. Assume that list_one is now ["1.cde.png", "1.abc.png", "1.bcd.png"], while list_two is ["abc", "bcd", "cde"]. The output is supposed to be [ "1.abc.png", "1.bcd.png", "1.cde.png"]. – Aliaksei Laurynovich Jul 19 '18 at 09:52

4 Answers4

4
key = {next((s for s in list_one if v in s), None): i for i, v in enumerate(list_two)}
print(sorted(list_one, key=key.get))

This outputs:

['a78', '67b', 'c11']
blhsing
  • 91,368
  • 6
  • 71
  • 106
  • Your code doesn't work if list_one is smaller than list_two. It gives an error "IndexError: list index out of range" – Aliaksei Laurynovich Jul 19 '18 at 09:30
  • Indeed. Fixed then. – blhsing Jul 19 '18 at 09:35
  • 1
    Why `key=key.__getitem__` instead of `key=key.get`? – tobias_k Jul 19 '18 at 09:39
  • I'm not sure what exactly you have changed, but it still doesn't work for the case when list_one is shorter than list_two. For example, I tested your code for the case when list_one = ["c11", "a78"], list_two = ["a", "b", "c"]. The output is supposed to be ["a78", "c11"]. But it gives me an error "IndexError: list index out of range". – Aliaksei Laurynovich Jul 19 '18 at 09:46
  • @tobias_k Good point. `__getitem__` was entirely unnecessary. Revised as suggested. Thanks. – blhsing Jul 19 '18 at 09:56
  • @AliakseiLaurynovich I now use the `next` function. And with that I can not reproduce the `IndexError` with the scenario you're giving here. – blhsing Jul 19 '18 at 09:57
2

Try this

list_one = ["c11", "a78", "67b"]
list_two = ["a", "b", "c"]

[x for y in list_two for x in list_one if y in x]

Output :

["a78", "67b", "c11"]
khelili miliana
  • 3,730
  • 2
  • 15
  • 28
  • 1
    if list_one has itens without "a", "b" or "c", and you want to keep them at the end of the output list, add this: `OUTPUT_LIST + list(set(list_one) - set(OUTPUT_LIST))` – cawecoy May 14 '23 at 23:20
1

Assuming that each item in list_one contains exactly one of the characters from list_two, and that you know the class of those characters, e.g. letters, you can extract those using a regex and build a dictionary mapping the characters to the element. Then, just look up the correct element for each character.

>>> list_one = ["c11", "a78", "67b"]
>>> list_two = ["a", "b", "c"]
>>> d = {re.search("[a-z]", s).group(): s for s in list_one}
>>> list(map(d.get, list_two))
['a78', '67b', 'c11']
>>> [d[c] for c in list_two]
['a78', '67b', 'c11']

Other than the other approaches posted so far, which all seem to be O(n²), this is only O(n).

Of course, the approach can be generalized to e.g. more than one character, or characters in specific positions of the first string, but it will always require some pattern and knowledge about that pattern. E.g., for your more recent example:

>>> list_one = ["1.cde.png", "1.abc.png", "1.bcd.png"]
>>> list_two = ["abc", "cde"]
>>> d = {re.search("\.(\w+)\.", s).group(1): s for s in list_one}
>>> d = {s.split(".")[1]: s for s in list_one}  # alternatively without re
>>> [d[c] for c in list_two if c in d]
['1.abc.png', '1.cde.png']
tobias_k
  • 81,265
  • 12
  • 120
  • 179
-1
>>> sorted(list_one, key=lambda x: [i for i,e in enumerate(list_two) if e in x][0])
['a78', '67b', 'c11']
Sunitha
  • 11,777
  • 2
  • 20
  • 23