シ〜らかんす

プログラミングとか、カメラとか。

ghq & hubを使って、organizationの全リポジトリをcloneする方法

最初に結論から

hubとghqを利用すると、orgs配下の全てのリポジトリをcloneすることができます。 転職したときなど、新しいorgに参加した際の初期セットアップの際に便利です。

導入の動機は?

転職した会社のリポジトリをとりあえず全部cloneしようと思ったけど、100以上リポジトリがあった。 100回以上git clone唱えるのが嫌だったので、一括でできる方法を探した。

手順をご紹介

まずは、事前準備

参考までに、~/.gitconfig 内にこういう風に書くと、ghqのrootディレクトリを ~/ghqに設定できます。

[ghq]
    root = ~/ghq

orgsの全リポジトリを実際にcloneするコマンドを順に紹介

  1. まずは、リポジトリのリストをhub apiコマンドで取得する
hub api orgs/{あなたのorgの名前}/repos | jq --raw-output '.[].full_name' > repolist.txt

ページングを利用する場合(100件ずつ)

hub api orgs/{あなたのorgの名前}/repos\?per_page=100\&page=1 | jq --raw-output '.[].full_name' > repolist.txt
hub api orgs/{あなたのorgの名前}/repos\?per_page=100\&page=2 | jq --raw-output '.[].full_name' >> repolist.txt
  1. repolist.txtから、全件リポジトリをcloneする
cat repolist.txt | ghq get --parallel -p

結果

ghqのrootディレクトリが仮に ~/ghq/ に設定されているとすると、 ~/ghq/github.com/{あなたのorgの名前}/ 配下に、全てのリポジトリがcloneされているはずです!

【Python】検索の速度なら、配列よりも辞書が優秀という話

どうも、駆け出しのエンジニアのMasaです!

今回は、Pythonの配列(list)と辞書(dict)の検索速度の違いについて書きたいと思います。

この記事で書いていること

まずは結論から。

  • 特定のデータを保持しているか検索する際、dictのkeyを使う方が、listを使うよりも圧倒的に速い
  • その理由は、dictはhash tableを使用しているから
  • LeetCodeの問題を例として紹介

では、詳細を見ていきます。

あるLeetCodeの問題を解いていて

配列と辞書の検索速度について調べることになったのは、あるLeetCodeの問題がきっかけでした。

leetcode.com

一言でいうと、「inputとして与えられたLinked Listが循環参照になっているかどうか調べて、True/Falseを返すメソッドを書いてください」という問題です。

「Linked List」というのは、複数のノードから成るデータ構造のことで、それぞれのノードがvalueと次のノードを指し示すpointerを保持しています。

詳しくは、下記の記事がわかりやすいです。

英語の記事ですが、図とコードを見るだけで、Linked Listが何者なのか大体わかります。

medium.com

さて、この問題ですが、一番シンプルで簡単な方法は、

  • Linked Listを前から順番に見ていって、出現したnodeを配列や辞書などに格納していく

  • 循環していれば、どこかの時点で、nodeのpointerが、記録済みのnodeを指しているはずなので、それを検知する

というやり方かなと思います。

LeetCode公式が出している、一番最初の解答例もこのやり方です。

もうお分かりだと思いますが、「あるnodeが記録済みのnodeを指し示しているか」を調べる際に、配列と辞書で大きな差が出るのです。

listよりも、dictの方が検索するのが圧倒的に速い

私が最初に書いたのは、配列を使う方法でした。

