Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

Linuxを使いたいけど、vi(Vim)って難しいよね。できれば使いたくない!……ってひと向けnanoエディタ


Linuxって使うのにいろいろハードルがあるのですが、
その一つの大きな障壁がコマンドライン上でのテキスト編集作業ではないでしょうか?
デフォルトで用意されていてかつメジャーなのが
viというこれまた変な操作体系のエディタで
最初使ったらなんじゃこりゃ。入力できないし、編集できないし……。
終了の仕方がわからず、そっとターミナルを閉じた……。
なんて人は多いんじゃないでしょうか?僕です。
第一、viなんてよっぽどのモノ好きでないと使いこなせるようになりません。
というか必要に迫られないと覚えられないようなものです。
Linuxが使いたいだけなのに変な操作体系を覚えさせられる…。
おれはLinuxをやりたいんだ。
でもなぜかviの学習をしている……。ってなんだかおかしいですよね。
GUIが整っている環境なら、geditというエディタを使って編集という手もありますが、
なかなかそうもいかず、ターミナル(黒い画面)経由でアクセスしてなんてことも多いですよね。
また、VPSなんか使って最小構成にしていると、GUIすらないし……。
ああ、もう、FTPかSCPでファイル取ってきてWindows上で編集してまたあげるか……
それは過去の僕です。
やっぱりviか……となるんですが、ターミナル上でもメモ帳感覚で使えるエディタがあるのです!
それがnanoエディタ。CentOSならだいたい最初から入っていることが多いです。
もしない場合は下記コマンドでインストールできます。(ルートユーザでやってね!)

使ってみよう

# yum install nano

起動は次のようにします。ファイルネームを引数にしてあげます。

$ nano [filename]

今回はtestというファイル名でファイルを作ってみるので

$ nano test

でエンターダンッ!
CentOSのターミナル上でつかうとこうなります(Ubuntu/Macでも使えるよ!)。

日本語環境をちゃんと整え切っていないので英語になっていますが、
日本語にも対応できます。
基本的にメモ帳感覚で使えます。
保存がCtrl+Oなのがちょっと奇妙ですが……。
もちろんvi同様カスタマイズができて、homeに.nanorcを置いて
何か書いてやればカスタマイズも可能です。
ちなみにviな人が使って油断するとjjjjjkkkkkとか思わず入力してしまいます。
※私はVimmerです

関連記事

Javaで優先順位をつけて複数のキーでソートする方法



