TIL - Python itertools groupby 활용

CodeSignal이라는 알고리즘 문제 풀이 사이트에서 문제에서 정해준 규칙으로 문자열을 변경하는 Line Encoding이라는 문제를 푸는데..

규칙은 아래와 같았습니다.

  • First, the string is divided into the least possible number of disjoint substrings consisting of identical characters
    • for example, "aabbbc" is divided into ["aa", "bbb", "c"]
  • Next, each substring with length greater than one is replaced with a concatenation of its length and the repeating character
    • for example, substring "bbb" is replaced by "3b"
  • Finally, all the new strings are concatenated together in the same order and a new string is returned.

예시

For s = "aabbbc", the output should be
lineEncoding(s) = "2a3bc".

조금 무식하게 풀긴 풀었는데.. 풀고나서 다른 문제풀이들을 보니 itertools 모듈의 groupby를 활용해서 간단하게 푸는 것을 볼 수 있었습니다.

‘aabbbc’라는 문자열이 있을 때 아래와 같이 활용 가능합니다.

1
2
3
4
from itertools import groupby

for k, g in groupby(input_string):
print(k, list(g), len((list(g))))
1
2
3
'a' ['a', 'a'] 2
'b' ['b', 'b', 'b'] 3
'c' ['c'] 1

Python - json.loads()와 json.dumps()의 차이

JSON (JavaScript Object Notation)

json object는 key value pair로 이루어지고 { } 중괄호에 의해 둘러쌓여있습니다.

json.loads()

  • json.loads()는 문자열을 받아서 json object를 return합니다.

json.dumps()

  • json.dumps()는 json object를 받아서 문자열을 return 합니다.

json

카카오 맵 API를 활용한 좌표 <-> 주소 변환

API 활용 준비

https://developers.kakao.com

카카오 개발자 페이지에서 여러 카카오 제품의 API를 활용할 수 있는데, 그 중에서 좌표와 주소 변환 관련해서는 지도/로컬 제품을 사용하면 됩니다.

REST API 테스트 페이지에서는 어떤 기능을 어떻게 활용할 수 있는지 확인할 수 있습니다. 확인해 볼 기능은 좌표와 주소간 변환이니 아래 이미지의 노란색 2개를 확인해보겠습니다.

좌표변환

주소 -> 좌표 변환

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import requests

KAKAO_REST_API_KEY = '' # 발급받은 API KEY

def convert_address_to_coordinates(address):
"""
입력받은 주소를 WGS84 좌표계 좌표로 변환
"""

url = 'https://dapi.kakao.com/v2/local/search/address.json?query=' + address
header = {'Authorization': 'KakaoAK ' + KAKAO_REST_API_KEY}

r = requests.get(url, headers=header)

if r.status_code == 200:
lng = float(r.json()["documents"][0]["address"]['x'])
lat = float(r.json()["documents"][0]["address"]['y'])
else:
return None

return lat, lng

# Test
convert_address_to_coordinates("서울특별시 강남구 강남대로 396") # 강남역 주소

# Output
# (37.4981646510326, 127.028307900881)

좌표가 맞는지 지도에 찍어봤는데, 강남역을 가리키고 있습니다.

지도좌표검색

좌표 -> 주소 변환

이번에는 좌표에서 주소로 변환해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def convert_coordinates_to_address(lat, lng):
"""
입력받은 위도, 경도를 도로명, 지번 주소로 변환
"""

y, x = str(lat), str(lng)
url = 'https://dapi.kakao.com/v2/local/geo/coord2address.json?x={}&y={}'.format(x, y)
header = {'Authorization': 'KakaoAK ' + KAKAO_REST_API_KEY}

r = requests.get(url, headers=header)

if r.status_code == 200:
road_address = r.json()["documents"][0]["road_address"]['address_name']
bunji_address = r.json()["documents"][0]["address"]['address_name']
else:
return None

return road_address, bunji_address

# TEST
convert_coordinates_to_address(37.4981646510326, 127.028307900881)

# Output
# ('서울특별시 강남구 강남대로 396', '서울 강남구 역삼동 804')

이전에 강남역 좌표로 얻은 것을 다시 역지오코딩을 했을 때 강남역 주소가 나오는 것을 확인할 수 있습니다.

