96

Is there a way to slice only the first and last item in a list?

For example; If this is my list:

>>> some_list
['1', 'B', '3', 'D', '5', 'F']

I want to do this (obviously [0,-1] is not valid syntax):

>>> first_item, last_item = some_list[0,-1]
>>> print first_item
'1'
>>> print last_item
'F'

Some things I have tried:

In [3]: some_list[::-1]
Out[3]: ['F', '5', 'D', '3', 'B', '1']

In [4]: some_list[-1:1:-1]
Out[4]: ['F', '5', 'D', '3']

In [5]: some_list[0:-1:-1]
Out[5]: []
...
chown
  • 51,908
  • 16
  • 134
  • 170
  • 6
    Haha 3 answers, identical, in the space of 2 seconds, and one was yours. Classic. – Aesthete Aug 31 '12 at 15:57
  • 3
    What's bad about `first, last = some_list[0], some_list[-1]`? – Matthew Adams Aug 31 '12 at 15:58
  • @MatthewAdams Because I am splitting it in the same line, and that would have to spend time splitting it twice: `x, y = a.split("-")[0], a.split("-")[-1]`. – chown Aug 31 '12 at 15:59
  • But actually, I am going to have to get the length of the list first anyway, so, I may need end up doing that. – chown Aug 31 '12 at 16:00
  • 3
    FWIW, I would reject `some_list[0::len(some_list)-1]` in a code review. Too clever by half. – DSM Aug 31 '12 at 16:01
  • 2
    @chown: but then, with your solution of the step set to len-1 you'd have to split twice again for getting the length anyway! – Martijn Pieters Aug 31 '12 at 16:01

13 Answers13

125

One way:

some_list[::len(some_list)-1]