久々にPureなJavaを書きました(*´Д`)

複数のキーでソートする書き方の説明

いろいろ書き方はありますが、
対象のクラスにComparableをimplementsして
compareTo()をオーバーライドすると
ソート順を定義できます。
そのうえで、

-1(左が先)
 0(同じ)
 1(右が先)

の値を返してやればいいのですが、
複数ある時は、
第1条件の判定結果を変数に格納してやり、
それで0の時は第2条件を判定、
さらに0の時は第3条件を判定……
さらに0の時は第4条件を判定……
とすると、第1条件、第2条件、第3条件………の順でソートされます。

ちなみに並び順を変えたいときは定数なり、enumなりでcompareTo()の中を分岐させます。

サンプルコード

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Data
@AllArgsConstructor
class Student implements Comparable<Student> {
    // 並び順にあまり意味はない…並び替えの名前もテキトー
    public static enum SortMethod {
        GRADE_CLAZZ_ASC,   /* 学年(ASC) > 学級(ASC) > 名前(ASC) > 班長(T>F) */
        GRADE_CLAZZ_DESC,  /* 学年(DSC) > 学級(DESC) > 名前(DESC) > 班長(F>T) */
    }
    @Getter
    @Setter
    private static SortMethod method = SortMethod.GRADE_CLAZZ_ASC;

    private int grade; /* 学年 */
    private String clazz; /* 学級 */
    private String name; /* 名前 */
    private boolean isLeader; /* 班長 */

    @Override
    public int compareTo(Student o) {
        int result = 0;
        if (method == SortMethod.GRADE_CLAZZ_ASC) {
            result = Integer.valueOf(this.grade).compareTo(Integer.valueOf(o.grade));
            if (result == 0) {
                result = this.clazz.compareTo(o.clazz);
            }
            if (result == 0) {
                result = this.name.compareTo(o.clazz);
            }
            if (result == 0) {
                result = Boolean.valueOf(this.isLeader).compareTo(Boolean.valueOf(o.isLeader));
            }
        } else if (method == SortMethod.GRADE_CLAZZ_DESC) {
            result = -Integer.valueOf(this.grade).compareTo(Integer.valueOf(o.grade));
            if (result == 0) {
                result = -this.clazz.compareTo(o.clazz);
            }
            if (result == 0) {
                result = -this.name.compareTo(o.clazz);
            }
            if (result == 0) {
                result = -Boolean.valueOf(this.isLeader).compareTo(Boolean.valueOf(o.isLeader));
            }
        }
        return result;
    }

    @Override
    public String toString() {
        return "学年 [" + this.grade + "] クラス [" + this.clazz + "] 名前 [ "
                + String.format("%1$-20s", this.name)  // 20文字右スペース埋め (こんな便利なメソッドあったんだ)
                + "] " + (isLeader ? "班長" : "");
    }
}

public class SortSample {
    public static void main(String[] args) {
        // テキトーにデータをぶち込む
        List<Student> students = new ArrayList<>(
                Arrays.asList(
                        new Student(6, "A", "Robin Givens", true),
                        new Student(6, "A", "Richard Burgi", false),
                        new Student(2, "A", "Sarah Walker", false),
                        new Student(2, "A", "Jon Casey", false),
                        new Student(2, "B", "Morgan Grimes", false),
                        new Student(1, "A", "Elenor Batorwski", false),
                        new Student(6, "B", "Agatha Christie", true),
                        new Student(5, "A", "Tony Todd", true),
                        new Student(5, "A", "Matthew Bomer", false),
                        new Student(4, "A", "Linda Hamilton", false),
                        new Student(3, "A", "Lauren Cohan", true),
                        new Student(2, "A", "Chales Bartowski", true),
                        new Student(5, "B", "Rachel Bilson", true),
                        new Student(4, "A", "Nicole Richie", false),
                        new Student(1, "B", "Devon Woodcomb", false)
                )
        );
        Student.setMethod(Student.SortMethod.GRADE_CLAZZ_ASC);
        Collections.sort(students);
        for (Student s : students) {
            System.out.println(s);
        }
        System.out.println("------------------------------------------------");
        // Stream APIでソート
        Student.setMethod(Student.SortMethod.GRADE_CLAZZ_DESC);
        students.stream().sorted((e1, e2) -> e1.compareTo(e2)).forEach(System.out::println);
        System.out.println("------------------------------------------------");
    }
}

Lombokを使っていますので、とりあえず、Mavenを書きます。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>SortSample</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

実行結果

学年 [1] クラス [A] 名前 [ Elenor Batorwski    ] 
学年 [1] クラス [B] 名前 [ Devon Woodcomb      ] 
学年 [2] クラス [A] 名前 [ Sarah Walker        ] 
学年 [2] クラス [A] 名前 [ Jon Casey           ] 
学年 [2] クラス [A] 名前 [ Chales Bartowski    ] 班長
学年 [2] クラス [B] 名前 [ Morgan Grimes       ] 
学年 [3] クラス [A] 名前 [ Lauren Cohan        ] 班長
学年 [4] クラス [A] 名前 [ Linda Hamilton      ] 
学年 [4] クラス [A] 名前 [ Nicole Richie       ] 
学年 [5] クラス [A] 名前 [ Tony Todd           ] 班長
学年 [5] クラス [A] 名前 [ Matthew Bomer       ] 
学年 [5] クラス [B] 名前 [ Rachel Bilson       ] 班長
学年 [6] クラス [A] 名前 [ Robin Givens        ] 班長
学年 [6] クラス [A] 名前 [ Richard Burgi       ] 
学年 [6] クラス [B] 名前 [ Agatha Christie     ] 班長
------------------------------------------------
学年 [6] クラス [B] 名前 [ Agatha Christie     ] 班長
学年 [6] クラス [A] 名前 [ Richard Burgi       ] 
学年 [6] クラス [A] 名前 [ Robin Givens        ] 班長
学年 [5] クラス [B] 名前 [ Rachel Bilson       ] 班長
学年 [5] クラス [A] 名前 [ Matthew Bomer       ] 
学年 [5] クラス [A] 名前 [ Tony Todd           ] 班長
学年 [4] クラス [A] 名前 [ Nicole Richie       ] 
学年 [4] クラス [A] 名前 [ Linda Hamilton      ] 
学年 [3] クラス [A] 名前 [ Lauren Cohan        ] 班長
学年 [2] クラス [B] 名前 [ Morgan Grimes       ] 
学年 [2] クラス [A] 名前 [ Chales Bartowski    ] 班長
学年 [2] クラス [A] 名前 [ Jon Casey           ] 
学年 [2] クラス [A] 名前 [ Sarah Walker        ] 
学年 [1] クラス [B] 名前 [ Devon Woodcomb      ] 
学年 [1] クラス [A] 名前 [ Elenor Batorwski    ] 
------------------------------------------------

あとがき

けっこうこういうコード書くはずなのだけど、ぐぐってもほとんど出てこなかったので書きました。
っていうかあれか、たいていDBから取ってくるときに既にソートして取ってるから
そのあと並び替えするなんてことあんまりしないですよね。
本当はStrategyパターンを書きたかったのですが、前書きで終了してしまいました。
次はStrategyパターンを書きます。

とおもったけど、そもそもStream APIでComparator.comparing()/Comparator.thenComparing()を使えばOKです。
blog1.mammb.com

C++のmapとJavaのmapの挙動が違う件

f:id:fa11enprince:20200701021345j:plain
C++でmapを書いててinsertしまくってましたが、
あれれ?Javaと挙動が違うということに気付きました。
mapでinsertするとC++ではキーが重複したときに、
insertされず、valueが上書きされない。
一方、Javaのほうはputしたときに
最後にputしたものでvalueが上書きされる
というようになっていました。

C++のコード

#include <iostream>
#include <string>
#include <unordered_map>

using namespace std;

int main()
{
    unordered_map<string, int> strmap;
    string k;
    int v;
    while (cin >> k >> v)
    {
        if (cin.eof())   // Ctrl+D (Unix/Linux), Ctrl+Z(Windows)
        {
            break;
        }
        strmap.insert({k, v});
    }

    for (const auto& s : strmap)
    {
        cout << s.first << " => " << s.second << '\n';
    }

    return 0;
}

実行結果

途中まで入力してCtrl+DまたはCtrl+Zで終了しています

aaa 1
bbb 1
aaa 2
bbb 2
bbb => 1
aaa => 1

お、おう、わざと無理やり最後の値でinsertするってことが
単純にはできないのか…

Javaのコード

package purin;

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Test1 {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        Scanner in = new Scanner(System.in);
        while (in.hasNext()) {
            String buf = in.nextLine();
            String[] bufs = buf.split(" ");
            map.put(bufs[0], Integer.parseInt(bufs[1]));
        }
        for (Map.Entry<String, Integer> m : map.entrySet()) {
            System.out.println(m.getKey() + " => " + m.getValue());
        }
        if (in != null) {
            in.close();
        }
    }
}

実行結果

aaa 1
bbb 1
aaa 2
bbb 2
aaa => 2
bbb => 2

うん、普段触ってる言語はこっちの動きだ……

C++で同じようなことを実現する方法

※追記 こんなことしなくてよいです!
詳細は id:yasuharu519 さんのコメントを参照ください。
operator[]で要素追加できるなんて盲点でした(´Д` )!!

