Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #108766 > unrolled thread
| Started by | Steven D'Aprano <steve@pearwood.info> |
|---|---|
| First post | 2016-05-19 01:47 +1000 |
| Last post | 2016-05-20 23:00 -0500 |
| Articles | 13 — 8 participants |
Back to article view | Back to comp.lang.python
Extract the middle N chars of a string Steven D'Aprano <steve@pearwood.info> - 2016-05-19 01:47 +1000
Re: Extract the middle N chars of a string Ian Kelly <ian.g.kelly@gmail.com> - 2016-05-18 10:34 -0600
Re: Extract the middle N chars of a string Peter Otten <__peter__@web.de> - 2016-05-18 18:41 +0200
Re: Extract the middle N chars of a string Peter Otten <__peter__@web.de> - 2016-05-18 18:54 +0200
Re: Extract the middle N chars of a string Steven D'Aprano <steve@pearwood.info> - 2016-05-19 11:37 +1000
Re: Extract the middle N chars of a string MRAB <python@mrabarnett.plus.com> - 2016-05-18 18:00 +0100
Re: Extract the middle N chars of a string Steven D'Aprano <steve@pearwood.info> - 2016-05-19 11:34 +1000
Re: Extract the middle N chars of a string Random832 <random832@fastmail.com> - 2016-05-18 17:28 -0400
Re: Extract the middle N chars of a string Steven D'Aprano <steve@pearwood.info> - 2016-05-19 11:03 +1000
Re: Extract the middle N chars of a string Grant Edwards <grant.b.edwards@gmail.com> - 2016-05-18 21:35 +0000
Re: Extract the middle N chars of a string Steven D'Aprano <steve@pearwood.info> - 2016-05-19 10:48 +1000
Re: Extract the middle N chars of a string Wildman <best_lay@yahoo.com> - 2016-05-18 23:17 -0500
Re: Extract the middle N chars of a string boB Stepp <robertvstepp@gmail.com> - 2016-05-20 23:00 -0500
| From | Steven D'Aprano <steve@pearwood.info> |
|---|---|
| Date | 2016-05-19 01:47 +1000 |
| Subject | Extract the middle N chars of a string |
| Message-ID | <573c8e97$0$1596$c3e8da3$5496439d@news.astraweb.com> |
Extracting the first N or last N characters of a string is easy with
slicing:
s[:N] # first N
s[-N:] # last N
Getting the middle N seems like it ought to be easy:
s[N//2:-N//2]
but that is wrong. It's not even the right length!
py> s = 'aardvark'
py> s[5//2:-5//2]
'rdv'
So after spending a ridiculous amount of time on what seemed like it ought
to be a trivial function, and an embarrassingly large number of off-by-one
and off-by-I-don't-even errors, I eventually came up with this:
def mid(string, n):
"""Return middle n chars of string."""
L = len(string)
if n <= 0:
return ''
elif n < L:
Lr = L % 2
a, ar = divmod(L-n, 2)
b, br = divmod(L+n, 2)
a += Lr*ar
b += Lr*br
string = string[a:b]
return string
which works for me:
# string with odd number of characters
py> for i in range(1, 8):
... print mid('abcdefg', i)
...
d
de
cde
cdef
bcdef
bcdefg
abcdefg
# string with even number of characters
py> for i in range(1, 7):
... print mid('abcdef', i)
...
c
cd
bcd
bcde
abcde
abcdef
Is this the simplest way to get the middle N characters?
--
Steven
[toc] | [next] | [standalone]
| From | Ian Kelly <ian.g.kelly@gmail.com> |
|---|---|
| Date | 2016-05-18 10:34 -0600 |
| Message-ID | <mailman.10.1463589299.27390.python-list@python.org> |
| In reply to | #108766 |
On Wed, May 18, 2016 at 9:47 AM, Steven D'Aprano <steve@pearwood.info> wrote:
> Extracting the first N or last N characters of a string is easy with
> slicing:
>
> s[:N] # first N
> s[-N:] # last N
>
> Getting the middle N seems like it ought to be easy:
>
> s[N//2:-N//2]
>
> but that is wrong. It's not even the right length!
>
> py> s = 'aardvark'
> py> s[5//2:-5//2]
> 'rdv'
>
>
> So after spending a ridiculous amount of time on what seemed like it ought
> to be a trivial function, and an embarrassingly large number of off-by-one
> and off-by-I-don't-even errors, I eventually came up with this:
>
> def mid(string, n):
> """Return middle n chars of string."""
> L = len(string)
> if n <= 0:
> return ''
> elif n < L:
> Lr = L % 2
> a, ar = divmod(L-n, 2)
> b, br = divmod(L+n, 2)
> a += Lr*ar
> b += Lr*br
> string = string[a:b]
> return string
>
>
> which works for me:
>
>
> # string with odd number of characters
> py> for i in range(1, 8):
> ... print mid('abcdefg', i)
> ...
> d
> de
> cde
> cdef
> bcdef
> bcdefg
> abcdefg
> # string with even number of characters
> py> for i in range(1, 7):
> ... print mid('abcdef', i)
> ...
> c
> cd
> bcd
> bcde
> abcde
> abcdef
>
>
>
> Is this the simplest way to get the middle N characters?
There's an ambiguity in the problem description when N and the length
of the string have different parity, but how about:
def mid(string, n):
L = len(string)
a = (L - n) // 2
return string[a:a+n]
py> for i in range(1, 8):
... print(mid('abcdefg', i))
...
d
cd
cde
bcde
bcdef
abcdef
abcdefg
[toc] | [prev] | [next] | [standalone]
| From | Peter Otten <__peter__@web.de> |
|---|---|
| Date | 2016-05-18 18:41 +0200 |
| Message-ID | <mailman.11.1463589702.27390.python-list@python.org> |
| In reply to | #108766 |
Steven D'Aprano wrote:
> Extracting the first N or last N characters of a string is easy with
> slicing:
>
> s[:N] # first N
> s[-N:] # last N
>
> Getting the middle N seems like it ought to be easy:
>
> s[N//2:-N//2]
>
> but that is wrong. It's not even the right length!
>
> py> s = 'aardvark'
> py> s[5//2:-5//2]
> 'rdv'
>
>
> So after spending a ridiculous amount of time on what seemed like it ought
> to be a trivial function, and an embarrassingly large number of off-by-one
> and off-by-I-don't-even errors, I eventually came up with this:
>
> def mid(string, n):
> """Return middle n chars of string."""
> L = len(string)
> if n <= 0:
> return ''
> elif n < L:
> Lr = L % 2
> a, ar = divmod(L-n, 2)
> b, br = divmod(L+n, 2)
> a += Lr*ar
> b += Lr*br
> string = string[a:b]
> return string
>
>
> which works for me:
>
>
> # string with odd number of characters
> py> for i in range(1, 8):
> ... print mid('abcdefg', i)
> ...
> d
> de
> cde
> cdef
> bcdef
> bcdefg
> abcdefg
> # string with even number of characters
> py> for i in range(1, 7):
> ... print mid('abcdef', i)
> ...
> c
> cd
> bcd
> bcde
> abcde
> abcdef
>
>
>
> Is this the simplest way to get the middle N characters?
>>> def mid(s, n):
... shave = len(s) - n
... if shave > 0:
... shave //= 2
... s = s[shave:shave+n]
... return s
...
>>> def show(s):
... for i in range(len(s)+1):
... print(i, repr(s), "-->", repr(mid(s, i)))
...
>>> show("abcdefg")
0 'abcdefg' --> ''
1 'abcdefg' --> 'd'
2 'abcdefg' --> 'cd'
3 'abcdefg' --> 'cde'
4 'abcdefg' --> 'bcde'
5 'abcdefg' --> 'bcdef'
6 'abcdefg' --> 'abcdef'
7 'abcdefg' --> 'abcdefg'
>>> show("abcdef")
0 'abcdef' --> ''
1 'abcdef' --> 'c'
2 'abcdef' --> 'cd'
3 'abcdef' --> 'bcd'
4 'abcdef' --> 'bcde'
5 'abcdef' --> 'abcde'
6 'abcdef' --> 'abcdef'
Not exactly the same results as your implementation though.
[toc] | [prev] | [next] | [standalone]
| From | Peter Otten <__peter__@web.de> |
|---|---|
| Date | 2016-05-18 18:54 +0200 |
| Message-ID | <mailman.12.1463590511.27390.python-list@python.org> |
| In reply to | #108766 |
Peter Otten wrote:
>>>> def mid(s, n):
> ... shave = len(s) - n
> ... if shave > 0:
shave += len(s) % 2
> ... shave //= 2
> ... s = s[shave:shave+n]
> ... return s
> Not exactly the same results as your implementation though.
The extra line should fix that, but it looks, err -- odd.
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve@pearwood.info> |
|---|---|
| Date | 2016-05-19 11:37 +1000 |
| Message-ID | <573d18f5$0$1593$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #108776 |
On Thu, 19 May 2016 02:54 am, Peter Otten wrote: > Peter Otten wrote: > >>>>> def mid(s, n): >> ... shave = len(s) - n >> ... if shave > 0: > shave += len(s) % 2 >> ... shave //= 2 >> ... s = s[shave:shave+n] >> ... return s > >> Not exactly the same results as your implementation though. > > The extra line should fix that, but it looks, err -- odd. Nice! Thank you. I like this solution. -- Steven
[toc] | [prev] | [next] | [standalone]
| From | MRAB <python@mrabarnett.plus.com> |
|---|---|
| Date | 2016-05-18 18:00 +0100 |
| Message-ID | <mailman.14.1463590822.27390.python-list@python.org> |
| In reply to | #108766 |
On 2016-05-18 16:47, Steven D'Aprano wrote:
> Extracting the first N or last N characters of a string is easy with
> slicing:
>
> s[:N] # first N
> s[-N:] # last N
>
> Getting the middle N seems like it ought to be easy:
>
> s[N//2:-N//2]
>
> but that is wrong. It's not even the right length!
>
> py> s = 'aardvark'
> py> s[5//2:-5//2]
> 'rdv'
>
>
> So after spending a ridiculous amount of time on what seemed like it ought
> to be a trivial function, and an embarrassingly large number of off-by-one
> and off-by-I-don't-even errors, I eventually came up with this:
>
> def mid(string, n):
> """Return middle n chars of string."""
> L = len(string)
> if n <= 0:
> return ''
> elif n < L:
> Lr = L % 2
> a, ar = divmod(L-n, 2)
> b, br = divmod(L+n, 2)
> a += Lr*ar
> b += Lr*br
> string = string[a:b]
> return string
>
>
> which works for me:
>
>
> # string with odd number of characters
> py> for i in range(1, 8):
> ... print mid('abcdefg', i)
> ...
> d
> de
> cde
> cdef
> bcdef
> bcdefg
> abcdefg
> # string with even number of characters
> py> for i in range(1, 7):
> ... print mid('abcdef', i)
> ...
> c
> cd
> bcd
> bcde
> abcde
> abcdef
>
>
>
> Is this the simplest way to get the middle N characters?
>
I think your results are inconsistent.
For an odd number of characters you have "abc" + "de" + "fg", i.e. more
on the left, but for an even number of characters you have "a" + "bcd" +
"ef", i.e. more on the right.
My own solution is:
def mid(string, n):
"""Return middle n chars of string."""
if n <= 0:
return ''
if n > len(string):
return string
ofs = (len(string) - n) // 2
return string[ofs : ofs + n]
If there's an odd number of characters remaining, it always has more on
the right.
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve@pearwood.info> |
|---|---|
| Date | 2016-05-19 11:34 +1000 |
| Message-ID | <573d1844$0$1593$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #108778 |
On Thu, 19 May 2016 03:00 am, MRAB wrote: > I think your results are inconsistent. > > For an odd number of characters you have "abc" + "de" + "fg", i.e. more > on the left, but for an even number of characters you have "a" + "bcd" + > "ef", i.e. more on the right. Correct. That's intentional. I didn't start with an algorithm. I started by manually extracting the N middle characters, for various values of N, then wrote code to get the same result. For example, if I start with an odd-length string, like "inquisition", then the "middle N" cases for odd-N are no-brainers, because they have to be centered on the middle character: N=1 's' N=3 'isi' N=5 'uisit' For even-N, I had a choice: N=2 'is' or 'si' and to be perfectly frank, I didn't really care much either way and just arbitrarily picked the second, based on the fact that string.center() ends up with a slight bias to the right: py> 'ab'.center(5, '*') '**ab*' For even-length string, like "aardvark", the even-N case is the no-brainer: N=2 "dv" N=4 "rdva" N=6 "ardvar" but with odd-N I have a choice: N=3 "rdv" or "dva" In this case, I *intentionally* biased it the other way, so that (in some sense) overall the mid() function would be unbiased: - half the cases, there's no bias at all; - a quarter of the time, there's a bias to the right; - a quarter of the time, there's a bias to the left; - so on average, the bias is zero. > My own solution is: > > > def mid(string, n): > """Return middle n chars of string.""" > if n <= 0: > return '' > if n > len(string): > return string > ofs = (len(string) - n) // 2 > return string[ofs : ofs + n] > > > If there's an odd number of characters remaining, it always has more on > the right. Thanks to you and Ian (who independently posted a similar solution), that's quite good too if you don't care about the bias. -- Steven
[toc] | [prev] | [next] | [standalone]
| From | Random832 <random832@fastmail.com> |
|---|---|
| Date | 2016-05-18 17:28 -0400 |
| Message-ID | <mailman.19.1463606931.27390.python-list@python.org> |
| In reply to | #108766 |
On Wed, May 18, 2016, at 11:47, Steven D'Aprano wrote:
> So after spending a ridiculous amount of time on what seemed like it
> ought
> to be a trivial function, and an embarrassingly large number of
> off-by-one
> and off-by-I-don't-even errors, I eventually came up with this:
>
> def mid(string, n):
> """Return middle n chars of string."""
> L = len(string)
> if n <= 0:
> return ''
> elif n < L:
> Lr = L % 2
> a, ar = divmod(L-n, 2)
> b, br = divmod(L+n, 2)
> a += Lr*ar
> b += Lr*br
> string = string[a:b]
> return string
My take:
def mid(string, n):
if n > len(string): n = len(string)
if n <= 0: n = 0
offset = int(len(string)/2+n/2)
return string[offset:offset+n]
It doesn't get the same result as yours when the length is odd and N is
even, but the problem is ambiguous there and I feel like my algorithm is
more clear.
I'm funneling the special cases through the slice statement at the end
rather than simply returning string and '' because it's conceptually
nicer and because it could be used for other purposes than slicing
strings.
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve@pearwood.info> |
|---|---|
| Date | 2016-05-19 11:03 +1000 |
| Message-ID | <573d10e0$0$1612$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #108785 |
On Thu, 19 May 2016 07:28 am, Random832 wrote:
> My take:
>
> def mid(string, n):
> if n > len(string): n = len(string)
> if n <= 0: n = 0
> offset = int(len(string)/2+n/2)
> return string[offset:offset+n]
>
> It doesn't get the same result as yours when the length is odd and N is
> even, but the problem is ambiguous there and I feel like my algorithm is
> more clear.
I'm not sure that "the algorithm is more clear" is the right way to judge
this. Especially when your function doesn't even come *close* to meeting
the requirements. It doesn't even return substrings of the right length!
py> for i in range(8):
... print mid('abcdefg', i)
...
<BLANKLINE>
e
ef
fg
fg
g
g
<BLANKLINE>
py> for i in range(7):
... print mid('abcdef', i)
...
<BLANKLINE>
d
ef
ef
f
f
<BLANKLINE>
I sympathise, I really do, because as my first post says, this really does
seem like it ought to be a no-brainer trivial exercise in slicing. Instead,
it is remarkably subtle and tricky to get right.
--
Steven
[toc] | [prev] | [next] | [standalone]
| From | Grant Edwards <grant.b.edwards@gmail.com> |
|---|---|
| Date | 2016-05-18 21:35 +0000 |
| Message-ID | <mailman.20.1463607348.27390.python-list@python.org> |
| In reply to | #108766 |
On 2016-05-18, Steven D'Aprano <steve@pearwood.info> wrote:
> Getting the middle N seems like it ought to be easy:
I'm still trying to figure out when one would want to do that...
--
Grant Edwards grant.b.edwards Yow! My CODE of ETHICS
at is vacationing at famed
gmail.com SCHROON LAKE in upstate
New York!!
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve@pearwood.info> |
|---|---|
| Date | 2016-05-19 10:48 +1000 |
| Message-ID | <573d0d53$0$1598$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #108786 |
On Thu, 19 May 2016 07:35 am, Grant Edwards wrote: > On 2016-05-18, Steven D'Aprano <steve@pearwood.info> wrote: > >> Getting the middle N seems like it ought to be easy: > > I'm still trying to figure out when one would want to do that... I wanted to centre some text and truncate it to a fixed width. So I needed the middle N characters. -- Steven
[toc] | [prev] | [next] | [standalone]
| From | Wildman <best_lay@yahoo.com> |
|---|---|
| Date | 2016-05-18 23:17 -0500 |
| Message-ID | <l8udnZMUVeDdo6DKnZ2dnUU7-fmdnZ2d@giganews.com> |
| In reply to | #108766 |
On Thu, 19 May 2016 01:47:33 +1000, Steven D'Aprano wrote:
> Is this the simplest way to get the middle N characters?
This will return a sub-string of any length starting at any
point. This is the way the old VB mid$ function worked.
def mid(string, start, length):
# start begins at 0
if string = "":
return None
if start > len(string) - 1:
start = len(string) - 1
if length > len(string):
length = len(string)
if length < 1:
length = 1
return string[start : start + length]
--
<Wildman> GNU/Linux user #557453
"The Constitution only gives people the right to
pursue happiness. You have to catch it yourself."
-Benjamin Franklin
[toc] | [prev] | [next] | [standalone]
| From | boB Stepp <robertvstepp@gmail.com> |
|---|---|
| Date | 2016-05-20 23:00 -0500 |
| Message-ID | <mailman.69.1463803242.27390.python-list@python.org> |
| In reply to | #108766 |
On Wed, May 18, 2016 at 10:47 AM, Steven D'Aprano <steve@pearwood.info> wrote:
> Getting the middle N seems like it ought to be easy:
>
> s[N//2:-N//2]
>
> but that is wrong. It's not even the right length!
>
> py> s = 'aardvark'
> py> s[5//2:-5//2]
> 'rdv'
>
>
> So after spending a ridiculous amount of time on what seemed like it ought
> to be a trivial function, and an embarrassingly large number of off-by-one
> and off-by-I-don't-even errors, I eventually came up with this:
>
> def mid(string, n):
> """Return middle n chars of string."""
> L = len(string)
> if n <= 0:
> return ''
> elif n < L:
> Lr = L % 2
> a, ar = divmod(L-n, 2)
> b, br = divmod(L+n, 2)
> a += Lr*ar
> b += Lr*br
> string = string[a:b]
> return string
As some of you know, I usually post on the Tutor list while attempting
to learn Python as time permits. I had to try my hand at this problem
as a learning opportunity. I hope you don't mind if I explain how I
got to my solution and welcome your critiques, so I may improve. I
chose to cheat my answers to the right; I did not think about the
possibility of alternating the sides to allot the extra character
(when needed) to average things out until I read everyone's answers
after getting my own.
I started considering two strings, s_even = '0123456789' and s_odd =
'123456789', with trial values of n = 4 and n = 5 for how many
characters to extract. This gave me the following four desired
outputs to replicate:
1) s_even with n = 5. Desired output: '34567' (Cheating right.) =>
Slice s_even[3:8]
2) s_even with n = 4. Desired output: '3456' (Exact.) => Slice s_even[3:7]
3) s_odd with n = 5. Desired output: '34567' (Exact.) => Slice s_odd[2:7]
4) s_odd with n = 4. Desired output: '4567' (Cheating right.) =>
Slice s_odd[3:7]
Starting to generalize to get the desired indices for each case:
1) (len(s_even)//2 - n//2):(len(s_even)//2 + n//2 + 1)
2) (len(s_even)//2 - n//2):(len(s_even)//2 + n//2)
3) (len(s_odd)//2 - n//2):(len(s_odd)//2 + n//2 + 1)
4) (len(s_odd)//2 + 1 - n//2):(len(s_odd)//2 + n//2 + 1)
Looking at the starting index for each case, I had an extra 1 for case
(4), which, in table form:
n even n odd
s_even 0 0
s_odd 1 0
To duplicate this I came up with the expression: (len(s)%2) * (1 - n%2)
Similarly, for the ending slice index, all cases have an extra "+ 1"
except for case (2), with the following table:
n even n odd
s_even 0 1
s_odd 1 1
And the expression: 1 - ((len(s) + 1)%2 * (n +1)%2)
All this was scribbled onto scratch paper, so I hope I did not make
any typos! This led me to the following code:
py3: def mid(s, n):
... index0_offset = (len(s)%2) * (1 - n%2)
... index1_offset = 1 - ((len(s) + 1)%2) * ((n + 1)%2)
... index0 = len(s)//2 - n//2 + index0_offset
... index1 = len(s)//2 + n//2 + index1_offset
... return s[index0:index1]
...
py3: s = '0123456789'
py3: n = 5
py3: mid(s, n)
'34567'
py3: n = 4
py3: mid(s, n)
'3456'
py3: s = '123456789'
py3: n = 5
py3: mid(s, n)
'34567'
py3: n = 4
py3: mid(s, n)
'4567'
py3: s = 'aardvark'
py3: n = 5
py3: mid(s, n)
'rdvar'
This also returns an empty string for values of n <= 0.
As far as I can tell, my solution works (Given cheating right.). I
ran it on all of Steve's examples, and I got what I expected given
that I am consistently cheating right. But I am not sure my code
adequately conveys an understanding of what I am doing to the casual
reader. Thoughts?
TIA!
boB
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.python
csiph-web