3

I have a list of strings. I want to only sort values that meet a certain condition. Consider this list

['foo','bar','testa','python','java','abc']

and I only want to sort the values with an a in them. The result should look like this

['foo','abc','bar','python','java','testa']

The elements with a will change places appropriately, but the other elements retain their original positions.

I have absolutely no idea how to implement this, so I hope someone else does. Can someone show me how to do this?

Prune
  • 76,765
  • 14
  • 60
  • 81

5 Answers5

8
y = sorted(w for w in x if 'a' in w)  # pick and sort only the elements with 'a'
x = [w if 'a' not in w else y.pop(0) for w in x]

The last line leaves word without an 'a' in them unchanged, while those with 'a' are picked progressively from the y list (that is already sorted)

EDIT: @MartijnPieters solution performs better, since it uses an iterator and won't use additional memory to store y.

y = iter(sorted(w for w in x if 'a' in w))  # create iterator, don't use memory
x = [w if 'a' not in w else next(y) for w in x]  # yield from iter instead of popping from a list

Since it looks like you need this algorithm to work with different condition, you could put this into a method:

x = ['foo','bar','testa','python','java','abc']

def conditional_sort(ls, f):
    y = iter(sorted(w for w in ls if f(w)))
    return [w if not f(w) else next(y) for w in ls]

conditional_sort(x, lambda w: 'a' in w)

The first parameter would be the list, the second one a function that takes a single parameter and returns a bool value.

EsotericVoid
  • 2,306
  • 2
  • 16
  • 25
  • 2
    `sorted()` already returns a list, your `list()` call is redundant. It'd be more efficient if you used an iterator instead (`y = iter(sorted(...))` and `else next(y)`). – Martijn Pieters Jul 28 '17 at 20:58
  • You may want to explain *what this does*. It works, but without an explanation it is not nearly as helpful. – Martijn Pieters Jul 28 '17 at 20:58
  • @MartijnPieters yes fixed that. You are right on the iterator. – EsotericVoid Jul 28 '17 at 20:58
  • Also consider how this looks to a beginning programmer. :-) – Prune Jul 28 '17 at 20:59
  • @Prune Sure, I was going to post my solution and then add the explanation after that (I see this is pretty common on SO). Could anybody please explain the downvote? – EsotericVoid Jul 28 '17 at 21:03
  • I don't know why this has a downvote, this is quite helpful. +1 Also, shouldn't the second line end with `for w in y`? – caird coinheringaahing Jul 28 '17 at 21:04
  • @cairdcoinheringaahing no, since `y` does not contain all the words if you iterate over the second list you'd lose elements. So you need to iterate over the list with all the elements (`x`) and pop from `y` if it meets the condition. – EsotericVoid Jul 28 '17 at 21:11
  • @Bit: I think the down-vote came against your original answer, which was only two lines of code and no explanation. I know it was already there when I saw your first update per Martijn's comment. – Prune Jul 28 '17 at 21:28
  • @Prune I guess your right. I thought it was accepted on SO to post a draft of the solution first and then edit with the explanation, I'll post refined answers in the future – EsotericVoid Jul 28 '17 at 21:31
  • 1
    It is acceptable ... but this is a large community, and not everyone has learned all the guidelines. I'm still picking up pointers. My recommendation: keep up the good work, and don't sweat the occasional down-vote. The one you just got is barely 1% of your total, and the overall response (currently +3/-1) suggests that you're on the right track. – Prune Jul 28 '17 at 21:36
  • @Prune I'll do that! I would just prefer sometimes to have some feedback from the downvoter so I can improve my answer. I guess some downvotes will just come anyways, so you're right that it is better to just move on and focus on improving the content I post. Thanks again for your words and your time. – EsotericVoid Jul 28 '17 at 21:41
1

Find the elements with a; mark the positions and pull them out.

orig = ['foo','bar','testa','python','java','abc']
just_a = [str for str in orig if `a` in str]
mark = [`a` in str for str in orig]

This gives us

just_a = ['bar', 'testa', 'java', 'abc'] 
mark = [False, True, True, False, True, True]

Sort just_a; I'm sure you can do that. Now, build your result: where there's True in mark, take the next item in the sorted list; otherwise, take the original element.

result = []
for pos in range len(orig):
    if mark[pos]:
        result.append(sort_a.pop())
    else:
        result.append(orig[pos])

This can be done with much less code. Among other things, this last loop can be done with a list comprehension. This code merely clarifies the process.

Prune
  • 76,765
  • 14
  • 60
  • 81
0

A possible approach would be to :

  1. Extract all values with an 'a' in them and note their positions.
  2. Sort the values alphabetically (see this post).
  3. Insert the sorted values into the original list.
Lucas
  • 3
  • 3
0

This can definitely be simplified, but here's one way of doing it

def custom_sort(lst):
    sorted_list = [x for x in lst if 'a' in x] # get list of everything with an a in it
    sorted_list.sort() # sort this of elements containing a
    final_list = [] # make empty list, we will fill this with what we need
    sorted_counter = 0 # need a counter to keep track of what element containing a have been accounted for below
    for x in lst: # loop over original list
        if 'a' in x: # if that element in our original list contains an a
            final_list.append(sorted_list[sorted_counter]) # then we will from our sorted list of elements with a
            sorted_counter += 1 # increment counter
        else: # otherwise
            final_list.append(x) # we add an element from our original list
    return final_list # return the custom sorted list
jacoblaw
  • 1,263
  • 9
  • 9
0

I just would use two additional lists to keep track of the indices of words with 'a' and to sorted the words:

L=['foo','bar','testa','python','java','abc']
M=[]
count=[]
for t in L:
    if 'a' in t:
        M.append(t)               #append both the word and the index
        count.append(L.index(t))
M=sorted(M)
for l in count:
    L[l]=M[count.index(l)]       
L

Probably is not very efficient but it works.