Open Source Routing Machine (OSRM) with Python

OSRM 최적의 경로 찾기

Open Source Routing Machine (OSRM)은 지도상의 도로 Network에서 최단 경로를 계산하는 C ++ 라우팅 엔진입니다.

지도상의 포인트 A와 포인트 B 사이의 거리를 구하는 가장 쉬운 방법은 직선거리를 계산하는 것입니다.

그러나 직선거리로 계산하게되면 도로 상황을 무시해서 실제 이동거리와 많은 차이가 발생할 수 있는데, 이 OSRM API를 활용하면 네이버 길찾기처럼 경로, 소요시간, 이동거리 등과 같은 값을 얻을 수 있어서 유용합니다.

OSRM

API Document

API Document를 보면 Nearest, Route, Trip등 다양한 Service가 있는데, 이 중에서 Route service를 활용해보려고 합니다.

OSRM API Doc

신도림역 -> 문래역 Route

  • 이동수단도 선택이 가능한데 아래에서는 bike로 설정
    • car, bike, foot
  • route는 map에서 그리기 용이한 형태로 저장
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import requests
import folium
import polyline
import json
import pandas as pd

def get_route(origin_lon, origin_lat, dest_lon, dest_lat):
"""출발지, 도착지 좌표를 입력해서 Route 정보 Return"""

loc = "{},{};{},{}".format(origin_lon, origin_lat, dest_lon, dest_lat)
url = "http://router.project-osrm.org/route/v1/bike/"
r = requests.get(url + loc)
if r.status_code!= 200:
return {}

res = r.json()
routes = polyline.decode(res['routes'][0]['geometry'])
start_point = [res['waypoints'][0]['location'][1], res['waypoints'][0]['location'][0]]
end_point = [res['waypoints'][1]['location'][1], res['waypoints'][1]['location'][0]]
distance = res['routes'][0]['distance']

route = {'route':routes,
'start_point':start_point,
'end_point':end_point,
'distance':distance
}

return route


origin_lon, origin_lat, dest_lon, dest_lat = 126.890975,37.508767,126.89472929779438, 37.51792066883597
test_route = get_route(pickup_lon, pickup_lat, dropoff_lon, dropoff_lat)
1
2
3
4
5
6
7
8
9
10
11
12
13
{'route': [(37.50901, 126.89072),
(37.50918, 126.89083),
(37.50932, 126.89135),
(37.50992, 126.89262),
(37.51094, 126.89181),
(37.51198, 126.89393),
(37.51315, 126.89728),
(37.51476, 126.89392),
(37.51491, 126.89407),
(37.51792, 126.89477)],
'start_point': [37.50901, 126.890723],
'end_point': [37.517916, 126.894771],
'distance': 1584.2}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def get_map(route):
"""출발지, 도착지, route 정보를 folium map에 표시"""
route_map = folium.Map(location=[(route['start_point'][0] + route['end_point'][0])/2,
(route['start_point'][1] + route['end_point'][1])/2],
zoom_start=13)

folium.PolyLine(
route['route'],
weight=8,
color='blue',
opacity=0.6
).add_to(route_map)

folium.Marker(
location=route['start_point'],
icon=folium.Icon(icon='play', color='green')
).add_to(route_map)

folium.Marker(
location=route['end_point'],
icon=folium.Icon(icon='stop', color='red')
).add_to(route_map)

return route_map

Route 시각화

신도림역 -> 문래역 Route가 map에 잘 표현된 걸 확인할 수 있습니다.

OSRM API TEST

TIL - Pyhton 'list' object has no attribute 'split'

구글링하면서 임기응변식으로 Python을 사용하고 학습하게 되었을 때 이런 부분들에서 약점이 생기는 것 같습니다.

위 제목의 에러가 발생하게 된 원인은 list에 split 메소드를 사용했기 때문인데, split 메소를 어떻게 써야하는지 document를 보면서 짚고 넘어가려고 합니다.

Error Message

1
'list' object has no attribute 'split'

Document

str split

첫 문구에 string의 word를 list형태로 return해준다고 나와있습니다. 즉, list에 직접 split 메소드를 쓸 수 없고 list안에 있는 문자열을 순회하면서 사용하면 됩니다.