#include <iostream>
#include <string>
#include <unordered_map>

using namespace std;

int main()
{
    unordered_map<string, int> strmap;
    string k;
    int v;
    while (cin >> k >> v)
    {
        if (cin.eof())   // Ctrl+D (Unix/Linux), Ctrl+Z(Windows)
        {
            break;
        }
        // not found
        if (strmap.find(k) == strmap.end())
        {
            strmap.insert({k, v});
        }
        // found
        else
        {
            strmap[k] = v;
        }
    }

    for (const auto& s : strmap)
    {
        cout << s.first << " => " << s.second << '\n';
    }

    return 0;
}

実行結果はJavaのと同様なので省略します。

うん。要はちゃんとチェックしろってことですね。
find()で見つからないときは最後の要素の次(iterator::end())を返します。
最後の要素でなくて最後の要素の次ですね。C++を使い慣れている人には常識ですが……。
ここ重要なのでテストに出ます。

わかりにくい関数ポインタを返す関数

f:id:fa11enprince:20200624215158j:plain
関数ポインタって変な文法ですよねってお話です。
もちろんポインタの文法もいろいろツッコミどころはあるのですが
(このせいでポインタよくわからんな人が続出)。
例えば次の宣言はぱっと見すぐにわかる人はC言語マスターです。