A better way (Doesn't use slicing, but is easier to read):

[some_list[0], some_list[-1]]
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • 26
    The second form is *much* more readable. Explicit is better than implicit again. – Martijn Pieters Aug 31 '12 at 15:59
  • 4
    Hence the "possibly a better way". the only reason I didn't put it first is because the question explicitly asks for a "slice" and the second form isn't technically a slice ... – mgilson Aug 31 '12 at 16:00
  • 4
    It's never too late to educate the OP about the folly of his ways. :-P – Martijn Pieters Aug 31 '12 at 16:02
  • @Martijn I did end up using `[0]` & `[-1]` since I was splitting a string into the list and I would have had to split it again to get the length. The Zen of Python wins again (*Simple is better than complex.*) ;). – chown Aug 31 '12 at 18:22
  • 2
    if there is only one item in some_list, the slice form fails with "ValueError: slice step cannot be zero" – rickfoosusa Apr 13 '15 at 20:56
  • 1
    @rickfoosusa -- well ... I guess that depends on the expected output. You're right that it doesn't give you 2 items as a result which (for a lot of applications) might be considered an error... The "better" way gives you the same item twice which might be equally as bad depending on the application I suppose... – mgilson Apr 13 '15 at 20:58
  • in case of numpy arrays: try np.append() – maniac Oct 17 '18 at 09:24
  • @rickfoosusa: I view that as a good thing in most cases; when you're doing this, there's a good change you implicitly expect *distinct* first and last elements. Silently receiving the same element twice is worse than being loudly informed of the error. [My solution](https://stackoverflow.com/a/43451567/364696) has the same feature (it improves on `[::len(seq)-1]` by working with arbitrary iterables, not just sequences, but suffers from the unavoidable temporary `list` that all the other elements get shoved into). – ShadowRanger Jan 04 '21 at 19:15
42

Python 3 only answer (that doesn't use slicing or throw away the rest of the list, but might be good enough anyway) is use unpacking generalizations to get first and last separate from the middle:

first, *_, last = some_list

The choice of _ as the catchall for the "rest" of the arguments is arbitrary; they'll be stored in the name _ which is often used as a stand-in for "stuff I don't care about".

Unlike many other solutions, this one will ensure there are at least two elements in the sequence; if there is only one (so first and last would be identical), it will raise an exception (ValueError).

Note: Since it must collect all the other elements into _, this is inappropriate for large inputs that are already sequences, doing unnecessary work (O(n), vs. O(1) for direct indexing or slicing approaches). In exchange, on top of the error-checking, it will work with any iterable input, not just lists, tuples, and other sequence types.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • 5
    I'm surprised this didn't get more upvotes. It's by far the most elegant solution, and avoids referring to the list more than once. Perfect for things like "open file - get first line - split it - get first and last elements (which is what brought me here). – nicolaum Apr 12 '20 at 09:49
  • @nicolaum: Given it was posted five years after the other answers, I'm not particularly surprised. If anything, I'm pleasantly surprised it's risen as high as it did, given the only time people see it is when they look for this question and scroll past the accepted answer, it doesn't typically appear in anyone's front page. And it's not a perfect solution, given the fact it doesn't actually throw away the middle elements, being forced to construct a temporary `list` (which the other answers avoid, at the expense of not guaranteeing at least two elements). – ShadowRanger Jan 04 '21 at 19:17
  • 6
    It's also O(N) whereas a lot of the other solutions are O(1). Whether _that_ matters or not depends a lot on the application :). – mgilson Jan 05 '21 at 16:09
  • 1
    @mgilson: True, that's a necessary consequence of working with arbitrary iterables, including iterators; you can't get to the end without getting to the end. :-) – ShadowRanger Jan 05 '21 at 21:27
  • I would like to emphasize that this approach being O(N) means it is worse than the @mgilson [solutions](https://stackoverflow.com/a/12218829/5721867), which are O(1). Also, to be specific, the performance overhead should only make a difference for operations that need to be tuned for performance, like those running against lists with millions of items. – ferreiradev Mar 04 '23 at 18:52
  • 1
    @ferreiradev: In fairness, for the arbitrary iterable case (which this supports with no changes), you *can't* do it in under `O(n)`. It has worse performance for longer inputs that are already sequences that other approaches. In exchange, it has (slightly) better performance for the shorter inputs, performs implicit error-checking, and works on any iterable input. "Worse" is an overstatement. – ShadowRanger Mar 04 '23 at 19:08
  • Spent some time trying to understand your point, and I admit that when you consider objects that are not [sequence types](https://docs.python.org/3/glossary.html#term-sequence), such as dict and set, you can't do it in under O(n), which is noteworthy. Thank you. There are some nuances in the words sequence, list and iterable that readers must be wary of to understand your answer, especially those expecting an answer scoped solely to lists (not dicts, sets, iterators). – ferreiradev Mar 04 '23 at 20:37
21

Just thought I'd show how to do this with numpy's fancy indexing:

>>> import numpy
>>> some_list = ['1', 'B', '3', 'D', '5', 'F']
>>> numpy.array(some_list)[[0,-1]]
array(['1', 'F'], 
      dtype='|S1')

Note that it also supports arbitrary index locations, which the [::len(some_list)-1] method would not work for:

>>> numpy.array(some_list)[[0,2,-1]]
array(['1', '3', 'F'], 
      dtype='|S1')

As DSM points out, you can do something similar with itemgetter:

>>> import operator
>>> operator.itemgetter(0, 2, -1)(some_list)
('1', '3', 'F')
jterrace
  • 64,866
  • 22
  • 157
  • 202
17
first, last = some_list[0], some_list[-1]
Katriel
  • 120,462
  • 19
  • 136
  • 170
9

Some people are answering the wrong question, it seems. You said you want to do:

>>> first_item, last_item = some_list[0,-1]
>>> print first_item
'1'
>>> print last_item
'F'

Ie., you want to extract the first and last elements each into separate variables.

In this case, the answers by Matthew Adams, pemistahl, and katrielalex are valid. This is just a compound assignment:

first_item, last_item = some_list[0], some_list[-1]

But later you state a complication: "I am splitting it in the same line, and that would have to spend time splitting it twice:"

x, y = a.split("-")[0], a.split("-")[-1]

So in order to avoid two split() calls, you must only operate on the list which results from splitting once.

In this case, attempting to do too much in one line is a detriment to clarity and simplicity. Use a variable to hold the split result:

lst = a.split("-")
first_item, last_item = lst[0], lst[-1]

Other responses answered the question of "how to get a new list, consisting of the first and last elements of a list?" They were probably inspired by your title, which mentions slicing, which you actually don't want, according to a careful reading of your question.

AFAIK are 3 ways to get a new list with the 0th and last elements of a list:

>>> s = 'Python ver. 3.4'
>>> a = s.split()
>>> a
['Python', 'ver.', '3.4']

>>> [ a[0], a[-1] ]        # mentioned above
['Python', '3.4']

>>> a[::len(a)-1]          # also mentioned above
['Python', '3.4']

>>> [ a[e] for e in (0,-1) ] # list comprehension, nobody mentioned?
['Python', '3.4']

# Or, if you insist on doing it in one line:
>>> [ s.split()[e] for e in (0,-1) ]
['Python', '3.4']

The advantage of the list comprehension approach, is that the set of indices in the tuple can be arbitrary and programmatically generated.

crobc1
  • 161
  • 2
  • 4
  • Just an FYI, the list comprehension approach's advantage of "arbitrary and programmatically generated" indices is shared by [a solution using `operator.itemgetter`](https://stackoverflow.com/a/43451355/364696); `itemgetter` takes an arbitrary number of things to look up when you construct it, and retrieves them all as a single `tuple` when you call the result on a collection. Typically runs faster than a listcomp, especially if the `itemgetter` is pre-constructed and reused, though it has the weakness of not tupling the result if only one item is requested. – ShadowRanger Feb 24 '20 at 20:51
7

You can do it like this:

some_list[0::len(some_list)-1]
Oleh Prypin
  • 33,184
  • 10
  • 89
  • 99
7

What about this?

>>> first_element, last_element = some_list[0], some_list[-1]
pemistahl
  • 9,304
  • 8
  • 45
  • 75
6

You can use something like

y[::max(1, len(y)-1)]

if you really want to use slicing. The advantage of this is that it cannot give index errors and works with length 1 or 0 lists as well.

chtenb
  • 14,924
  • 14
  • 78
  • 116
4

Actually, I just figured it out:

In [20]: some_list[::len(some_list) - 1]
Out[20]: ['1', 'F']
chown
  • 51,908
  • 16
  • 134
  • 170
4

This isn't a "slice", but it is a general solution that doesn't use explicit indexing, and works for the scenario where the sequence in question is anonymous (so you can create and "slice" on the same line, without creating twice and indexing twice): operator.itemgetter

import operator

# Done once and reused
first_and_last = operator.itemgetter(0, -1)

...

first, last = first_and_last(some_list)

You could just inline it as (after from operator import itemgetter for brevity at time of use):

first, last = itemgetter(0, -1)(some_list)

but if you'll be reusing the getter a lot, you can save the work of recreating it (and give it a useful, self-documenting name) by creating it once ahead of time.

Thus, for your specific use case, you can replace:

x, y = a.split("-")[0], a.split("-")[-1]

with:

x, y = itemgetter(0, -1)(a.split("-"))

and split only once without storing the complete list in a persistent name for len checking or double-indexing or the like.

Note that itemgetter for multiple items returns a tuple, not a list, so if you're not just unpacking it to specific names, and need a true list, you'd have to wrap the call in the list constructor.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
3

How about this?

some_list[:1] + some_list[-1:]

Result: ['1', 'F']
Paolo
  • 20,112
  • 21
  • 72
  • 113
Brendan Metcalfe
  • 753
  • 10
  • 10
2

More General Case: Return N points from each end of list

The answers work for the specific first and last, but some, like myself, may be looking for a solution that can be applied to a more general case in which you can return the top N points from either side of the list (say you have a sorted list and only want the 5 highest or lowest), i came up with the following solution:

In [1]
def GetWings(inlist,winglen):
    if len(inlist)<=winglen*2:
        outlist=inlist
    else:
        outlist=list(inlist[:winglen])
        outlist.extend(list(inlist[-winglen:]))
    return outlist

and an example to return bottom and top 3 numbers from list 1-10:

In [2]
GetWings([1,2,3,4,5,6,7,8,9,10],3)

#Out[2]
#[1, 2, 3, 8, 9, 10]
Vlox
  • 717
  • 8
  • 18
0

Fun new approach to "one-lining" the case of an anonymously split thing such that you don't split it twice, but do all the work in one line is using the walrus operator, :=, to perform assignment as an expression, allowing both:

first, last = (split_str := a.split("-"))[0], split_str[-1]

and:

first, last = (split_str := a.split("-"))[::len(split_str)-1]

Mind you, in both cases it's essentially exactly equivalent to doing on one line:

split_str = a.split("-")

then following up with one of:

first, last = split_str[0], split_str[-1]
first, last = split_str[::len(split_str)-1]

including the fact that split_str persists beyond the line it was used and accessed on. It's just technically meeting the requirements of one-lining, while being fairly ugly. I'd never recommend it over unpacking or itemgetter solutions, even if one-lining was mandatory (ruling out the non-walrus versions that explicitly index or slice a named variable and must refer to said named variable twice).

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271