class Solution(object):
    def hasCycle(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        l = []
        while True:
            if not head:
                return False
            l.append(head)
            if head.next in l:
                return True
            else:
                head = head.next

このコードの結果がこちら。

f:id:sas-surfer0jonny:20190428125633p:plain
配列を使ったとき

速度が全然出ていません。

調べた結果、配列よりもdictを使った方が断然速く、かつPython3.6以降はメモリ使用にも関しても差が小さくなったということが分かりました。

dictを使うようにしたコードがこちら。

class Solution(object):
    def hasCycle(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        l = {}
        while head:
            l.setdefault(head, True)
            if l.has_key(head.next):
                return True
            head = head.next
        return False

結果は、  

f:id:sas-surfer0jonny:20190428130108p:plain
辞書を使ったとき

一気に速くなりました。

こんなに差が出るのは、dictがhash tableというデータ構造を使っているからです。

hash tableとは?

wikipediaの定義だと、

ハッシュテーブルはキーをもとに生成されたハッシュ値を添え字とした配列である。

とのこと。

https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB

つまり、dictのkeyを検索するときは、keyを元に生成されたハッシュ値を検索しているということです。(たぶん)

中身を探索しにいってしまう配列と比べれば、速度に大きな差が出るのは明らかですね。

まとめ

dictのkeyを使う方が、listを使うよりも検索が速いというお話でした。

LeetCodeみたいなコンテストなら、迷わずdictを選ぶべきでしょう。

ただ、実務においては、使いもしないdictのvalueを適当に入れたりするのは、他メンバーの混乱を招きそうな気もするので、どうなのかなと思ったりもしました。

参考にしたサイト

http://www.jessicayung.com/how-python-implements-dictionaries/

【LeetCode道場】26. Remove Duplicates from Sorted Array

何を書いている記事なのか

LeetCodeというサイトでプログラミングの問題を解きながら、効率の良いコードを書けるようになるよう修行しています。

その過程をブログに残しておこうと思い、書きました。

私の最初の回答、リファクタリング後の回答、その他気づきや学びを記したいと思います。

今回の問題は、 「26. Remove Duplicates from Sorted Array」です

leetcode.com

問題の詳細

難易度はEasyです。 私にはまだ、Easyしか解ける力がありません。日々精進あるのみ。

問題文はこちら。

Given a sorted array nums, remove the duplicates in-place such that each element appear only once and return the new length.

Do not allocate extra space for another array, you must do this by modifying the input array in-place with O(1) extra memory.

要するに、「ソートされた配列が入力として与えられるので、配列内のダブりを無くし、加工後の配列のlengthを返せ」ということですね。

私の最初の回答

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        del_idx = []
        new_nums = []
        for i, num in enumerate(nums):
            del_idx.append(i) if num in new_nums else new_nums.append(num)
        
        for i, idx in enumerate(del_idx):
            del nums[idx - i]
                
        return len(nums)

これは、正解こそしたものの、実行時間は下から数えて全体の6.03%、メモリー使用量は下から数えて全体の5.43%という惨憺たる結果でしたwwww

まあ、ここで課題が見つかったことをポジティブに捉えつつ、頑張ってリファクタリングしていきます。

リファクタリング

上記のコードのまずそうなところを予想すると、

  • まず、新しく空の配列を二つも用意して処理していて、メモリ効率悪い。
  • ループが2回あって、処理速度が遅い

新しい配列を使わない、かつループの回数を抑えるには、1度のループでダブりチェックと排除の両方の仕事をこなさないとなりません。

他の人がどうしてんのかチラ見してみると、whileを使ったり、integerを一緒に走らせたりと色々工夫を凝らしています。

それらをパクって参考にしつつ、修正したコードがこちら。

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        if not nums:
            return 0
        else:
            i,j=1,1
            while j<len(nums):
                if nums[i-1]!=nums[j]:
                    nums[i]=nums[j]
                    i+=1
                j+=1
            return i

いやあ、賢いやり方ですねえ。

自分の力だけでこれだけ書けるようになりたいものです。先は長い。

ただ、これでもスピードは全体の50%くらいの立ち位置でした。 メモリ効率に至っては、最初の私のコードとほとんど立ち位置が変わってないという状況。

LeetCodeの世界は厳しい。

気づき・学び

LeetCodeの世界では、新しい変数を作ることによるメモリの発行。ループ処理による速度の問題に対して、かなり気を使わねばならないということを改めて痛感した問題でした。

1つの変数、1つのループにどれだけ意味を持たせられるかが勝負です。分かっていてもできないのですけどもね。。。

今回賢いなあと思ったのは、"i"という遅い進み方をする変数と、"j"という速い進み方をする変数を使って、配列のダブりを徐々に消していくという処理。ソートされた配列という前提があるからこそ使える技ですが。 私の引き出しの一つにしたいと思います。

今回はこれで以上です。

【Python】argparseで任意引数を受け取る方法

pythonで引数を受け取る方法

argparseというライブラリを使うと、簡単に引数を足すことができます。

import argparse

def main(start_at, end_at):
    do_something

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("start_at", help="対象年月日のはじめ")
    parser.add_argument("end_at", help="対象年月日の終わり")
    args = parser.parse_args()

    main(args.start_at, args.end_at)

sysを使って引数を受け取ることもできますが、引数を扱う上で便利な機能がargparseの方が充実している印象です。

  • add_argumentメソッドのhelp引数に、説明を加えておくことができ、ドキュメンテーションラク
  • add_argumentメソッドのaction引数に、store_truestore_false など入れて、型や値を指定できる etc...

任意の引数を受け取るには?

上記のコードでは、start_atとend_atという引数は必ずなくてはならず、存在しないとエラーになってしまいます。

引数の付与は任意、としたい場合は、下記のように書けます。

import argparse

def main(start_at, end_at):
    do_something

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--start_at", help="対象年月日のはじめ")
    parser.add_argument("--end_at", help="対象年月日の終わり")
    args = parser.parse_args()

    main(args.start_at, args.end_at)

add_argumentメソッドで引数の名前を指定する時に、名前の直前に -- (ハイフン二つ)を足しています。

こうすることで、start_atとend_atはあってもなくてもいい、任意の引数として設定されました。

任意引数を与えたいときのコマンドの書き方

任意引数は、コマンドを書く時に以下のように「引数ありますよー」ということを明記してやる必要があるので注意してください。

python sample.py --start_at=2019-03-19 --end_at=2019-03-20

これがもし必須の引数の場合は、以下のような書き方ができます。

pythono sample.py 2019-03-19 2019-03-20

参考サイト

【Django】モデルの複数カラムをセットでユニークに設定する方法

いつもやり方忘れてしまうので、メモ。

単一カラムに対するユニーク設定

djangoでモデルのあるカラムをユニークにするには、 以下のようにモデル定義のオプション引数として unique=True を渡してやればOKです。

from django.db import models


class SomeModel(models.Model):
    column1 = models.CharField(max_length=255, unique=True)

複数カラムセットでユニークにする設定

カラム一つに対してユニーク設定をかけたい場合は上記で問題ありませんが、「複数カラムセットでユニーク」な設定をかけたい場合は、以下のように書く必要があります。

from django.db import models


class SomeModel(models.Model):
    column1 = models.CharField(max_length=255)
    column2 = models.CharField(max_length=255)

    class Meta:
        unique_together = ("column1", "column2",)

参考にしたサイト

https://docs.djangoproject.com/en/2.1/ref/models/fields/#django.db.models.Field.unique

https://stackoverflow.com/questions/2201598/how-to-define-two-fields-unique-as-couple