void (*func(const char *str)) ();

これはvoidを返すのではなく、
void (*)()という関数ポインタを返す、
funcという名前のconst char *strを引数にとる関数です(;'∀')

(void (*)()) func(const char *str);

のような書き方が許されていれば誰も混乱しないのに……

コードを書いてみます。
gccでコンパイルしてみました(VC++だともっとincludeしないとダメかも)

#include <stdio.h>

void ps1()
{
    puts("banana");
}
void ps2()
{
    puts("apple");
}
void ps3()
{
    puts("orange");
}
void (*func(const char *str)) ()
{
    if (strcmp(str, "ps1") == 0)
    {
        return ps1;
    }
    else if (strcmp(str, "ps2") == 0)
    {
        return ps2;
    }
    else if (strcmp(str, "ps3") == 0)
    {
        return ps3;
    }
    return NULL;
}

int main()
{
    func("ps1")();
    // こう書いても同じ
    //void (*test)() = func("ps1");
    //(*test)();
    
    func("ps2")();
    func("ps3")();
    return 0;
}

実行結果

banana
apple
orange

これtypedefを使うともっとましになるよってことで書いてみます。

#include <stdio.h>

typedef void (*FUNC_PS)();

void ps1()
{
    puts("banana");
}
void ps2()
{
    puts("apple");
}
void ps3()
{
    puts("orange");
}
FUNC_PS func(const char *str)
{
    if (strcmp(str, "ps1") == 0)
    {
        return ps1;
    }
    else if (strcmp(str, "ps2") == 0)
    {
        return ps2;
    }
    else if (strcmp(str, "ps3") == 0)
    {
        return ps3;
    }
    return NULL;
}

int main()
{
    func("ps1")();
    func("ps2")();
    func("ps3")();
    return 0;
}

多少マシになりましたね。
ただ、見て分かる通り、ここでも問題が、
typedefがわかりにくい……
なぜ、typedef FUNC_PS void(*)();ではないのだ……
これならだれもこんがらがらないのに……

じゃあ、そんな書き方できるのあるよ!!ってことでC++11ならば

#include <cstring>
#include <cstdio>
#include <cstdlib>

using FUNC_PS = void (*)();

void ps1()
{
    std::puts("banana");
}
void ps2()
{
    std::puts("apple");
}
void ps3()
{
    std::puts("orange");
}
FUNC_PS func(const char *str)
{
    if (std::strcmp(str, "ps1") == 0)
    {
        return ps1;
    }
    else if (std::strcmp(str, "ps2") == 0)
    {
        return ps2;
    }
    else if (std::strcmp(str, "ps3") == 0)
    {
        return ps3;
    }
    return NULL;
}

int main()
{
    func("ps1")();
    func("ps2")();
    func("ps3")();
    return 0;
}

これならわかりやすい!!
ということでこれならOKでしょう。

ただし、C++11使うならstd::functionを使いましょう。

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <functional>

void ps1()
{
    std::puts("banana");
}
void ps2()
{
    std::puts("apple");
}
void ps3()
{
    std::puts("orange");
}
std::function<void()> func(const char *str)
{
    if (std::strcmp(str, "ps1") == 0)
    {
        return ps1;
    }
    else if (std::strcmp(str, "ps2") == 0)
    {
        return ps2;
    }
    else if (std::strcmp(str, "ps3") == 0)
    {
        return ps3;
    }
    return NULL;
}

int main()
{
    func("ps1")();
    func("ps2")();
    func("ps3")();
    return 0;
}

世の中にはもっと変態的な宣言がありまして
signalが有名ですね

void (*signal(int sig, void (*func)(int))) (int);

Σ(゚д゚lll)!!!?
もうチョイわかりやすく書くと…

void (*signal(
    int sig, /* シグナル番号 */
    void (*func) (int) /* シグナルハンドラ */
)) (int);

これは
void (*)(int)を返す
signalという名前のintとvoid (*func)(int)を引数にとる関数です。
typedefしてあるとなんてことはない…

typedef void (*sighandler_t)(int);
sighandler_t signal(int signo, sighandler_t func);

typedefの文法がもっとましならよかったのに…

using sighandler_t = void (*)(int);
sighandler_t signal(int signo, sighandler_t func);

だったらよかったのに……
というかこれならだれも混乱せずに読めるのに

(void (*)(int)) signal(int sig, void (*func)(int));